The undefined behavior strikes when the program will cause undefined behavior no matter what happens next. However, you gave the following example.
int num = ReadNumberFromConsole();
if (num == 3) {
PrintToConsole(num);
*((char*)NULL) = 0; //undefined behavior
}
Unless the compiler knows definition of PrintToConsole
, it cannot remove if (num == 3)
conditional. Let's assume that you have LongAndCamelCaseStdio.h
system header with the following declaration of PrintToConsole
.
void PrintToConsole(int);
Nothing too helpful, all right. Now, let's see how evil (or perhaps not so evil, undefined behavior could have been worse) the vendor is, by checking actual definition of this function.
int printf(const char *, ...);
void exit(int);
void PrintToConsole(int num) {
printf("%d\n", num);
exit(0);
}
The compiler actually has to assume that any arbitrary function the compiler doesn't know what does it do may exit or throw an exception (in case of C++). You can notice that *((char*)NULL) = 0;
won't be executed, as the execution won't continue after PrintToConsole
call.
The undefined behavior strikes when PrintToConsole
actually returns. The compiler expects this not to happen (as this would cause the program to execute undefined behavior no matter what), therefore anything can happen.
However, let's consider something else. Let's say we are doing null check, and use the variable after null check.
int putchar(int);
const char *warning;
void lol_null_check(const char *pointer) {
if (!pointer) {
warning = "pointer is null";
}
putchar(*pointer);
}
In this case, it's easy to notice that lol_null_check
requires a non-NULL pointer. Assigning to the global non-volatile warning
variable is not something that could exit the program or throw any exception. The pointer
is also non-volatile, so it cannot magically change its value in middle of function (if it does, it's undefined behavior). Calling lol_null_check(NULL)
will cause undefined behavior which may cause the variable to not be assigned (because at this point, the fact that the program executes the undefined behavior is known).
However, the undefined behavior means the program can do anything. Therefore, nothing stops the undefined behavior from going back in the time, and crashing your program before first line of int main()
executes. It's undefined behavior, it doesn't have to make sense. It may as well crash after typing 3, but the undefined behavior will go back in time, and crash before you even type 3. And who knows, perhaps undefined behavior will overwrite your system RAM, and cause your system to crash 2 weeks later, while your undefined program is not running.
const int i = 0; if (i) 5/i;
. – AftershockPrintToConsole
doesn't callstd::exit
so it has to make the call. – AftershockPrintToConsole
could be replaced with a side-effecting write to a global. I'm interested in all reasonable variations of this problem. Don't restrict yourself to this example if I have chosen it badly. – Bilbao