20

I'm in the middle of a discussion trying to figure out whether unaligned access is allowable in C++ through reinterpret_cast. I think not, but I'm having trouble finding the right part(s) of the standard which confirm or refute that. I have been looking at C++11, but I would be okay with another version if it is more clear.

Unaligned access is undefined in C11. The relevant part of the C11 standard (§ 6.3.2.3, paragraph 7):

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.

Since the behavior of an unaligned access is undefined, some compilers (at least GCC) take that to mean that it is okay to generate instructions which require aligned data. Most of the time the code still works for unaligned data because most x86 and ARM instructions these days work with unaligned data, but some don't. In particular, some vector instructions don't, which means that as the compiler gets better at generating optimized instructions code which worked with older versions of the compiler may not work with newer versions. And, of course, some architectures (like MIPS) don't do as well with unaligned data.

C++11 is, of course, more complicated. § 5.2.10, paragraph 7 says:

An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified.

Note that the last word is "unspecified", not "undefined". § 1.3.25 defines "unspecified behavior" as:

behavior, for a well-formed program construct and correct data, that depends on the implementation

[Note: The implementation is not required to document which behavior occurs. The range of possible behaviors is usually delineated by this International Standard. — end note]

Unless I'm missing something, the standard doesn't actually delineate the range of possible behaviors in this case, which seems to indicate to me that one very reasonable behavior is that which is implemented for C (at least by GCC): not supporting them. That would mean the compiler is free to assume unaligned accesses do not occur and emit instructions which may not work with unaligned memory, just like it does for C.

The person I'm discussing this with, however, has a different interpretation. They cite § 1.9, paragraph 5:

A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible executions of the corresponding instance of the abstract machine with the same program and the same input. However, if any such execution contains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).

Since there is no undefined behavior, they argue that the C++ compiler has no right to assume unaligned access don't occur.

So, are unaligned accesses through reinterpret_cast safe in C++? Where in the specification (any version) does it say?

Edit: By "access", I mean actually loading and storing. Something like

void unaligned_cp(void* a, void* b) {
  *reinterpret_cast<volatile uint32_t*>(a) =
    *reinterpret_cast<volatile uint32_t*>(b);
}

How the memory is allocated is actually outside my scope (it is for a library which can be called with data from anywhere), but malloc and an array on the stack are both likely candidates. I don't want to place any restrictions on how the memory is allocated.

Edit 2: Please cite sources (i.e., the C++ standard, section and paragraph) in answers.

nemequ
  • 16,623
  • 1
  • 43
  • 62
  • What does access mean? Aliasing, or just casting pointer types to and fro? – Columbo Sep 15 '15 at 15:05
  • Aliasing—specifically, I'm interested in loads and stores to misaligned `uint32_t`s. – nemequ Sep 15 '15 at 15:12
  • It may help the discussion if you post some code that you think might permit unaligned access. If you cannot think of any such code snippet, that is good evidence that there is none. – M.M Sep 15 '15 at 15:15
  • That's undefined behavior. There is no such thing as a misaligned object; A condition for the beginning of an objects lifetime is that properly aligned storage is obtained ([basic.life]/(1.1)). – Columbo Sep 15 '15 at 15:15
  • @Columbo how about: `int *ptr = (int *)((char *)malloc(5000) + 1);` – M.M Sep 15 '15 at 15:18
  • @M.M: okay, question edited. – nemequ Sep 15 '15 at 15:21
  • @M.M The status quo is that malloc doesn't create objects. – Columbo Sep 15 '15 at 15:21
  • @Columbo Shafik's answer [here](http://stackoverflow.com/questions/30114397/constructing-a-trivially-copyable-object-with-memcpy) discusses a proposal that it should ; I think the status quo is best described as "unclear". I don't think there would be much support for a position that `int *ptr = (int *)malloc(sizeof(int)); *ptr = 5;` causes UB since you write an object that wasn't created. – M.M Sep 15 '15 at 15:23
  • @nemequ you have to show where the objects are created (or allegedly created at least). For example if `a` was allocated as `uint8_t buf[100];` then this code violates the strict aliasing rule. The strict aliasing rule guarantees no unaligned access in objects declared with a type , but it is less clear in the case of objects created in malloc'd space, or even in space allocated by `new char[]`. – M.M Sep 15 '15 at 15:26
  • @M.M: I'm not actually in control of where the objects are allocated, but any of those (especially malloc) are likely candidates. To add to the fun, the code will often be called from C. – nemequ Sep 15 '15 at 15:33
  • The reason that unaligned reads are sometimes not implemented at hardware level is to save complexity and gates. An unaligned access not only has potential to span cache lines, but also page boundaries - possibly with different MMU mappings. There are a lot of edge cases. On that basis its perfectly legitimate to fix the edge cases up in software with a trap. Believe some ARM parts allowed aligned access, but returned clear the bottom few bits of the access address and then rotate the result. – marko Sep 15 '15 at 16:55
  • Slight annoyance: you changed your question from being one about accessing misaligned pointers, to dereferencing misaligned pointers. All of your quotes are about the *pointer value*, not the act of dereferencing, which also explains your confusion (if you thought they where about dereferencing). – Yakk - Adam Nevraumont Sep 15 '15 at 19:41

