I'm aware that undefined behavior can potentially cause anything, which makes any program containing UB potentially meaningless. I was wondering if there is any way to identify the earliest point in a program that undefined behavior could cause problems. Here is an example to illustrate my question.
void causeUndefinedBehavior()
{
//any code that causes undefined behavior
//every time it is run
char* a = nullptr;
*a;
}
int main()
{
//code before call
//...
causeUndefinedBehavior();
//code after call
//...
}
From my understanding, the possible times undefined behavior could be evoked (not necessarily manifested) are:
causeUndefinedBehavior()
is compiled.main()
is compiled.causeUndefinedBehavior()
is executed.Or is the point where undefined behavior is evoked completely different for every case and every implementation?
In addition, if I commented out the line where causeUndefinedBehavior()
is called, would that eliminate the UB, or would it still be in the program since code containing UB was compiled?
As your code somewhat demonstrates, undefined behavior is almost always a condition of runtime state at the time the behavior is attempted. A slight modification of your code can make this painfully obvious:
void causeUndefinedBehavior()
{
//any code that causes undefined behavior
//every time it is run
char* a = nullptr;
*a;
}
int main()
{
srand(time(NULL));
//code before call
//...
if (rand() % 973 == 0)
causeUndefinedBehavior();
//code after call
//...
}
You could execute this a thousand times or more and never trip the UB execute-condition. that doesn't change the fact the function itself is clearly UB, but detecting it at compile time in context of the invoker is not trivial.
I think it depends on the type of undefined behavior. Things that would affect something like structure offsets could cause undefined behavior, which would show up any time code that touches that structure is executed.
In general, however, most undefined behavior happens at run time, meaning only if that code is executed will the undefined behavior occur.
For example, an attempt to modify a string literal has undefined behavior:
char* str = "StackOverflow";
memcpy(str+5, "Exchange", 8); // undefined behavior
This "undefined behavior" will not take place until the memcpy
executes. It will still compile into perfectly sane code.
Another example is omitting the return from a function with a non-void return type:
int foo() {
// no return statement -> undefined behavior.
}
Here, it is at the point at which foo
returns that the undefined behavior occurs. (In this case, on x86, whatever happened to be in the eax
register is the resultant return value of the function.)
Many of these scenarios can be identified by enabling the a higher level of compiler error reporting (eg. -Wall
on GCC.)
while it is "undefined behaviour", given a particular compiler, it will have a predictable behavior of some sort. But because it is undefined, on different compilers, it may result in that behavior occurring at any point of the complilation / runtime
"Undefined behavior" means that the language definition doesn't tell you what your program will do. That's a very simple statement: no information. You can speculate all you like about what your implementation may or may not do, but unless your implementation documents what it does, you're only guessing. Programming isn't about guessing; it's about knowing. If the behavior of your program is undefined, fix it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With