54

Is the behaviour of this code well defined?

#include <stdio.h>
#include <stdint.h>

int main(void)
{
    void *ptr = (char *)0x01;
    size_t val;

    ptr = (char *)ptr + 1;
    val = (size_t)(uintptr_t)ptr;

    printf("%zu\n", val);
    return 0;
}

I mean, can we assign some fixed number to a pointer and increment it even if it is pointing to some random address? (I know that you can not dereference it)

curiousguy
  • 8,038
  • 2
  • 40
  • 58
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • Did you try to compile and run it? –  Jun 28 '18 at 12:50
  • 1
    The pointer value is just another value that is stored in a memory location and you can do arithmetic with it. When you try to deference it then only the value is interpreted as a memory location. – Ankur Jun 28 '18 at 12:50
  • 25
    @Dan - Hardly a test of code validity. Plenty of code with undefined behavior "compiles and runs". – StoryTeller - Unslander Monica Jun 28 '18 at 12:50
  • Why would you want to do this? – Scott Hunter Jun 28 '18 at 12:50
  • 4
    @Dan just because code compiles does not necessarily mean it is well-defined behavior. – Christian Gibbons Jun 28 '18 at 12:50
  • @StoryTeller & ChristianGibbons '... and run it'. Considering the print statement, it should print the value 2. That's the well-defined behaviour check right there. If it prints anything else than 2 its not well-defined. –  Jun 28 '18 at 12:55
  • 37
    @Dan - What part of "undefined" is not clear? Undefined doesn't mean "unexpected results". It can *appear* to work just fine for years. That's the problem. – StoryTeller - Unslander Monica Jun 28 '18 at 12:56
  • 28
    @Dan *Considering the print statement, it should print the value 2. That's the well-defined behaviour check right there.* I'll get right to the point: that statement of yours demonstrates a complete lack of understanding of "undefined behavior". "I ran it and it even emitted the output I expected" most certainly does not preclude undefined behavior. – Andrew Henle Jun 28 '18 at 13:03
  • 11
    @Dan In other words, Undefined Behavior roughly means that the spec doesn't specify what should happen. In fact, IIRC the spec defines that any program with undefined behavior can compile to *whatever the compiler wants* since it's undefined. That includes both "compiles perfectly to the instructions you expected/wanted", but also "compiles into code that just prints 2", and also "fails to compile". Something doing what you expect can still be undefined behavior. – Delioth Jun 28 '18 at 14:25
  • 8
    @Dan: Among the behaviors that fall under the description of *undefined behavior* is "do exactly what you expect when you're testing, and then do something else entirely when you're doing something important and/or have convinced yourself that this can never be the cause of the bugs you're finding". –  Jun 28 '18 at 14:36
  • 1
    @KeineLust It really is, though. Don't. If you have any other option, you'll be much happier not doing naughty things with them, and if you don't, you can probably abstract them behind a couple of functions so at least you're not trying to figure out `((void(*)(char*))0x20000EE0)("Hello, World!")` when that could just as easily have been `puts`. That said, this question is useful academically; it's good to know what's UB/IDB. – Nic Jun 28 '18 at 17:20
  • 1
    @Dan Code containing Undefined Behaviour is simply *broken*. No "if"s, "but"s or *anything* - *wrong*, *broken*, period. It may seem to do what you expect now, but change compiler or compiler version or compiler flags or OS or upgrade a dynamic library or many other things and you may suddenly see completely different behaviour. You broke the rules of the language and no longer have *any* guarantees on what will happen. – Jesper Juhl Jun 28 '18 at 17:30
  • This is sometimes called "implementation-undefined": there is implementation-defined behaviour, and depending on exactly what the implementation defines, the follow-up code may or may not have UB – M.M Jun 28 '18 at 21:16
  • @Hurkyl: Also among them are "behave in a documented fashion characteristic of the environment". The authors of the Standard have explicitly recognized that much of the usefulness of the language comes from the fact that implementations allow non-portable programs to do things that portable programs can't. They expected that compiler writers would recognize that a quality implementation intended for low-level programming on a platform should expose characteristic behaviors of that platform, even if implementations which aren't intended to be suitable for such purposes need not do so. – supercat Jun 29 '18 at 15:57
  • @JesperJuhl: The authors of the Standard explicitly recognize in the Rationale that an implementation could comply with all of the requirements of the Standard and yet be of such low quality as to be essentially useless. It would not be possible for something to be a quality implementation suitable for many purposes without behaving predictably in some cases where the Standard imposes no requirements, and the fact that code for a particular purpose won't work on anything other than quality implementations that are suitable for such purpose should hardly be viewed as a defect. – supercat Jun 29 '18 at 16:03
  • @M.M: Unfortunately, the authors of the Standard thought compiler writers would recognize that quality implementations intended for certain targets and application fields must (in order to be quality implementations suitable for those purposes) behave predictably in cases beyond those mandated by the Standard, without the Standard having to explicitly say such things. Nobody thought it necessary to explicitly document that compilers behave sensibly in cases when it could obviously useful, on platforms where behaving sensibly would be simpler and easier than doing anything else. – supercat Jun 29 '18 at 16:13
  • @supercat: ... which is great a great comment to reassure the programmers in niche domains that know how they're using the features their compiler vendor guarantees to elicit specific behavior. But it's an incredibly bad comment to make in a discussion where people think "it did it once in an artificial toy problem" is the gold standard for determining consistent behavior. –  Jun 29 '18 at 16:36
  • @supercat: The problem is "sensible behavior" is in the eye of the beholder. For example, it's obviously useful for compilers to emit efficient code for all meaningful inputs, and think that's the only sensible behavior. Others seem to find it more sensible for compilers to emit inefficient code if that's what is needed to produce specific garbage outputs from garbage input. –  Jun 29 '18 at 16:43
  • @Hurkyl: On any compiler suitable for low-level or systems programming, lvalue reads and writes will have natural behavior defined in terms of converting a pattern of bits to a value of a given type, or converting a value of a given type to a pattern of bits. While an implementation may be suitable for low-level programming without behaving in that fashion in all conceivable circumstances, someone seeking to produce a quality implementation suitable for low-level programming should support all semantics that would be achievable with such behavior, and should when practical... – supercat Jun 29 '18 at 18:10
  • ...try to behave in that fashion in non-contrived cases where it is likely to matter (if customers complain about cases where it doesn't, that's a pretty good sign that those cases do matter). I'd regard a compiler that could safely support an easily-identifiable 90% of low-level programs while achieving 80% of the theoretically-achievable optimizations as superior to one that couldn't reliably support many low-level programs without disabling almost all optimizations. – supercat Jun 29 '18 at 18:15
  • @Hurkyl: Somewhat more broadly, most programs have a general requirement "When given valid input, produce valid output; when given invalid input, refrain from launching nuclear missiles". A good language should allow a programmer to meet both requirements without too much more effort than would be required to meet just the first. Many "optimizations" allow implementations to improve the efficiency of code that meets only the first requirement, but at the expense of increasing the amount of work programmers must do to meet the second, and in many cases reducing the efficiency of such code. – supercat Jun 29 '18 at 18:29
  • An increment of a void pointer should produce a compiler error. an increment of a char * pointer will increment by the size of (' '); 1 an increment of a int * pointer will increment by the size of an int. and so forth for pointers to variables and structures. I leave it to you to look at a pointer to a function. (can it be incremented without a cast? – Leslie Satenstein Apr 01 '20 at 01:37

5 Answers5

68

The assignment:

void *ptr = (char *)0x01;

Is implementation defined behavior because it is converting an integer to a pointer. This is detailed in section 6.3.2.3 of the C standard regarding Pointers:

5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.

As for the subsequent pointer arithmetic:

ptr = (char *)ptr + 1;

This is dependent on a few things.

First, the current value of ptr may be a trap representation as per 6.3.2.3 above. If it is, the behavior is undefined.

Next is the question of whether 0x1 points to a valid object. Adding a pointer and an integer is only valid if both the pointer operand and the result point to elements of an array object (a single object counts as an array of size 1) or one element past the array object. This is detailed in section 6.5.6:

7 For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type

8 When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P) ) and (P)-N (where N has the value n ) point to, respectively, the i+n-th and i−n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

