5 ms·
Headline is misleading. What's going on here is that the GCC developers feel free to change the generated code for behaviour undefined in the C spec. This mea
by zzalpha 11y ago
Headline is misleading.
What's going on here is that the GCC developers feel free to change the generated code for behaviour undefined in the C spec. This means they might alter the behaviour of code since 40% of all packages in Debian (the "half of all packages" mentioned here) contain code whose behaviour is undefined.
But the poster seems to define any change in behaviour as "breaking" code, which is ridiculous. How much of that 40% is rare edge cases or similar, such that in reality the behaviour of the code doesn't change in practice?
The follow-up email raises this exact point, noting that in the specific example of pointer overflow:
In executions where overflow does not happen, the gcc produced binary will match the behavior of the abstract machine in the C spec.
Which means that the cited static analysis, while correctly identifying code containing UB, may not actually have an issue at runtime as the condition may never occur in practice.
The real problem is any software that truly relies on undefined behaviour for correct operation (which, I'll bet, is far less than the 40% cited here). That code is fundamentally non-portable to other compilers specifically because each compiler may produce semantically different output.
- deleted 11y ago[deleted]
- DanWaterworth 11y agoJust read the email.
- zzalpha 11y agoActually it's still a useful question, I think. A simple example is function argument evaluation order. If you have: foo(bar(), baz()) A compiler is free to call these functions in either order. That means if baz relies on state mutated by bar, the program may behave differently if the compiler chooses to reorder evaluation.
- tedunangst 11y agoA perhaps more interesting, quite realistic example: lock_two_widgets(widget *a, widget *b) { if (a < b) { lock(&a->lock); lock(&b->lock); } else { lock(&b->lock); lock(&a->lock); } }
- just_curioussss 11y agoIf lock calls include a memory barrier, which they should, then they cannot be reordered. Edit: Your code has undefined behavior, unless the two pointers point to the same object, which is unlikely in a realistic example.
- ufo 11y agoYes, the pointer comparison is UB but if the compiler didn't go out of its way to screw you over UB code then it would be a reasonable way to prevent deadlock.
- just_curioussss 11y agoThere is a defined method for comparing such pointers. Take unsigned char pointers to them and inspect their bytes. Then write your code, using that information, that does the comparison, assuming you know your architecture. It is defined behavior to read the bytes of any object using unsigned char.
- kbwt 11y agoAlternatively, if the implementation supports uintptr_t, you can convert to that and then compare the respective integral values.
- DanWaterworth 11y agoRight, because you can get to the end of a non-void function without producing a value. The pointer comparison isn't necessarily UB.
- coherentpony 11y agoNasal demons.
- barrkel 11y agoThe real problem is any software that truly relies on undefined behaviour for correct operation (which, I'll bet, is far less than the 40% cited here). I'd bet it's well into double digits relying on undefined behaviour, and over 50% relying on unspecified behaviour. It's hard to write interesting code in C that doesn't, where interesting means code that couldn't just as profitably be written in a different language with stronger memory safety. Programmers have a mental model of many things - in particular, 2's complement signed numbers, and the idea that pointers are just numbers indexing memory - that are not guaranteed by the C spec. Much of the reason for programming in a lower level language is to take advantage of lower level machine characteristics, like tagged pointers, unsafe unions, structs with blobs of data appended to avoid an indirection, custom memory allocators, etc., but the C abstract machine doesn't necessarily give all the guarantees required, without a lot of care and attention to details.
- makomk 11y agoEven C compiler developers can't always get it right. There have been a few bugs where, for example, one compiler pass optimised a struct initialisation into a single write that wrote into the slack space after the end of the struct, and a subsequent pass detected this undefined behaviour and removed the initialisation altogether. (These optimisation tricks inherently make compiler development more risky, because they mean that composing safe compiler passes is likely to create something unsafe.)