4 ms·
The UB in unaligned pointers is even worse: an unaligned pointer in itself is UB, not only an access to it. So even implicit casting a void*v to an int*i (like
by beeforpork 5mo ago
The UB in unaligned pointers is even worse: an unaligned pointer in itself is UB, not only an access to it. So even implicit casting a void*v to an int*i (like 'i=v' in C or 'f(v)' when f() accepts an int*) is UB if the cast pointer is not aligned to int.
It is important to understand that this is a C level problem: if you have UB in your C program, then your C program is broken, i.e., it is formally invalid and wrong, because it is against the C language spec. UB is not on the HW, it has nothing to do with crashes or faults. That cast from void* to int* most likely corresponds to no code on the HW at all -- types are in C only, not on the HW, so a cast is a reinterpretation at C level -- and no HW will crash on that cast (because there is not even code for it). You may think that an integer value in a register must be fine, right? No, because it's not about pointers actually being integers in registers on your HW, but your C program is broken by definition if the cast pointer is unaligned.
- tovej 5mo agoBut that seems obvious. You can't load an integer from an unaligned address. It's not only C-level is it. There's no (guarantee across architectures for) machine code for that either.
- mbel 5mo agoUnless your code targets some exotic architecture, like idk x86.
- cataphract 5mo agoNot really. Wait until the compiler starts vectorizing your code and using instructions requiring alignment (like the ones with A or NT in the mnemonic).
- saagarjha 5mo agoUsually the compiler will probably not generate those
- bigfishrunning 5mo ago> Usually...probably... you're betting against the compiler ever improving.
- saagarjha 5mo agoThis would be a regression
- bigfishrunning 5mo agoWhy? Automatic vectorization is pretty bad and has been for years, but wouldn't it be nice if the compiler could unroll-loops and use SIMD instructions to make your code faster while also being correct?
- saagarjha 5mo agoIf you are using SIMD instructions on x86 you probably want the unaligned ones
- pjc50 5mo agoYou missed the point: the pointer existing as a value of that type at all is UB, even if you never try to access anything through it and no corresponding machine code is ever emitted.
- tovej 5mo agoYes? I agree with that. I don't really see the issue there. The computer will allocate data in aligned addresses, so you would have to be doing something weird to begin with to access unaligned pointers. And aligned access is always better anyway. I guess packed structs are a thing if you're really byte golfing. Maybe compressed network data would also make sense. But then I would assume you are aware of unaligned pointers, and have a sane way to parse that data, rather than read individual parts of it from a raw pointer. I am curious, what would be a legitimate reason for an unaligned pointer to int?
- simonask 5mo agoString search algorithms would be one example, where a 64-bit register can be used as a “vector” containing 8x1 bytes.
- jstimpfle 5mo agoWhere is the part about unaligned pointers?
- simonask 5mo agoStrings typically consist of UTF-8 bytes, and any old `char*` pair has no alignment guarantees.
- jstimpfle 5mo agoThat's true, and that's why your typical string vector code has a prelude and a postlude to do the incomplete chunks at the ends. Between the ends, it's processing larger self-aligned chunks.
- codeflo 5mo ago> You can't load an integer from an unaligned address. You can, and the results are machine specific, clearly defined and well-documented. Ancient ARM raises an exception, modern ARM and x86 can do it with a performance penalty. It's only the C or C++ layer that is allowed to translate the code into arbitrary garbage, not the CPU.
- matheusmoreira 5mo agoSure you can. In many architectures it works just fine. Works perfectly in x86_64, for example. It's just a little slower.
- tovej 5mo agoIn many architectures does not mean you can. The standard is supposed to cover all architectures.
- matheusmoreira 5mo agoIf some architecture traps on unaligned access, then the compiler can and should simply generate the correct code so that it loads the integer piece by piece instead. Load multiple integers and shift and mask away the irrelevant bits, done. This is exactly what modern architectures already do in hardware. Works, it's just a little slower. This is exactly what the compilers do if you use a packed structure to access unaligned data. Works everywhere, as expected. Compilers have always known what to do, they just weren't doing it. C standard says no. The fact is the standard is garbage and the first thing every C programmer should learn is that they can and should ignore it. There is never any reason to wonder what the standard is supposed to do. The only thing that matters is what compilers actually do.
- bluGill 5mo agoThe pointer might be something you forced. The compiler needs to do the right thing but if you set the pointer to an unaligned address because you have information on the hardware you can get this undefined situation with nothing the compiler can do about it.
- matheusmoreira 5mo agoAny reason the hardware pointer can't be accessed via the packed structure? https://news.ycombinator.com/item?id=48205371 https://news.ycombinator.com/item?id=48205371
- account42 5mo agoWhich is totally fine and expected for any decent programmer. Casting pointers is clearly here be dragons territory.
- simonask 5mo agoMany, many programmers come to C (and C++) with a lower-level understanding that actually gets in the way here. They understand that all types "are" just bytes and that all pointers "are" just register-sized integer addresses, because that's how the hardware works and has worked for decades. It's perfectly reasonable to expect any load through `int*` to just load 4 bytes from memory, done and done. They get surprised that it is far from the whole story, and the result is UB. Meanwhile, the actual computers we have been using for decades have no problems actually just loading 4 bytes through any arbitrary pointer with zero overhead. But no.
- pjc50 5mo agoExcept ARM32. ARM64 doesn't guarantee it to be valid in all cases either.
- lelanthran 5mo ago> They understand that all types "are" just bytes and that all pointers "are" just register-sized integer addresses, because that's how the hardware works and has worked for decades. I'd clarify this with "They understand that all values are just bytes". > Meanwhile, the actual computers we have been using for decades have no problems actually just loading 4 bytes through any arbitrary pointer with zero overhead. It's partly the standards fault here - rather than saying "We don't know how vendors will implement this, so we shall leave it as implementation-defined", they say "We don't know how vendors will implement this, so we will leave it as undefined". A clear majority of the UB problems with C could be fixed if the standards committee slowly moved all UB into IB. It's not that there isn't any progress (Signed twos-complement is coming, after all), it's that there is (I believe) much pushback from compiler authors (who dominate the standards) who don't want to make UB into IB.
- 5mo ago
- thomashabets2 5mo agoAuthor here. > an unaligned pointer in itself is UB Yup. Per the "Actually, it was UB even before that" section in the post. > UB is not on the HW, it has nothing to do with crashes or faults Yeah. I tried to convey this too, but I'm also addressing the people who say "but it's demonstrably fine", by giving examples. Because it's not.
- stilley2 5mo agoDoes that mean that if I have a struct with #pragma pack(push, 1) I can't use pointers to any members that don't happen to be aligned?
- saagarjha 5mo agoThis is a non-standard extension, so your compiler may provide stronger guarantees.
- AlotOfReading 5mo agoIn practice, both GCC and Clang consider pointers to these unaligned members to be UB and will flag them in UBSAN.
- imtringued 5mo agoThe problem with C UBI is that originally it meant the compiler has the freedom to map your code to the hardware inspite of machine instructions differing slightly between one another. The same C program may express different behaviour depending on which architecture it is running on. This type of UB is fine and nobody really complains about hardware differences leading to bugs. However, over time aggressive readings of UB evolved C into an implicit "Design by Contract" language where the constraints have become invisible. This creates a similar problem to RAII, where the implicit destructor calls are invisible. When you dereference a pointer in C, the compiler adds an implicit non-nullable constraint to the function signature. When you pass in a possibly nullable pointer into the function, rather than seeing an error that there is no check or assertion, the compiler silently propagates the non-nullable constraint onto the pointer. When the compiler has proven the constraints to be invalid, it marks the function as unreachable. Calls to unreachable functions make the calling function unreachable as well.
- jcranmer 5mo ago> The problem with C UBI is that originally it meant the compiler has the freedom to map your code to the hardware inspite of machine instructions differing slightly between one another. The same C program may express different behaviour depending on which architecture it is running on. You're conflating undefined behavior with implementation-defined behavior. If it was only to do with what we think of as normal variance between processors, then it would be easy to make it implementation-defined behavior instead. The differentiating factor of undefined behavior is that there are no constraints on program behavior at that point, and it was introduced to handle cases where processor or compiler behavior cannot be meaningfully constrained. One key class is of course hardware traps: in the presence of compiler optimizations, it is effectively impossible to make any guarantees about program state at the time of a trap (Java tried, and most people agreed they failed); but even without optimizations, there are processors that cannot deliver a trap at a precise point of execution and thus will continue to execute instructions after a trapping instruction.
- 201984 5mo ago>an unaligned pointer in itself is UB, not only an access to it. Can someone point to where the standard states this?
- adrian17 5mo agoI think this is 6.3.2.3.7 in C99 about casting between pointer types: > If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined. However, unless I’m missing something, producing such a pointer from an integer is apparently not insta-UB? 6.3.2.2.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 And later on 6.5.3.2.4: > If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined. Which implies that the invalid pointer must have been obtained without being already undefined, right?