3 Answers3

10

Looking at 3.11/1:

Object types have alignment requirements (3.9.1, 3.9.2) which place restrictions on the addresses at which an object of that type may be allocated.

There's some debate in comments about exactly what constitutes allocating an object of a type. However I believe the following argument works regardless of how that discussion is resolved:

Take *reinterpret_cast<uint32_t*>(a) for example. If this expression does not cause UB, then (according to the strict aliasing rule) there must be an object of type uint32_t (or int32_t) at the given location after this statement. Whether the object was already there, or this write created it, does not matter.

According to the above Standard quote, objects with alignment requirement can only exist in a correctly aligned state.

Therefore any attempt to create or write an object that is not correctly aligned causes UB.

Columbo
  • 60,038
  • 8
  • 155
  • 203
M.M
  • 138,810
  • 21
  • 208
  • 365
  • I like this answer, but I think it is incomplete without answering the question of what constitutes allocating an object of a type. If nobody answers that part I'll accept this answer and create another question for that issue. – nemequ Sep 15 '15 at 16:29
  • 1
    @nemequ that would be a separate issue. Read through [this question](http://stackoverflow.com/questions/30114397/constructing-a-trivially-copyable-object-with-memcpy) first. – M.M Sep 15 '15 at 20:59
3

EDIT This answers the OP's original question, which was "is accessing a misaligned pointer safe". The OP has since edited their question to "is dereferencing a misaligned pointer safe", a far more practical and less interesting question.


The round-trip cast result of the pointer value is unspecified under those circumstances. Under certain limited circumstances (involving alignment), converting a pointer to A to a pointer to B, and then back again, results in the original pointer, even if you didn't have a B in that location.

If the alignment requirements are not met, than that round trip -- the pointer-to-A to pointer-to-B to pointer-to-A results in a pointer with an unspecified value.

As there are invalid pointer values, dereferencing a pointer with an unspecified value can result in undefined behavior. It is no different than *(int*)0xDEADBEEF in a sense.

Simply storing that pointer is not, however, undefined behavior.

None of the above C++ quotes talk about actually using a pointer-to-A as a pointer-to-B. Using a pointer to the "wrong type" in all but a very limited number of circumstances is undefined behavior, period.

An example of this involves creating a std::aligned_storage_t<sizeof(T), alignof(T)>. You can construct your T in that spot, and it will live their happily, even though it "actually" is an aligned_storage_t<sizeof(T), alignof(T)>. (You may, however, have to use the pointer returned from the placement new for full standard compliance; I am uncertain. See strict aliasing.)

Sadly, the standard is a bit lacking in terms of what object lifetime is. It refers to it, but does not define it well enough last I checked. You can only use a T at a particular location while a T lives there, but what that means is not made clear in all circumstances.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    If a system were to use one word to hold an `int*` but two words to hold a `char*` or `void*` [e.g. some systems use word addresses, but include an instruction to access a half-word at a certain byte offset from a given word address], would the system be allowed to trap if an attempt were made to cast to `int*` a `char*` with an odd offset, or would the system be required to have the cast yield a pointer that could be assigned (though not necessarily dereferenced) without trapping? – supercat Sep 15 '15 at 17:20
  • @supercat good question; I'd have to check what the standard says about trap representations of pointers themselves. Can copying a pointer with unspecified value cause a trap? – Yakk - Adam Nevraumont Sep 15 '15 at 17:31
  • Given `int foo; int *x,*y;`, the statement `x=(int*)(((char*)&foo)+1);` could legitimately cause `x` to hold a trap representation, and if it does so, `y=x;` would have Undefined Behavior. What's somewhat less clear is whether storing an implementation-defined or unspecified value *directly* from an expression that generates it has defined behavior. I see no benefit to forbidding compilers from trapping on attempts to *generate* invalid values, but that would render the distinction between "generate unspecified value" or "generate implementation-defined value [that could be a trap...]"... – supercat Sep 15 '15 at 18:11
  • ...as being essentially synonymous with "invoke Undefined Behavior" except in the case where code never tried to do anything with a value it generates (in which case the code could never have any non-empty defined behavior) – supercat Sep 15 '15 at 18:12
  • @supercat `(char*)(int*)(((char*)&foo)+1)` is an expression. `x=` that is another expression. I'm wondering if the copy of the (temporary rvalue) `char*` into `x` could be a "copy of a trap representation" (of a pointer), and hence legitimately UB. The value of the pointer is clearly unspecified (assuming `alignof(int) > alignof(char)`). And `int i; (int*)(char*)(&i)` is fully specified by the above. The case of `(int*)(((char*)&foo)+1)` is similar. – Yakk - Adam Nevraumont Sep 15 '15 at 18:16
  • My point is that I'm unclear on what the Standard is trying to say by having it be an unspecified value, rather than saying that the cast invokes Unspecified Behavior. There are a number of places where the Standard allows Undefined Behavior [e.g. relational operators on unrelated pointers] where I think it would have been better to require an implementation to define a macro specifying one of several behaviors, with one of the choices being "Undefined Behavior" [but with a recommendation that compilers should try to be more specific when practical], but here the Standard doesn't say UB. Why? – supercat Sep 15 '15 at 18:30
  • @supercat Well, it might be the case here that unspecified pointer values *can* be safely copied to another pointer value. In which case, there is a difference between undefined behavior and unspecified value here. I don't know! As for the standard in general, C and C++ like specifying minimal amounts required to generate a useful program, and leave things that could cause performance issues in any implementation unspecified or undefined. Well defining error cases leads to compilers having to inject code to *test* for error cases, even when the programmer can prove they cannot occur. – Yakk - Adam Nevraumont Sep 15 '15 at 18:34
  • The set of useful behaviors that can be guaranteed on many platforms exceeds the set that can be guaranteed on all. If the Standard were to say that implementations which can guarantee that relational operators define an ordered relation in which distinct objects do not overlap should define some macro (e.g. __UNREL_PTR_COMPARE) to 3, those which cannot guarantee a non-overlapping ordering must define a number less than 3, but if they can guarantee consistency they should define it to 2, those which can't guarantee consistency must define it less than 2, but if they can guarantee... – supercat Sep 15 '15 at 18:39
  • ...that relational operators will always yield a 0 or 1, they should define it to 1, and those that can't guarantee anything must define it to 0, then code which would benefit from being able to use relational operators on undefined pointers would be allowed to do so safely on platforms that accommodate the code's needs (and refuse compilation on platforms that don't). – supercat Sep 15 '15 at 18:42
1

All of your quotes are about the pointer value, not the act of dereferencing.

5.2.10, paragraph 7 says that, assuming int has a stricter alignment than char, then the round trip of char* to int* to char* generates an unspecified value for the resulting char*.

On the other hand, if you convert int* to char* to int*, you are guaranteed to get back the exact same pointer as you started with.

It doesn't talk about what you get when you dereference said pointer. It simply states that in one case, you must be able to round trip. It washes its hands of the other way around.


Suppose you have some ints, and alignof(int) > 1:

int some_ints[3] ={0};

then you have an int pointer that is offset:

int* some_ptr = (int*)(((char*)&some_ints[0])+1);

We'll presume that copying this misaligned pointer doesn't cause undefined behavior for now.

The value of some_ptr is not specified by the standard. We'll be generous and presume it actually points to some chunk of bytes within some_bytes.

Now we have a int* that points to somewhere an int cannot be allocated (3.11/1). Under (3.8) the use of a pointer to an int is restricted in a number of ways. Usual use is restricted to a pointer to an T whose lifetime has begun allocated properly (/3). Some limited use is permitted on a pointer to a T which has been allocated properly, but whose lifetime has not begun (/5 and /6).

There is no way to create an int object that does not obey the alignment restrictions of int in the standard.

So the theoretical int* which claims to point to a misaligned int does not point to an int. No restrictions are placed on the behavior of said pointer when dereferenced; usual dereferencing rules provide behavior of a valid pointer to an object (including an int) and how it behaves.


And now our other assumptions. No restrictions on the value of some_ptr here are made by the standard: int* some_ptr = (int*)(((char*)&some_ints[0])+1);.

It is not a pointer to an int, much like (int*)nullptr is not a pointer to an int. Round tripping it back to a char* results in a pointer with unspecified value (it could be 0xbaadf00d or nullptr) explicitly in the standard.

The standard defines what you must do. There are (nearly? I guess evaluating it in a boolean context must return a bool) no requirements placed on the behavior of some_ptr by the standard, other than converting it back to char* results in an unspecified value (of the pointer).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524