On a hosted implementation the value 0x1 almost certainly does not point to a valid object, in which case the addition is undefined. An embedded implementation could however support setting pointers to specific values, and if so it could be the case that 0x1 does in fact point to a valid object. If so, the behavior is well defined, otherwise it is undefined.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • 1
    I was wondering about that section in 6.5.6 and how it applies to this case. if `(char *)ptr` is considered to be pointing to the first element of a 1-element array, then would `(char *)ptr + 1` not be pointing one past the last element of a 1-element array? – Christian Gibbons Jun 28 '18 at 13:16
  • 2
    Your quote of p8 would be relevant if the OP didn't add `+1`. But... There's [p7](https://port70.net/~nsz/c/c11/n1570.html#6.5.6p7) to consider. – StoryTeller - Unslander Monica Jun 28 '18 at 13:17
  • Nitpicking. Just for completness, `char` pointers are allowed pointer arithmetics when they point inside an object, whatever the type (from 6.3.2.3 § 7 in draft n1570 for C11). Anyway it is irrelevant here... – Serge Ballesta Jun 28 '18 at 13:25
  • @ChristianGibbons A pointer can have a value of one past the end of the array but you must not dereference it there. – Goswin von Brederlow Jun 28 '18 at 13:46
  • @GoswinvonBrederlow Right. OP specified that there would be no dereferencing of the pointer, just the pointer arithmetic. – Christian Gibbons Jun 28 '18 at 14:12
  • 2
    I think the pointer arithmetic is implementation defined because: The assignment is implementation defined. If the implementation results in a pointer to inside a char array then `ptr + 1` is well defined. On the other hand if the implementation returns e.g. a trap then it is UB. – Goswin von Brederlow Jun 28 '18 at 14:20
  • 3
    @GoswinvonBrederlow: I don't think implementation defined behavior is allowed to be undefined. But I know what you're trying to say -- whether or not the addition is undefined behavior depends on implementation defined choices. –  Jun 28 '18 at 14:31
  • @Hurkyl Converting to a trap representation is within acceptable implementation defined behaviour. See 6.3.2.3/5 – Passer By Jun 28 '18 at 15:07
  • “The pointer does not point to a valid object“ — What’s missing from this answer is an explanation of what would constitute a valid object; and in particular, why the pointee *isn’t* a (possibly implementation defined) valid object. I only know the relevant rules for C++, not C, so I’m genuinely curious. – Konrad Rudolph Jun 28 '18 at 16:43
  • 2
    Strictly speaking, couldn't this be well-defined on a specific implementation? i.e. if `(int *)0x01` was defined by my fictional implementation WeirdC to point to a valid char array, would that make the rest valid as well, albeit only on WeirdC? I ask because this is relevant for e.g. embedded development, where you might well be using pointers to some given integer value to modify data. – Nic Jun 28 '18 at 17:15
  • 1
    @KonradRudolph You are correct. I've added additional explanation regarding the cases when the addition could be well defined vs. undefined. – dbush Jun 28 '18 at 18:27
  • @GoswinvonBrederlow The cast might result in a one-past-the-end pointer in which case adding `1` to it is undefined – M.M Jun 28 '18 at 21:11
  • If the implementation considers whole addressable space as an object (does anything prevent it?), then `(char*)1` most likely points inside it, then all arithmetic on it should be well defined. – Andrew Svietlichnyy Jun 30 '18 at 17:17
  • @AndrewSvietlichnyy That's basically what every C compiler does on hardware that doesn't have a trap representation. But it's still all implementation defined behavior. Conversion from int to pointer is already implementation defined. It doesn't even have to result in an address pointing to 0x1. It's just that most implementations have the int-pointer conversion simply preserve the bit pattern (since that's a NOP) and most hardware have the whole addressable space as one object. – Goswin von Brederlow Jul 02 '18 at 12:07
