5 ms·
It doesn't mean that but maybe it actually should. The fact these perfectly good intuitions don't apply because of completely irrelevant reasons is a huge cause
by matheusmoreira 2y ago
It doesn't mean that but maybe it actually should. The fact these perfectly good intuitions don't apply because of completely irrelevant reasons is a huge cause of friction and bugs.
Your article contains the perfect example:
> UB on integer overflow is a compiler-only concept
> every target supported by the compiler will do the obvious thing and just produce an overflowing result
Pretty much every computer that matters works the way you expect it to: the value will overflow, some flag will be set, etc. The compiler couldn't care less though. The compiler decrees that it shall be undefined. You know it works but it doesn't because the compiler refuses to do what you expect it to do because it's "undefined".
Well then just define it for god's sake. I'm so tired of the uncertainty. Tired of playing these games with the compiler. Undefined? I don't even want to read that word ever again.
Let's define this right now.
-fwrapv
There. It is now defined to be what everyone expects and wants. Now the optimizer won't be getting "clever" with this and generating complete garbage code as result. The compiler's optimizer is no longer your enemy in this case. It won't be erroneously optimizing entire loops to some random constant anymore. One would think they'd emit a warning instead in these cases but no.
Strict aliasing is yet another massive pain that you need to deal with because of compilers and their "optimizations". People have actually told me to "launder" pointers through asm statements so the compiler can't make nonsense assumptions about that stuff.
This is C. We do things like write memory allocators. Of course we're going to alias stuff. I have a structure type, I have a byte buffer with the data, of course I want to overlay the type on the byte buffer and just access it directly. Why is it that the compiler just doesn't let me reinterpret memory however I want? Compiler has no business making my life hell because of this.
Turns out the C standard prescribes some "strict aliasing" nonsense because Fortran has it or some other reason nobody really cares about. If you're doing anything at all in C you're probably violating this.
-fno-strict-aliasing
There. You can now do what you want to do without the compiler getting all clever about it and ruining your day with nonsense code generation. Now it won't be reordering your code into nonsense just because it can "prove" that two pointers can't be equal even though you literally assigned one pointer to the other.
Over time I've built up this little collection of C compiler flags and they've become unconditional overrides in all my makefiles. No matter what users pass in via CFLAGS or whatever, these little things still get disabled. I have no idea what the performance impact is and honestly I don't care.
There's quite a lot of documented compiler flags, I couldn't evaulate every single one of them. If anyone here knows of any other useful flags that define the undefined, please reply to this comment. I'll add them to my makefiles without thinking twice.
- mistercow 2y agoIt’s been a long time since I worked with C, but my recollection was that a) strict aliasing allows for optimizations that are actually worthwhile, and b) it’s really easy to type pun in a defined way using unions anyway.
- abainbridge 2y ago> strict aliasing allows for optimizations that are actually worthwhile I don't think there are many sensible, real world examples. A nice explanation of the optimizations the strict-aliasing rule allows: https://stackoverflow.com/a/99010/66088 https://stackoverflow.com/a/99010/66088 The example given is: typedef struct Msg { unsigned int a; unsigned int b; } Msg; void SendWord(uint32_t); int main(void) { // Get a 32-bit buffer from the system uint32_t* buff = malloc(sizeof(Msg)); // Alias that buffer through message Msg* msg = (Msg*)(buff); // Send a bunch of messages for (int i = 0; i < 10; ++i) { msg->a = i; msg->b = i+1; SendWord(buff[0]); SendWord(buff[1]); } } The explanation is: with strict aliasing the compiler doesn't have to think about inserting instructions to reload the contents of buff every iteration of the loop. The problem I have is that when we re-write the example to use a union, the generated code is the same regardless of whether we pass -fno-strict-aliasing or not. So this isn't a working example of an optimization enabled by strict aliasing. It makes no difference whether I build it with clang or gcc, for x86-64 or arm7. I don't think I did it wrong. We still have a memory load instruction in the loop. See https://godbolt.org/z/9xzq87d1r https://godbolt.org/z/9xzq87d1r Knowing whether a C compiler will make an optimization or not is all but impossible. The simplest and most reliable solution in this case is to do the loop hoisting optimization manually: uint32_t buff0 = buff[0]; unit32_t buff1 = buff[1]; for (int i = 0; i < 10; ++i) { msg->a = i; msg->b = i+1; SendWord(buff0); SendWord(buff1); } Doing so removes the load instruction from the loop. See https://godbolt.org/z/ecGrvb3se https://godbolt.org/z/ecGrvb3se Note 1: The first thing that goes wrong for Stackoverflow example is that the compiler spots that malloc returns uninitialized data, so it can omit the reloading of buff in the loop anyway. In fact it removes the malloc too. Here's clang 18 doing that https://godbolt.org/z/97a8K73ss https://godbolt.org/z/97a8K73ss. I had to replace malloc with an undefined GetBuff() function, so the compiler couldn't assume the returned data was unintialized. Note 2: Once we're calling GetBuff() instead of malloc(), the compiler has to assume that SendWord(buff[0]) could change buff, and therefore it has to reload it in the loop even with strict-aliasing enabled.