undefined-behavior
What are "sequence points"?
What is the relation between undefined behaviour and sequence points?
I often use funny and convoluted expressions like a[++i] = i;
, to make myself feel better. Why should I stop using them?
If you've read this, be sure to visit the follow-up question Undefined behavior and sequence points reloaded.
(Note: This is meant to be an entry to Stack Overflow's C++ FAQ. If you want to critique the idea of providing an FAQ in this form, then the posting on meta that started all this would be the place to do that. Answers to that question are monitored in the C++ chatroom, where the FAQ idea started out in the first place, so your answer is very likely to get read by those who came up with the idea.) Why is unsigned integer overflow defined behavior but signed integer overflow isn't?Unsigned integer overflow is well defined by both the C and C++ standards. For example, the C99 standard (§6.2.5/9
) states
A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.
However, both standards state that signed integer overflow is undefined behavior. Again, from the C99 standard (§3.4.3/1
)
An example of undefined behavior is the behavior on integer overflow
Is there an historical or (even better!) a technical reason for this discrepancy?
Why does integer overflow on x86 with GCC cause an infinite loop?The following code goes into an infinite loop on GCC:
#include <iostream>
using namespace std;
int main(){
int i = 0x10000000;
int c = 0;
do{
c++;
i += i;
cout << i << endl;
}while (i > 0);
cout << c << endl;
return 0;
}
So here's the deal: Signed integer overflow is technically undefined behavior. But GCC on x86 implements integer arithmetic using x86 integer instructions - which wrap on overflow.
Therefore, I would have expected it to wrap on overflow - despite the fact that it is undefined behavior. But that's clearly not the case. So what did I miss?
I compiled this using:
~/Desktop$ g++ main.cpp -O2
GCC Output:
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
0
0
0
... (infinite loop)
With optimizations disabled, there is no infinite loop and the output is correct. Visual Studio also correctly compiles this and gives the following result:
Correct Output:
~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3
Here are some other variations:
i *= 2; // Also fails and goes into infinite loop.
i <<= 1; // This seems okay. It does not enter infinite loop.
Here's all the relevant version information:
~/Desktop$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ..
...
Thread model: posix
gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)
~/Desktop$
So the question is: Is this a bug in GCC? Or did I misunderstand something about how GCC handles integer arithmetic?
*I'm tagging this C as well, because I assume this bug will reproduce in C. (I haven't verified it yet.)
EDIT:
Here's the assembly of the loop: (if I recognized it properly)
.L5:
addl %ebp, %ebp
movl $_ZSt4cout, %edi
movl %ebp, %esi
.cfi_offset 3, -40
call _ZNSolsEi
movq %rax, %rbx
movq (%rax), %rax
movq -24(%rax), %rax
movq 240(%rbx,%rax), %r13
testq %r13, %r13
je .L10
cmpb $0, 56(%r13)
je .L3
movzbl 67(%r13), %eax
.L4:
movsbl %al, %esi
movq %rbx, %rdi
addl $1, %r12d
call _ZNSo3putEc
movq %rax, %rdi
call _ZNSo5flushEv
cmpl $3, %r12d
jne .L5
Is using malloc for int undefined behavior until C++20
I was told that the following code has undefined behavior until C++20:
int *p = (int*)malloc(sizeof(int));
*p = 10;
Is that true?
The argument was that the lifetime of the int
object is not started before assigning the value to it (P0593R6). To fix the problem, placement new
should be used:
int *p = (int*)malloc(sizeof(int));
new (p) int;
*p = 10;
Do we really have to call a default constructor that is trivial to start the lifetime of the object?
At the same time, the code does not have undefined behavior in pure C. But, what if I allocate an int
in C code and use it in C++ code?
// C source code:
int *alloc_int(void)
{
int *p = (int*)malloc(sizeof(int));
*p = 10;
return p;
}
// C++ source code:
extern "C" int *alloc_int(void);
auto p = alloc_int();
*p = 20;
Is it still undefined behavior?
Can branches with undefined behavior be assumed unreachable and optimized as dead code?Consider the following statement:
*((char*)NULL) = 0; //undefined behavior
It clearly invokes undefined behavior. Does the existence of such a statement in a given program mean that the whole program is undefined or that behavior only becomes undefined once control flow hits this statement?
Would the following program be well-defined in case the user never enters the number 3
?
while (true) {
int num = ReadNumberFromConsole();
if (num == 3)
*((char*)NULL) = 0; //undefined behavior
}
Or is it entirely undefined behavior no matter what the user enters?
Also, can the compiler assume that undefined behavior will never be executed at runtime? That would allow for reasoning backwards in time:
int num = ReadNumberFromConsole();
if (num == 3) {
PrintToConsole(num);
*((char*)NULL) = 0; //undefined behavior
}
Here, the compiler could reason that in case num == 3
we will always invoke undefined behavior. Therefore, this case must be impossible and the number does not need to be printed. The entire if
statement could be optimized out. Is this kind of backwards reasoning allowed according to the standard?
Consider the following code:
#include <iostream>
struct foo
{
// (a):
void bar() { std::cout << "gman was here" << std::endl; }
// (b):
void baz() { x = 5; }
int x;
};
int main()
{
foo* f = 0;
f->bar(); // (a)
f->baz(); // (b)
}
We expect (b)
to crash, because there is no corresponding member x
for the null pointer. In practice, (a)
doesn't crash because the this
pointer is never used.
Because (b)
dereferences the this
pointer ((*this).x = 5;
), and this
is null, the program enters undefined behavior, as dereferencing null is always said to be undefined behavior.
Does (a)
result in undefined behavior? What about if both functions (and x
) are static?
Compiling this:
#include <iostream>
int main()
{
for (int i = 0; i < 4; ++i)
std::cout << i*1000000000 << std::endl;
}
and gcc
produces the following warning:
warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
std::cout << i*1000000000 << std::endl;
^
I understand there is a signed integer overflow.
What I cannot get is why i
value is broken by that overflow operation?
I've read the answers to Why does integer overflow on x86 with GCC cause an infinite loop?, but I'm still not clear on why this happens - I get that "undefined" means "anything can happen", but what's the underlying cause of this specific behavior?
Online: http://ideone.com/dMrRKR
Compiler: gcc (4.8)
Is it undefined behavior to print null pointers with the %p
conversion specifier?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
The question applies to the C standard, and not to C implementations.
Accessing inactive union member and undefined behavior?I was under the impression that accessing a union
member other than the last one set is UB, but I can't seem to find a solid reference (other than answers claiming it's UB but without any support from the standard).
So, is it undefined behavior?
Why does this for loop exit on some platforms and not on others?I have recently started to learn C and I am taking a class with C as the subject. I'm currently playing around with loops and I'm running into some odd behaviour which I don't know how to explain.
#include <stdio.h>
int main()
{
int array[10],i;
for (i = 0; i <=10 ; i++)
{
array[i]=0; /*code should never terminate*/
printf("test \n");
}
printf("%d \n", sizeof(array)/sizeof(int));
return 0;
}
On my laptop running Ubuntu 14.04, this code does not break. It runs to completion. On my school's computer running CentOS 6.6, it also runs fine. On Windows 8.1, the loop never terminates.
What's even more strange is that when I edit the condition of the for
loop to: i <= 11
, the code only terminates on my laptop running Ubuntu. It never terminates in CentOS and Windows.
Can anyone explain what's happening in the memory and why the different OSes running the same code give different outcomes?
EDIT: I know the for loop goes out of bounds. I'm doing it intentionally. I just can't figure out how the behaviour can be different across different OSes and computers.
Why does printf("%f",0); give undefined behavior?The statement
printf("%f\n",0.0f);
prints 0.
However, the statement
printf("%f\n",0);
prints random values.
I realize I'm exhibiting some kind of undefined behaviour, but I can't figure out why specifically.
A floating point value in which all the bits are 0 is still a valid float
with value of 0.
float
and int
are the same size on my machine (if that is even relevant).
Why does using an integer literal instead of a floating point literal in printf
cause this behavior?
P.S. the same behaviour can be seen if I use
int i = 0;
printf("%f\n", i);
Optimizing away a "while(1);" in C++0x
Updated, see below!
I have heard and read that C++0x allows an compiler to print "Hello" for the following snippet
#include <iostream>
int main() {
while(1)
;
std::cout << "Hello" << std::endl;
}
It apparently has something to do with threads and optimization capabilities. It looks to me that this can surprise many people though.
Does someone have a good explanation of why this was necessary to allow? For reference, the most recent C++0x draft says at 6.5/5
A loop that, outside of the for-init-statement in the case of a for statement,
- makes no calls to library I/O functions, and
- does not access or modify volatile objects, and
- performs no synchronization operations (1.10) or atomic operations (Clause 29)
may be assumed by the implementation to terminate. [ Note: This is intended to allow compiler transfor- mations, such as removal of empty loops, even when termination cannot be proven. — end note ]
Edit:
This insightful article says about that Standards text
Unfortunately, the words "undefined behavior" are not used. However, anytime the standard says "the compiler may assume P," it is implied that a program which has the property not-P has undefined semantics.
Is that correct, and is the compiler allowed to print "Bye" for the above program?
There is an even more insightful thread here, which is about an analogous change to C, started off by the Guy done the above linked article. Among other useful facts, they present a solution that seems to also apply to C++0x (Update: This won't work anymore with n3225 - see below!)
endless:
goto endless;
A compiler is not allowed to optimize that away, it seems, because it's not a loop, but a jump. Another guy summarizes the proposed change in C++0x and C201X
By writing a loop, the programmer is asserting either that the loop does something with visible behavior (performs I/O, accesses volatile objects, or performs synchronization or atomic operations), or that it eventually terminates. If I violate that assumption by writing an infinite loop with no side effects, I am lying to the compiler, and my program's behavior is undefined. (If I'm lucky, the compiler might warn me about it.) The language doesn't provide (no longer provides?) a way to express an infinite loop without visible behavior.
Update on 3.1.2011 with n3225: Committee moved the text to 1.10/24 and say
The implementation may assume that any thread will eventually do one of the following:
- terminate,
- make a call to a library I/O function,
- access or modify a volatile object, or
- perform a synchronization operation or an atomic operation.
The goto
trick will not work anymore!
I know the uninitialized local variable is undefined behaviour(UB), and also the value may have trap representations which may affect further operation, but sometimes I want to use the random number only for visual representation and will not further use them in other part of program, for example, set something with random color in a visual effect, for example:
void updateEffect(){
for(int i=0;i<1000;i++){
int r;
int g;
int b;
star[i].setColor(r%255,g%255,b%255);
bool isVisible;
star[i].setVisible(isVisible);
}
}
is it that faster than
void updateEffect(){
for(int i=0;i<1000;i++){
star[i].setColor(rand()%255,rand()%255,rand()%255);
star[i].setVisible(rand()%2==0?true:false);
}
}
and also faster than other random number generator?
How did I get a value larger than 8 bits in size from an 8-bit integer?I tracked down an extremely nasty bug hiding behind this little gem. I am aware that per the C++ spec, signed overflows are undefined behavior, but only when the overflow occurs when the value is extended to bit-width sizeof(int)
. As I understand it, incrementing a char
shouldn't ever be undefined behavior as long as sizeof(char) < sizeof(int)
. But that doesn't explain how c
is getting an impossible value. As an 8-bit integer, how can c
hold values greater than its bit-width?
// Compiled with gcc-4.7.2
#include <cstdio>
#include <stdint.h>
#include <climits>
int main()
{
int8_t c = 0;
printf("SCHAR_MIN: %i\n", SCHAR_MIN);
printf("SCHAR_MAX: %i\n", SCHAR_MAX);
for (int32_t i = 0; i <= 300; i++)
printf("c: %i\n", c--);
printf("c: %i\n", c);
return 0;
}
SCHAR_MIN: -128
SCHAR_MAX: 127
c: 0
c: -1
c: -2
c: -3
...
c: -127
c: -128 // <= The next value should still be an 8-bit value.
c: -129 // <= What? That's more than 8 bits!
c: -130 // <= Uh...
c: -131
...
c: -297
c: -298 // <= Getting ridiculous now.
c: -299
c: -300
c: -45 // <= ..........
This is an example to illustrate my question which involves some much more complicated code that I can't post here.
#include <stdio.h>
int main()
{
int a = 0;
for (int i = 0; i < 3; i++)
{
printf("Hello\n");
a = a + 1000000000;
}
}
This program contains undefined behavior on my platform because a
will overflow on the 3rd loop.
Does that make the whole program have undefined behavior, or only after the overflow actually happens? Could the compiler potentially work out that a
will overflow so it can declare the whole loop undefined and not bother to run the printfs even though they all happen before the overflow?
(Tagged C and C++ even though are different because I'd be interested in answers for both languages if they are different.)
Is passing a C++ object into its own constructor legal?I am surprised to accidentally discover that the following works:
#include <iostream>
int main(int argc, char** argv)
{
struct Foo {
Foo(Foo& bar) {
std::cout << &bar << std::endl;
}
};
Foo foo(foo); // I can't believe this works...
std::cout << &foo << std::endl; // but it does...
}
I am passing the address of the constructed object into its own constructor. This looks like a circular definition at the source level. Do the standards really allow you to pass an object into a function before the object is even constructed or is this undefined behavior?
I suppose it's not that odd given that all class member functions already have a pointer to the data for their class instance as an implicit parameter. And the layout of the data members is fixed at compile time.
Note, I'm NOT asking if this is useful or a good idea; I'm just tinkering around to learn more about classes.
Why does the enhanced GCC 6 optimizer break practical C++ code?GCC 6 has a new optimizer feature: It assumes that this
is always not null and optimizes based on that.
Value range propagation now assumes that the this pointer of C++ member functions is non-null. This eliminates common null pointer checks but also breaks some non-conforming code-bases (such as Qt-5, Chromium, KDevelop). As a temporary work-around -fno-delete-null-pointer-checks can be used. Wrong code can be identified by using -fsanitize=undefined.
The change document clearly calls this out as dangerous because it breaks a surprising amount of frequently used code.
Why would this new assumption break practical C++ code? Are there particular patterns where careless or uninformed programmers rely on this particular undefined behavior? I cannot imagine anyone writing if (this == NULL)
because that is so unnatural.
What are all the common undefined behaviours that a C++ programmer should know about?
Say, like:
a[i] = i++;
Why does the first element outside of a defined array default to zero?
I'm studying for the final exam for my introduction to C++ class. Our professor gave us this problem for practice:
Explain why the code produces the following output:
120 200 16 0
using namespace std;
int main()
{
int x[] = {120, 200, 16};
for (int i = 0; i < 4; i++)
cout << x[i] << " ";
}
The sample answer for the problem was:
The cout statement is simply cycling through the array elements whose subscript is being defined by the increment of the for loop. The element size is not defined by the array initialization. The for loop defines the size of the array, which happens to exceed the number of initialized elements, thereby defaulting to zero for the last element. The first for loop prints element 0 (120), the second prints element 1 (200), the third loop prints element 2 (16) and the forth loop prints the default array value of zero since nothing is initialized for element 3. At this point i now exceeds the condition and the for loop is terminated.
I'm a bit confused as to why that last element outside of the array always "defaults" to zero. Just to experiment, I pasted the code from the problem into my IDE, but changed the for loop to for (int i = 0; i < 8; i++)
. The output then changed to 120 200 16 0 4196320 0 547306487 32655
. Why is there not an error when trying to access elements from an array that is outside of the defined size? Does the program just output whatever "leftover" data was there from the last time a value was saved to that memory address?
Consider this code:
int i = 1;
int x = ++i + ++i;
We have some guesses for what a compiler might do for this code, assuming it compiles.
++i
return 2
, resulting in x=4
.++i
returns 2
and the other returns 3
, resulting in x=5
.++i
return 3
, resulting in x=6
.To me, the second seems most likely. One of the two ++
operators is executed with i = 1
, the i
is incremented, and the result 2
is returned. Then the second ++
operator is executed with i = 2
, the i
is incremented, and the result 3
is returned. Then 2
and 3
are added together to give 5
.
However, I ran this code in Visual Studio, and the result was 6
. I'm trying to understand compilers better, and I'm wondering what could possibly lead to a result of 6
. My only guess is that the code could be executed with some "built-in" concurrency. The two ++
operators were called, each incremented i
before the other returned, and then they both returned 3
. This would contradict my understanding of the call stack, and would need to be explained away.
What (reasonable) things could a C++
compiler do that would lead to a result of 4
or a result or 6
?
This example appeared as an example of undefined behavior in Bjarne Stroustrup's Programming: Principles and Practice using C++ (C++ 14).
See cinnamon's comment.
Is signed integer overflow still undefined behavior in C++?As we know, signed integer overflow is undefined behavior. But there is something interesting in C++11 cstdint
documentation:
signed integer type with width of exactly 8, 16, 32 and 64 bits respectively with no padding bits and using 2's complement for negative values (provided only if the implementation directly supports the type)
And here is my question: since the standard says explicitly that for int8_t
, int16_t
, int32_t
and int64_t
negative numbers are 2's complement, is still overflow of these types an undefined behavior?
Edit I checked C++11 and C11 Standards and here is what I found:
C++11, §18.4.1:
The header defines all functions, types, and macros the same as 7.20 in the C standard.
C11, §7.20.1.1:
(Why) is using an uninitialized variable undefined behavior?The typedef name
intN_t
designates a signed integer type with width N, no padding bits, and a two’s complement representation. Thus,int8_t
denotes such a signed integer type with a width of exactly 8 bits.
If I have:
unsigned int x;
x -= x;
it's clear that x
should be zero after this expression, but everywhere I look, they say the behavior of this code is undefined, not merely the value of x
(until before the subtraction).
Two questions:
Is the behavior of this code indeed undefined?
(E.g. Might the code crash [or worse] on a compliant system?)
If so, why does C say that the behavior is undefined, when it is perfectly clear that x
should be zero here?
i.e. What is the advantage given by not defining the behavior here?
Clearly, the compiler could simply use whatever garbage value it deemed "handy" inside the variable, and it would work as intended... what's wrong with that approach?
What is the strict aliasing rule?When asking about common undefined behavior in C, people sometimes refer to the strict aliasing rule.
What are they talking about?
#include <stdio.h>
int main(void)
{
int i = 0;
i = i++ + ++i;
printf("%d\n", i); // 3
i = 1;
i = (i++);
printf("%d\n", i); // 2 Should be 1, no ?
volatile int u = 0;
u = u++ + ++u;
printf("%d\n", u); // 1
u = 1;
u = (u++);
printf("%d\n", u); // 2 Should also be one, no ?
register int v = 0;
v = v++ + ++v;
printf("%d\n", v); // 3 (Should be the same as u ?)
int w = 0;
printf("%d %d\n", ++w, w); // shouldn't this print 1 1
int x[2] = { 5, 8 }, y = 0;
x[y] = y ++;
printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}
Is the "struct hack" technically undefined behavior?
What I am asking about is the well known "last member of a struct has variable length" trick. It goes something like this:
struct T {
int len;
char s[1];
};
struct T *p = malloc(sizeof(struct T) + 100);
p->len = 100;
strcpy(p->s, "hello world");
Because of the way that the struct is laid out in memory, we are able to overlay the struct over a larger than necessary block and treat the last member as if it were larger than the 1 char
specified.
So the question is: Is this technique technically undefined behavior?. I would expect that it is, but was curious what the standard says about this.
PS: I am aware of the C99 approach to this, I would like the answers to stick specifically to the version of the trick as listed above.
Why is f(i = -1, i = -1) undefined behavior?I was reading about order of evaluation violations, and they give an example that puzzles me.
1) If a side effect on a scalar object is un-sequenced relative to another side effect on the same scalar object, the behavior is undefined.
// snip f(i = -1, i = -1); // undefined behavior
In this context, i
is a scalar object, which apparently means
Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types.
I don’t see how the statement is ambiguous in that case. It seems to me that regardless of if the first or second argument is evaluated first, i
ends up as -1
, and both arguments are also -1
.
Can someone please clarify?
I really appreciate all the discussion. So far, I like @harmic’s answer a lot since it exposes the pitfalls and intricacies of defining this statement in spite of how straight forward it looks at first glance. @acheong87 points out some issues that come up when using references, but I think that's orthogonal to the unsequenced side effects aspect of this question.
Since this question got a ton of attention, I will summarize the main points/answers. First, allow me a small digression to point out that "why" can have closely related yet subtly different meanings, namely "for what cause", "for what reason", and "for what purpose". I will group the answers by which of those meanings of "why" they addressed.
The main answer here comes from Paul Draper, with Martin J contributing a similar but not as extensive answer. Paul Draper's answer boils down to
It is undefined behavior because it is not defined what the behavior is.
The answer is overall very good in terms of explaining what the C++ standard says. It also addresses some related cases of UB such as f(++i, ++i);
and f(i=1, i=-1);
. In the first of the related cases, it's not clear if the first argument should be i+1
and the second i+2
or vice versa; in the second, it's not clear if i
should be 1 or -1 after the function call. Both of these cases are UB because they fall under the following rule:
If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.
Therefore, f(i=-1, i=-1)
is also UB since it falls under the same rule, despite that the intention of the programmer is (IMHO) obvious and unambiguous.
Paul Draper also makes it explicit in his conclusion that
Could it have been defined behavior? Yes. Was it defined? No.
which brings us to the question of "for what reason/purpose was f(i=-1, i=-1)
left as undefined behavior?"
Although there are some oversights (maybe careless) in the C++ standard, many omissions are well-reasoned and serve a specific purpose. Although I am aware that the purpose is often either "make the compiler-writer's job easier", or "faster code", I was mainly interested to know if there is a good reason leave f(i=-1, i=-1)
as UB.
harmic and supercat provide the main answers that provide a reason for the UB. Harmic points out that an optimizing compiler that might break up the ostensibly atomic assignment operations into multiple machine instructions, and that it might further interleave those instructions for optimal speed. This could lead to some very surprising results: i
ends up as -2 in his scenario! Thus, harmic demonstrates how assigning the same value to a variable more than once can have ill effects if the operations are unsequenced.
supercat provides a related exposition of the pitfalls of trying to get f(i=-1, i=-1)
to do what it looks like it ought to do. He points out that on some architectures, there are hard restrictions against multiple simultaneous writes to the same memory address. A compiler could have a hard time catching this if we were dealing with something less trivial than f(i=-1, i=-1)
.
davidf also provides an example of interleaving instructions very similar to harmic's.
Although each of harmic's, supercat's and davidf' examples are somewhat contrived, taken together they still serve to provide a tangible reason why f(i=-1, i=-1)
should be undefined behavior.
I accepted harmic's answer because it did the best job of addressing all meanings of why, even though Paul Draper's answer addressed the "for what cause" portion better.
JohnB points out that if we consider overloaded assignment operators (instead of just plain scalars), then we can run into trouble as well.
Detecting signed overflow in C/C++At first glance, this question may seem like a duplicate of How to detect integer overflow?, however it is actually significantly different.
I've found that while detecting an unsigned integer overflow is pretty trivial, detecting a signed overflow in C/C++ is actually more difficult than most people think.
The most obvious, yet naive, way to do it would be something like:
int add(int lhs, int rhs)
{
int sum = lhs + rhs;
if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) {
/* an overflow has occurred */
abort();
}
return sum;
}
The problem with this is that according to the C standard, signed integer overflow is undefined behavior. In other words, according to the standard, as soon as you even cause a signed overflow, your program is just as invalid as if you dereferenced a null pointer. So you can't cause undefined behavior, and then try to detect the overflow after the fact, as in the above post-condition check example.
Even though the above check is likely to work on many compilers, you can't count on it. In fact, because the C standard says signed integer overflow is undefined, some compilers (like GCC) will optimize away the above check when optimization flags are set, because the compiler assumes a signed overflow is impossible. This totally breaks the attempt to check for overflow.
So, another possible way to check for overflow would be:
int add(int lhs, int rhs)
{
if (lhs >= 0 && rhs >= 0) {
if (INT_MAX - lhs <= rhs) {
/* overflow has occurred */
abort();
}
}
else if (lhs < 0 && rhs < 0) {
if (lhs <= INT_MIN - rhs) {
/* overflow has occurred */
abort();
}
}
return lhs + rhs;
}
This seems more promising, since we don't actually add the two integers together until we make sure in advance that performing such an add will not result in overflow. Thus, we don't cause any undefined behavior.
However, this solution is unfortunately a lot less efficient than the initial solution, since you have to perform a subtract operation just to test if your addition operation will work. And even if you don't care about this (small) performance hit, I'm still not entirely convinced this solution is adequate. The expression lhs <= INT_MIN - rhs
seems exactly like the sort of expression the compiler might optimize away, thinking that signed overflow is impossible.
So is there a better solution here? Something that is guaranteed to 1) not cause undefined behavior, and 2) not provide the compiler with an opportunity to optimize away overflow checks? I was thinking there might be some way to do it by casting both operands to unsigned, and performing checks by rolling your own two's-complement arithmetic, but I'm not really sure how to do that.
Undefined, unspecified and implementation-defined behaviorWhat is undefined behavior (UB) in C and C++? What about unspecified behavior and implementation-defined behavior? What is the difference between them?
Does the C++ standard allow for an uninitialized bool to crash a program?I know that an "undefined behaviour" in C++ can pretty much allow the compiler to do anything it wants. However, I had a crash that surprised me, as I assumed that the code was safe enough.
In this case, the real problem happened only on a specific platform using a specific compiler, and only if optimization was enabled.
I tried several things in order to reproduce the problem and simplify it to the maximum. Here's an extract of a function called Serialize
, that would take a bool parameter, and copy the string true
or false
to an existing destination buffer.
Would this function be in a code review, there would be no way to tell that it, in fact, could crash if the bool parameter was an uninitialized value?
// Zero-filled global buffer of 16 characters
char destBuffer[16];
void Serialize(bool boolValue) {
// Determine which string to print based on boolValue
const char* whichString = boolValue ? "true" : "false";
// Compute the length of the string we selected
const size_t len = strlen(whichString);
// Copy string into destination buffer, which is zero-filled (thus already null-terminated)
memcpy(destBuffer, whichString, len);
}
If this code is executed with clang 5.0.0 + optimizations, it will/can crash.
The expected ternary-operator boolValue ? "true" : "false"
looked safe enough for me, I was assuming, "Whatever garbage value is in boolValue
doesn't matter, since it will evaluate to true or false anyhow."
I have setup a Compiler Explorer example that shows the problem in the disassembly, here the complete example. Note: in order to repro the issue, the combination I've found that worked is by using Clang 5.0.0 with -O2 optimisation.
#include <iostream>
#include <cstring>
// Simple struct, with an empty constructor that doesn't initialize anything
struct FStruct {
bool uninitializedBool;
__attribute__ ((noinline)) // Note: the constructor must be declared noinline to trigger the problem
FStruct() {};
};
char destBuffer[16];
// Small utility function that allocates and returns a string "true" or "false" depending on the value of the parameter
void Serialize(bool boolValue) {
// Determine which string to print depending if 'boolValue' is evaluated as true or false
const char* whichString = boolValue ? "true" : "false";
// Compute the length of the string we selected
size_t len = strlen(whichString);
memcpy(destBuffer, whichString, len);
}
int main()
{
// Locally construct an instance of our struct here on the stack. The bool member uninitializedBool is uninitialized.
FStruct structInstance;
// Output "true" or "false" to stdout
Serialize(structInstance.uninitializedBool);
return 0;
}
The problem arises because of the optimizer: It was clever enough to deduce that the strings "true" and "false" only differs in length by 1. So instead of really calculating the length, it uses the value of the bool itself, which should technically be either 0 or 1, and goes like this:
const size_t len = strlen(whichString); // original code
const size_t len = 5 - boolValue; // clang clever optimization
While this is "clever", so to speak, my question is: Does the C++ standard allow a compiler to assume a bool can only have an internal numerical representation of '0' or '1' and use it in such a way?
Or is this a case of implementation-defined, in which case the implementation assumed that all its bools will only ever contain 0 or 1, and any other value is undefined behaviour territory?
Does "Undefined Behavior" really permit *anything* to happen?The classic apocryphal example of "undefined behavior" is, of course, "nasal demons" — a physical impossibility, regardless of what the C and C++ standards permit.
Because the C and C++ communities tend to put such an emphasis on the unpredictability of undefined behavior and the idea that the compiler is allowed to cause the program to do literally anything when undefined behavior is encountered, I had assumed that the standard puts no restrictions whatsoever on the behavior of, well, undefined behavior.
But the relevant quote in the C++ standard seems to be:
[C++14: defns.undefined]:
[..] Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). [..]
This actually specifies a small set of possible options:
I assume that in most cases, compilers choose to ignore the undefined behavior; for example, when reading uninitialized memory, it would presumably be an anti-optimization to insert any code to ensure consistent behavior. I suppose that the stranger types of undefined behavior (such as "time travel") would fall under the second category--but this requires that such behaviors be documented and "characteristic of the environment" (so I guess nasal demons are only produced by infernal computers?).
Am I misunderstanding the definition? Are these intended as mere examples of what could constitute undefined behavior, rather than a comprehensive list of options? Is the claim that "anything can happen" meant merely as an unexpected side-effect of ignoring the situation?
Two minor points of clarification:
This question was not intended as a forum for discussion about the (de)merits of undefined behavior, but that's sort of what it became. In any case, this thread about a hypothetical C-compiler with no undefined behavior may be of additional interest to those who think this is an important topic.
undefined-behavior