18

No, the behaviour of this program is undefined. Once an undefined construct is reached in a program, any future behaviour is undefined. Paradoxically, any past behaviour is undefined too.

The result of void *ptr = (char*)0x01; is implementation-defined, due in part to the fact that a char can have a trap representation.

But the behaviour of the ensuing pointer arithmetic in the statement ptr = (char *)ptr + 1; is undefined. This is because pointer arithmetic is only valid within arrays including one past the end of the array. For this purpose an object is an array of length one.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/174056/discussion-on-answer-by-bathsheba-does-the-c-standard-permit-assigning-an-arbitr). –  Jun 29 '18 at 19:24
8

Yes, the code is well-defined as implementation-defined. It is not undefined. See ISO/IEC 9899:2011 [6.3.2.3]/5 and note 67.

The C language was originally created as a system programming language. Systems programming required manipulating memory-mapped hardware, requiring that you would stuff hard-coded addresses into pointers, sometimes increment those pointers, and read and write data from and to the resulting address. To that end, assigning and integer to a pointer and manipulating that pointer using arithmetic is well defined by the language. By making it implementation-defined, what the language allows is that all kinds of things can happen: from the classic halt-and-catch-fire to raising a bus error when trying to dereference an odd address.

The difference between undefined behaviour and implementation-defined behaviour is basically undefined behaviour means "don't do that, we don't know what will happen" and implementation-defined behaviour means "it's OK to go ahead and do that, it's up to you to know what will happen."

