3 ms·
Strict aliasing shouldn't have been set as the default [1]. It was a huge mistake that instantly broke maybe all C programs everywhere. The standard also prov
by camgunz 8y ago
Strict aliasing shouldn't have been set as the default [1]. It was a huge mistake that instantly broke maybe all C programs everywhere. The standard also provided no guidance on how to work around the problem. `memcpy` is insufficient because it's a copy and that's a huge performance issue. Swapping through unions is UB. Casting about through `void * ` and `char * ` is gross, dangerous, and often runs afoul of alignment problems. It's a mess, and it has been for 20 years. Linus is right to be pissed.
[1]: https://blog.regehr.org/archives/1307 https://blog.regehr.org/archives/1307
- tedunangst 8y agomemcpy(&int, &float) probably turns into the same register move as a cast. Check with your compiler.
- camgunz 8y agoYeah that's ok, but I don't know about arrays, or the "inheritance" pattern: struct Person { char *name; unsigned char age; } struct Teacher { struct Person person; char *subject; } This kind of code is pretty common in C, and memcpy doesn't (can't) handle it. (also hey Ted I'm a huge fan of your work, thanks in particular for threading, holy shit)
- FartyMcFarter 8y ago> `memcpy` is insufficient because it's a copy and that's a huge performance issue. Compilers (at least clang and gcc) are smart enough to see through those memcpy's. For example: https://godbolt.org/g/5L5PT5 https://godbolt.org/g/5L5PT5
- AnIdiotOnTheNet 8y agoMaybe, but wouldn't you rather be explicit so you know the compiler will do what you want instead of crossing your fingers and hoping it does?
- camgunz 8y agoFor stack allocated scalars sure. What about stuff on the heap or arrays and structs?
- comex 8y agoGenerally speaking, if a memcpy's size argument is a constant expression equal to the size of a native integer type, it will be transformed to a native load from the source pointer followed by a store to the destination pointer. After that, the load or store operation (or both) may itself be optimized away if the pointer points to a local variable. Thus, for example, given something like: void write32(int *ptr, int val) { memcpy(ptr, &val, 4); } …the load is removed entirely, and the store is kept but becomes a native store: write32: mov DWORD PTR [rdi], esi ret Godbolt link: https://godbolt.org/g/HYv24F https://godbolt.org/g/HYv24F
- Gibbon1 8y ago> Compilers (at least clang and gcc) are smart enough to see through those memcpy's That's worse though because compiler optimization are wholly unreliable. Maybe today, maybe not tomorrow. And you have utterly no control over that.
- flamedoge 8y ago> Swapping through unions is UB Writing to union member and reading back as another member should be okay. But it does incur copy.
- camgunz 8y agoReading from a union field other than the one last written to is explicitly UB. I wish this worked too, but it doesn't (compiler-specific guarantees aside).
- comex 8y agoIt's actually well-defined in the C standard (although the format of the byte representation is implementation-defined), but undefined behavior in the C++ standard: https://stackoverflow.com/a/11996970 https://stackoverflow.com/a/11996970 Since Linux is C, not C++, it should be fine; the author of the patch is just mistaken. (Note that in practice, this will work in C++ too, since most compilers support both C and C++ and don't change their optimization behavior based on which mode the code was compiled in; but of course there's no guarantee this won't change in the future.)
- camgunz 8y agoNot all compilers handle it like you'd expect, and it looks like that's because it's actually unspecified in the standard [1]. But furthermore, this will really only work for scalars. Plus you have to go and declare a whole new type to cast things safely. It's extremely awkward. [1]: https://blog.regehr.org/archives/959 https://blog.regehr.org/archives/959
- Too 8y ago> Casting about through `void ` and `char * ` is gross, dangerous, and often runs afoul of alignment problems.* This just highlights why strict aliasing rules exists. Because the exact same alignment problems is what you get when you type pun. Looking at the raw data as a char* or memcpy'ing it just makes it more visible that what you are doing is wrong. The only valid reason to do type punning is to serialize data and then deserialize it back to exactly the same type. And since type puning with char* is defined as ok so there is no problem. I am having a really hard time seeing other valid reasons to do type punning and have never ever had to use it myself. Seriously, why are people upset over strict aliasing? What is the valid use case for type punning that people are missing?
- camgunz 8y agoMostly you run into problems either using the "inheritance" pattern, or with discriminated unions. These are pretty common in C. There really is no alternative to inheritance because if you don't type pun you end up needing to copy a lot (or you can pass around pointers to internal members but it's really easy to lose track of memory that way). And there's also not really an alternative for discriminated unions; the functionality is pretty unique. You can pull the polymorphism out of the type up to the top of your control flow, but that severely degrades your locality. In other words, at some point you have different batches of functions for each "type", but you don't realize that's what they are until you trace the call graph all the way up. Of course, sometimes that's preferable to avoid branching, but at least w/ discriminated unions you get the option.
- Too 8y ago1. Discriminated unions don't have any issues with strict aliasing, you are allowed to read a union member as long as it was the last union member being written to, and the tag-member helps you identify that. 2. If you are trying to retrofit polymorphism in C maybe you are using the wrong tool for the job. 3. If you really want polymorphism you can create baseclass-struct with a void* this pointer and a bunch of function pointers as instance-functions. To "override" the struct you just instantiate it with different function pointers. If you want to inherit an existing struct you add a void* super-member into your this-data and instantiate it explicitly. For any function you don't want to override you use just call the existing function function and pass in you super-data as this. This is also safe as each subclass implementation only casts the void* this into itself which is a known struct of the same type as was last written to. You do have to split the data and keep super-class data in another struct one pointer-hop away but that shouldn't be a big issue.