return printf("%s: we see %s", argv[0], argv[1]);
IOW, the conditional has been elided. But you're right in that the wording of the complaint doesn't match the example. The author presumably had in mind some of the more infamous NULL pointer-related optimizations, without spending the time to put together a properly analogous example. 1 if (argc <= 2)
2 puts("A");
3 puts("B");
4 if (argc <= 2)
5 unreachable();
6 else
7 return puts("C");
8 return puts("D");
in which not just lines 4-6,8 go away (as you said) but also lines 1-2.It makes sense to me but I can see why the author would characterize this situation as "license to use an unreachable annotation on one code path to justify removing an entirely different code path that is not marked unreachable". In a different world one might expect A to be printed "before the UB happens".
This is a behaviour I would absolutely expect from the construct, I would even qualify it as "the point".
What am I missing in this example?
If someone really just wants a delay, it's easy to either (for programs running on normal OSs) call a sleep function, or (on tiny embedded systems) add an empty inline assembler statement that the compiler can't see through.
Neither is measuring delays of cached versus non-cached instructions. Yet it turns out to be very observable.
Practically, if you need to execute a specific sequence of machine instructions in order to prevent side-channel attacks, then you have to rely on assembler, compiler intrinsics and/or OS support. But that was true way before Spectre.
It would be of course nice if a warning was produced for that specific case: This whole loop was removed - is it really what you wanted, or is it a broken delay loop?
I've been many loops that turn into no-ops because all the functionality has been refactored out but this fact is hidden in function calls.
Sure, this should ideally be surfaced as a lint error, not a compiler optimization, but you cannot say that intentional delays are the "only" reason.
Also since processing time is variable, using that as a method should be extremely heavily discouraged/warned/require-opt-in
The `realloc()` change though...
Is it to aid building for multiple targets? For debug builds?
Described e.g. here https://web.archive.org/web/20160508051118/http://blog.regeh...
To aid with optimisation, it basically lets you ask the compiler to remove branches, and provide constraints to the same.
An implementation might trap in debug code, but given no context would be provided you'd likely avoid this and would instead use your own wrapper macro to output a message of some sort in that case.
The branch containing the unreachable() obviously gets removed but the compiler then propagates the constraint (the condition for that illegal branch), and can prune any other path where `argc <= 2` upstream and downstream, as they are dead code per the constraint.
If a branch is truly not supposed to ever happen, why have a branch at all? Just remove that code from the source entirely- that helps the optimizer even more, because the most optimal code is of course no code at all.
Because sometimes you don't have a choice e.g. say you have a switch/case, if you don't do anything and none of the cases match, then it's equivalent to having an empty `default`. But you may want a `default: unreachable()` instead, to tell the compiler that it needs no fallback.
> If a branch is truly not supposed to ever happen, why have a branch at all? Just remove that code from the source entirely- that helps the optimizer even more, because the most optimal code is of course no code at all.
Except the compiler may compile code with the assumption that it needs to handle edge cases you "know" are not valid. By providing these branches-which-are-not, you're giving the compiler more data to work with. That extra data might turn out to be useless, but it might not.
if(condition) {
error_stuff()
abort();
}
normal_stuff();
If the compiler doesn't know that abort exits the program, they have to compile the normal_stuff path under the assumption that the error path might have run before it. This might result in suboptimal code.Currently, many compilers support annotations such as __attribute__(noreturn) and __builtin_unreachable() to manually indicate that a code path is unreachable. C23 is now standardizing these features (with a slight tweak to the syntax).
if (argc <= 2)
do_something();
else
return printf("%s: we see %s", argv[0], argv[1]);
So the `return printf` is executed when `argc` is greater than 2. If we remove just the body of the first branch: if (argc <= 2)
;
else
return printf("%s: we see %s", argv[0], argv[1]);
the same thing holds. And additionally when `argc <= 2`, control will move past the `if`.Under this view, if the `unreachable` won't cause the entire removal of the `if`, the compiler will produce the equivalent of:
if (argc > 2)
return printf("%s: we see %s", argv[0], argv[1]);
return puts("this should never be reached")
Again, I don't say this is the correct interpretation, but it is one possibility, that would have to be ruled out by other parts of the standard.
I don't agree with that description at all. Here's the code:
The only code path that's "entirely different" is lines 1,4,5 and in that case of course you remove a return that's after a return.And the other valid code path is 1,2,5, which has `puts` after `unreachable`.
To need `puts` you have to imagine a code path that gets past the "if" without taking either branch?
Maybe the author means something by "code path" that's very different from how I interpret it?
I would be pretty surprised if the above code means something different from: