8 ms·
How I think about C99 strict aliasing rules
- sylware 4y agoBeyond this, one thing I have been putting thoughts into is removing integer promotion and the implicit casts (except for void*) and use explicit static/dynamic casts (not with the horrible c++ syntax). I also thought about static and dynamic consts, but if I understood well, constant folding would be one of the easiest optimizations to do, then that would not be worth it. I still wonder what typedef/typeof/_Generic are doing into C. They may be "cheap" to implement, but could lead to excessive generalizations with a strong smell of Rube Goldberg Machine (happening in the linux kernel).
- spacechild1 4y agotypedef has been there since at least C89... the only alternative is #define - how is that better?
- mort96 4y agoWhy was the "How" removed from the title? "How I think about C99 strict aliasing rules" is surely much more appropriate?
- dang 4y agoYes, HN's title truncator is overzealous sometimes. We've recapitated the title above.
- Tomte 4y agoI think I've asked this before, but can you please make the title change not silent, but something like "we've improved the title" with an accept or restore click? The current flow is really unfortunate.
- jimbob45 4y agoIt’s ironic that this website is built on the one language known more than anything else for making fast, sweeping changes on the fly and yet has changed arguably the least of any message board of the last 20 years.
- deleted 4y ago[deleted]
- dang 4y agoThere have been many changes—just not many visible changes. Users hate change.
- JonChesterfield 4y agoFor code that has to do clever things with types, I've now given up on the C type system and just copy bytes through arrays of uint64_t instead. I miss structs but overall the loss is workable. Atomics through the non-standardised intrinsics that work on normal types instead of the _Atomic qualified ones.
- andi999 4y agoCan you give an example?
- formerly_proven 4y ago> I've now given up on the C type system and just copy bytes through arrays of uint64_t instead. I miss structs but overall the loss is workable. Can you elaborate? This sounds like you're using uint64_t as an universal-aliasing-mcguffin, which doesn't work, so I'm guessing that's not what you mean?
- JonChesterfield 4y agoI've been putting off replying until at a desk but that's not worked out. What used to be struct { atomic_uint64_t count; uint32_t X; uint32_t Y; void * Z; }; is now a uint64_t[4] with bytewise copies to get the void* back and intrinsics for the atomic operations. Functions all take a uint64_t* so the array length is discarded. Means some functions can take a pointer to the refcount field and others to the first data field for when there is no refcount involved. uint64_t has no magic aliasing properties, but pointers and doubles can be memcpy'd into it, and things like uint64_t data[2048] in .rodata can have whatever set of objects one likes embedded in it. Slightly sketchy is casting addresses directly to uint64_t instead of memcpy but clang does not yet mangle code doing that. Will change static data to asm when it does. This makes writing correct C moderately harder but has the redeeming feature that type based alias analysis is no longer a hazard.
- xscott 4y agoIt really seems like the compiler writers have gone insane in the last decade or so. Any reasonable person would look at the standard and conclude the intent is to describe what is required to write super-portable programs. Yes, you could have 1s-complement or sign-magnitude integers, so you can't rely on signed overflow for portable code. Yes, you could have separate address spaces for floating point and integers, so you can't rely on cross-type pointer casts or address arithmetic for portable code. But the compiler writers are using "undefined behavior" as justification to break code on machines that don't have weird address spaces and don't use 1-s complement integers. So the result... anyone who wants C/C++ to just be a high level assembler is going to need to do all their arithmetic with unsigned and all their pointers as char. Say goodbye to readability or type safety.
- bonzini 4y agoThere are lots of real-world cases where type-based alias analysis produces not just better, but even just "decent" code. Example: void add_all(float **x, float **y, int m, int n) { for (int i = 0; i < m; i++) for (int j = 0; i < n; i++) x[i][j] += y[i][j]; } without TBAA cannot be optimized to void add_all(float **x, float **y, int m, int n) { for (int i = 0; i < m; i++) { float *xi = x[i], *yi = y[i]; for (int j = 0; i < n; i++) xi[j] += yi[j]; } Or for overflow, "x[i] = 0;" cannot be compiled to "movl [x+RAX*4], 0" if i is an "unsigned int".
- xscott 4y ago> Or for overflow, "x[i] = 0;" cannot be compiled to "movl [x+RAX*4], 0" if i is an "unsigned int". This is another pet peeve of mine. Why does almost every justification about UB and signed overflow involve using a 32 bit integer on a 64 bit platform? Change those int variables to ssize_t (or size_t if you must), and it's good. Instead of creating and jumping through insane hoops to improve bad code, they should warn you that there are performance costs for using the wrong sized integer as an array subscript. Edit: Oops, I missed the point on the first one. So I removed a bad example.
- lloda 4y agoIt's a good point that aliasing issues may show up as different warnings, like maybe-uninitialized in the example. Those warnings are often not obvious at all.
- AshamedCaptain 4y agoI have always imagine LTO to be such a huge pandora's box regarding this type of issues (e.g. the removal of the impicit memory barrier between function calls) that the fact that it isn't (e.g. many distributions enable lto by default) means that either these problems are way overblown or compilers are still keeping barriers between function calls internally.
- planede 4y agoCompilers definitely don't keep barriers between function calls internally. AFAIK LTO enables inlining as well, but inlining is not the only inter-procedural optimization that can happen.
- gpderetta 4y agobut LTO enables interproc optimizations between separate compilation units.
- MauranKilom 4y agoYes. OP should mention that they talk about "function calls (to functions defined in other translation units)", even though it's semi-implicit in mentioning LTO.
- Annatar 4y agoThis was a good article, and more such articles with examples are needed because understanding how pointer aliasing works can yield significant performance gains, since the compiler can be directed to make optimizations it would otherwise not make.
- synergy20 4y agolinux kernel defaults to 'no-strict-aliasing'. I wonder if this statement is correct: "always do -fno-strict-aliasing by default, only do -fstrict-aliasing where the performance bottleneck is"? It seems like a compromise between safety(less UB) and performance to me.