4 ms·
C++ without strict aliasing is not C++ anymore. Furthermore, that was just an example. There are other such concepts, such as the illegality of accessing memory
by Chabsff 4y ago
C++ without strict aliasing is not C++ anymore. Furthermore, that was just an example. There are other such concepts, such as the illegality of accessing memory as anything else than raw bytes or the type of the object that resides there (which also applies to union fields). Another one would be the fact that signed integer overflow is UB.
C++ is so tightly bound to memory layouts that once you start getting a solid mental model of what's going on, it's really easy to come up with legitimately neat and creative ideas that work very well and fast on a test bench environment without any complaint from modern compilers, but are fundamentally broken. I feel we do not do a good job at equipping up-and-coming programmers so that they can benefit from that mental model without writing code that only works out of sheer luck.
In the extreme, you can write code that does stuff like dynamically retype an object by swapping out its vtable when vtables are not even thing as far as the language is concerned. It's astounding what it sometimes feels like you are capable of doing in C++.
- synergy20 4y agoI understand compiler likes strict aliasing for performance optimizations, use it carefully but _only_ at bottleneck-ed areas seems like a good trade-off between safety and performance, why without-it is no c++ anymore? why do I need do pointer aliasing randomly?
- Chabsff 4y agoStrict aliasing is what prevents compilers from having to sprinkle Load-Hit-Stores all over the place. This is not a marginal performance difference, and doubly-so for CPUs with deep execution pipelines. It also has massive code-reordering implications. In any case, Strict Aliasing is not a flippant rule that compilers like to apply. It's a formal component of the ISO standard that C++ is. You can chose to make a deal with a compiler (via a flag) to compile otherwise non-compliant code in a consistent way, but that does not change the fact that the code is non-compliant.
- kazinator 4y agoThere are ways of coding coding in C which makes those optimizations unnecessary. The basic idea is: don't load your block of code with lots of pointer dereferences. Load the values you need into local variables. Don't proliferate common expressions which dereference the same pointer to get at the same value. Consolidate the assignments through pointers. Don't do this in three places: (*ptr)++. Have that value in a local variable var, and do var++ in three places, then assign it *ptr = var. Even without no-strict aliasing, a compiler can still assume that local variables whose addresses are not taken are not the targets of any pointers. In C (99 or later), you have restrict also. restrict is independent of strict aliasing, because it's not based on type. Furthermore, speaking of restrict, aliasing between like typed objects matters for optimization. It's good and well to optimize based on the idea that a double * cannot be aiming at an object whose declared type is long. But it's insufficient, because code that is manipulating double * pointers is likely working with objects of type double which could be the targets of those pointers. So without some combination of tight coding and possibly using restrict, you will end up with those load-hit-stores in all sorts of code. This code for removing from a linked list: node->prev->next = node->next; node->next->prev = node->prev; still isn't as good as this: node *next = node->next; node *prev = node->prev; next->prev = prev; prev->next = next; The problem is that the common expressions can't be eliminated based on aliasing, and the aliasing is between like types, so strict aliasing doesn't help. With gcc (x86) I get: movl (%eax), %edx movl 4(%eax), %ecx movl %ecx, 4(%edx) movl 4(%eax), %eax movl %eax, (%eax) versus: movl (%eax), %edx movl 4(%eax), %eax movl %eax, 4(%edx) movl %edx, (%eax) We shaved off an instruction through tighter coding, and IMHO (in this case) improved the readability also. That was gcc 7 on Ubuntu. The result is exactly the same like what I saw under gcc 2.7.x a quarter century ago. How about gcc 11, x86-64? I should probably be using godbolt, but anyway, also five instructions down to four: movq (%rdi), %rax movq 8(%rdi), %rdx movq %rdx, 8(%rax) movq 8(%rdi), %rax movq %rax, (%rax) vs: movq 8(%rdi), %rax movq (%rdi), %rdx movq %rax, 8(%rdx) movq %rdx, (%rax) (I think in this particular case the compiler could do a better job because even if the assignment "node->prev->next = node->next" clobbers the value of "node->next" due to the nodes being aliases, the assignment can only clobber it with the value that node->next already has! The compiler doesn't analyze it that far though.) C was designed from the start as a language which the programmer does the optimizing, and regardless of the advancements in compilers, that has not been entirely eliminated. How you write C still makes a difference, even at the microscopic level of individual statements and expressions, not just the level of overall program organization and use of algorithms. If you write tight code, you can turn off strict aliasing optimizations globally and it won't matter. But you don't have to do that globally. You may be able to confine your type punning hack in its own source file, and just turn it off for that file. (Or may be even on a finer granularity if you have such compiler support.)
- stoeckley 4y agoI remember once telling a friend of mine, after I'd spent several years writing Clojure full-time, that upon getting back into writing C++, the feeling was like "sculpting with electricity." There is a strange sense of power you feel from harnessing the language in a creative way, unlike any other language I've used.
- kazinator 4y agoDid he mean with rubber gloves and insulated mat, or without?
- deleted 4y ago[deleted]
- kazinator 4y ago> C++ without strict aliasing is not C++ anymore That is completely false. Firstly, a C++ implementation that doesn't optimize based on no aliasing assumptions can be entirely conforming: it can handle all correct, portable programs in the required way. Secondly, the handling for programs which do type punning doesn't make it not C++ anymore; just a dialect. Programs in that dialect are C++, just not ISO C++. Every implementation of a standardized language is a dialect of that language; the standard just explains what is the common dialect (theoretically) supported by all of them. In fact it's common for C and C++ compilers to recognize their own dialect by default.
- arcticbull 4y ago> That is completely false. I suppose but only because C++ is everything and nothing. C++ compilers are borderline self aware because the spec is 'write literally whatever you want'. Skynet was obviously a C++ compiler ;)
- kazinator 4y agoThe meaning of punning memory is obvious if you understand the implementation-specific representations being used. E.g. if you understand what a 64 bit double looks like and what a 64 bit integer looks like, then it makes sense when you look at the memory of one through the type of the other. The C or C++ program to it has only two rationally defensible meanings: "undefined behavior" (ISO C or C++ interpretation, allowing certain optimizations, or run-time diagnosis of a type mismatch or whatever) or else "do the obvious thing and access the bits like the code says". What the code is asking for can certainly be be framed in terms of concepts in the standard. Just what the code is obviously requesting is not granted by any requirements in the standard. The idea that by asking for it, it's not a C++ program any more is silly. There is no standardized programming language that doesn't have vendor specific extensions comprising a local dialect.