Stephen M. Webb
  • 1,705
  • 11
  • 18
  • 2
    “manipulating that pointer using arithmetic is well defined by the language” — citation needed. – Konrad Rudolph Jun 28 '18 at 16:44
  • 4
    The language standard specifically defines that pointer arithmetic is only valid within the bounds of an object – M.M Jun 28 '18 at 21:12
  • 2
    I think it's pretty clear that this is UB in the C standard (because of typically creating a pointer outside the bounds of any object), but many implementations will define the behaviour of pointers in more cases than the C standard does. e.g. if you don't dereference them, on many implementations it is safe to create pointers that don't point inside any object. – Peter Cordes Jun 29 '18 at 03:21
  • @PeterCordes: The Standard makes no attempt to mandate that all implementations be suitable for low-level systems programming, nor does it in any way imply that it's possible to have a quality implementation that is suitable for low-level or systems programming without it supporting behaviors beyond those mandated by the Standard (and which might not be processed predictably by implementations that aren't suitable for systems programming). – supercat Jun 29 '18 at 16:20
8

It is undefined behavior.

From N1570 (emphasis added):

An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.

If the value is a trap representation, reading it is undefined behavior:

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.) Such a representation is called a trap representation.

And

An identifier is a primary expression, provided it has been declared as designating an object (in which case it is an lvalue) or a function (in which case it is a function designator).

Therefore, the line void *ptr = (char *)0x01; is already potentially undefined behavior, on an implementation where (char*)0x01 or (void*)(char*)0x01 is a trap representation. The left-hand side is an lvalue expression that does not have character type and reads a trap representation.

On some hardware, loading an invalid pointer into a machine register could crash the program, so this was a forced move by the standards committee.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • 1
    So far as I can tell, even on platforms where loading an invalid pointer into a machine register could trigger a trap, the statement `int *p = (int*)someValue;` would have defined behavior provided nothing tries to use the pointer in `p`, regardless of whether the expression yields a valid pointer. Were that not the case, the construct would always invoke Undefined Behavior, since--precisely as with UB--the behavior would be behave usefully on implementations that choose to specify useful behavior, and behave unpredictably on implementations that don't. – supercat Jun 29 '18 at 20:50
  • @supercat It looks to me that, on a machine where `(char*)0x01` were a trap representation, `ptr` in the expression `void *ptr = (char*)0x01;` is a lvalue expression that reads that value and does not have character type, which is stated to be UB? If not, the next line that uses `ptr` definitely is. Am I misunderstanding? – Davislor Jun 29 '18 at 21:58
  • The line which uses reads `ptr` and casts it to `uintptr_t` would invoke UB if `(char*)1` were a trap representation. The presence within an expression of an lvalue that holds a trap representation, however, only invokes UB in cases where the lvalue is evaluated. In the declaration `void *ptr = (char*)0x01`; however, there is no lvalue, and if the declaration and store were split off as in `void *ptr; ptr = (char*)0x01;` the statement after the declaration would contain the lvalue `ptr`, but would write to that lvalue without evaluating it. – supercat Jul 05 '18 at 19:11
  • @supercat I quote the section of the (draft) standard that says an identifier is a lvalue, and for a lvalue (such as `ptr`) whose type is not a character type (such as `void*`) to read a trap representation is already UB. So, a compiler would be permitted to emit a machine instruction to read the invalid bit pattern into an address or segment register, on a CPU where that would cause a hardware trap. If you interpret the standard differently, then we can still agree that the increment is UB in this case? – Davislor Jul 05 '18 at 19:25
  • @supercat And that’s a reasonable behavior characteristic of many environments. Many architectures, including x86 with segment registers in protected mode, have trap representations for pointers, and the expected behavior of an int-to-pointer conversion could well be to load that exact bit pattern and shoot yourself in the foot. – Davislor Jul 05 '18 at 19:38
  • I'll agree that the Standard does not require implementations to define the behavior of evaluating an lvalue of pointer type, nor would it require implementations to define the behavior of adding 1 to a pointer formed from an integer-to-pointer cast in any circumstances (except when the value being converted is a Null Pointer Constant), though quality implementations should, at minimum, define such behavior in cases where the integer value in question was produced by converting a pointer to `intptr_t` or `uintptr_t`. – supercat Jul 05 '18 at 19:39
  • @supercat But keep in mind, many ISAs have bitfields inside their pointers, or word-addressing. Strictly defining the result of incrementing a cast pointer might preclude the most natural and efficient conversion. – Davislor Jul 05 '18 at 20:12
  • I'm not saying that implementations for all architectures should be expected to define the effect of applying to increment every possible pointer, but that if the Standard would define the behavior of incrementing a particular pointer, then putting it through a round trip to/from uintptr_t and then incrementing the result of that conversion (as opposed to incrementing the original uintptr_t) should yield the same behavior on a quality implementation. – supercat Jul 05 '18 at 20:36
  • @supercat It does guarantee, “Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.” That implies, to me, that the pointer comparing equal must be a valid pointer to the same object. – Davislor Jul 05 '18 at 20:55
  • Given `struct { int x[1],y[1]; } it;`, a compiler may (and for most compilers, likely would) lay things out so that after `int *p = it.x+1, *q = it.y;`, pointers `p` and `q` would compare equal but would not be equivalent. Putting p or q through a round-trip conversion through `uintptr_t` could yield either `p` or `q`, chosen in Unspecified fashion [since they compare equal, either would be allowable]. Consequently, the only operations that could be done on the result of that conversion would be those that are valid on both `p` and `q`. – supercat Jul 05 '18 at 21:17
  • Do you agree or disagree that in a situation like the above, `p` and `q` could compare equal without being usable to access the same storage? I don't see any reason the Standard shouldn't guarantee that a round-trip conversion of a pointer through `uintptr_t` within the lifetime of the original pointer's target must yield a pointer that can access everything the original one could, but the Standard doesn't actually say any such thing. – supercat Jul 06 '18 at 18:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/174520/discussion-between-davislor-and-supercat). – Davislor Jul 06 '18 at 19:20
4

The Standard does not require that implementations process integer-to-pointer conversions in a meaningful fashion for any particular integer values, or even for any possible integer values other than Null Pointer Constants. The only thing it guarantees about such conversions is that a program which stores the result of such a conversion directly into an object of suitable pointer type and does nothing with it except examine the bytes of that object will, at worst, see Unspecified values. While the behavior of converting an integer to a pointer is Implementation-Defined, nothing would forbid any implementation (no matter what it actually does with such conversions!) from specifying that some (or even all) of the bytes of the representation having Unspecified values, and specifying that some (or even all) integer values may behave as though they yield trap representations.

The only reasons the Standard says anything at all about integer-to-pointer conversions are that:

  1. In some implementations, the construct is meaningful, and some programs for those implementations require it.

  2. The authors of the Standard did not like the idea of a construct that was used on some implementations would represent a constraint violation on others.

  3. It would have been odd for the Standard to describe a construct but then specify that it has Undefined Behavior in all cases.

Personally, I think the Standard should have allowed implementations to treat integer-to-pointer conversions as constraint violations if they don't define any situations where they would be useful, rather than require that compilers accept the meaningless code, but that wasn't the philosophy at the time.

I think it would be simplest to simply say that any operation involving integer-to-pointer conversions with anything other than intptr_t or uintptr_t values received from pointer-to-integer conversions invokes Undefined Behavior, but then note that it is common for quality implementations intended for low-level programming to process Undefined Behavior "in a documented manner characteristic of the environment". The Standard doesn't specify when implementations should process programs that invoke UB in that fashion but instead treats it as a Quality of Implementation issue.

If an implementation specifies that integer-to-pointer conversions operate in a fashion that would define the behavior of

char *p = (char*)1;
p++;

as equivalent to "char p = (char)2;", then the implementation should be expected to work that way. On the other hand, an implementation could define the behavior of integer-to-pointer conversion in such a way that even:

char *p = (char*)1;
char *q = p;  // Not doing any arithmetic here--just a simple assignment

would release nasal demons. On most platforms, a compiler where arithmetic on pointers produced by integer-to-pointer conversions behaved oddly would not be viewed as a high-quality implementation suitable for low-level programming. A programmer that is not intending to target any other kind of implementations could thus expect such constructs to behave usefully on compilers for which the code was suitable, even though the Standard does not require it.

lazzy_ms
  • 1,169
  • 2
  • 18
  • 40
supercat
  • 77,689
  • 9
  • 166
  • 211
  • if `char *p = (char *)1;` didn't cause UB then the further assignment to `q` would not either. The first stage of problem comes with the result of the cast being assigned to `p` – M.M Jun 30 '18 at 08:20
  • @M.M: If an lvalue holds a trap representation, reading it will produce UB. Since the Standard does not allow an integer-to-pointer conversion to do anything other than yield a pointer, nor does it specify that storing a pointer into an existing object of appropriate type can do anything other than alter the contents of that object. – supercat Jun 30 '18 at 17:43
  • The standard allows the integer-pointer conversion to yield a trap representation, and storing that in `p` would cause UB – M.M Jul 01 '18 at 09:10
  • 1
    @M.M: In what cases is the act of *storing* a trap representation yield UB? The Standard specifically says that the action yields a pointer that may be a trap representation, rather than saying that the action may invoke UB. If the cast itself could invoke UB, that would of course allow it to yield a trap representation, but saying so would be meaningless since the cast would be allowed to do anything else whatsoever. Note that "might" is rather vague as to whether it is granting permission to the implementation to do something other than yield a pointer to a valid object, or... – supercat Jul 01 '18 at 18:29
  • 1
    ...is giving permission to the programmer to form such a pointer without invoking UB, provided that the pointer isn't actually used in an invalid way [and, as noted, I know of nothing to suggest that the act of storing anything into a legitimate object of a particular type using an lvalue of that type might do anything other than alter the stored value in that object]. – supercat Jul 01 '18 at 18:32
  • 1
    @M.M: Of course, the authors of the Standard don't make much effort to handle actions which would have meaningful and necessary on some platforms and application fields, but not others. Occasionally, they say such actions should have defined behavior even on platforms/fields where they would serve no purpose. More often, they invoke UB even on platforms/fields where it's meaningful and necessary. If quality implementations for such platforms can be expected to process them in a fashion characteristic of the environment regardless of what the Standard mandates... – supercat Jul 01 '18 at 19:06
  • 1
    ...it shouldn't really matter whether the Standard defines such behaviors or not. The only reason any of these issues pose problems is that some compiler writers have become more interested in identifying cases that can be regarded as invoking UB than in cases where platforms would have useful characteristic behaviors which programmers might benefit from or rely upon. – supercat Jul 01 '18 at 19:07
  • @supercat Agreed. In theory, the Standard doesn’t guarantee much more than round-trip compatibility. In practice, people want to be able to compile kernels and device drivers, which must deal with absolute addresses. – Davislor Jul 05 '18 at 19:31
  • 1
    @Davislor: Even when round-tripping, the Standard imposes no requirements about what happens if code does anything with the resulting pointer other than compare it for equality with other pointers. Obviously a quality implementation should be able to offer behavioral guarantees in many such cases, but the Standard doesn't require it. – supercat Jul 05 '18 at 19:42