Below are two common issues resulting in undefined behavior due to the sequence point rules:
a[i] = i++; //has a read and write between sequence points
i = i++;
An example similar to Dario's, which I've also seen people fall into:
printf("%s %s\n", inet_ntoa(&addr1), inet_ntoa(&addr2));
Not only will this either print "addr1 addr1
" or "addr2 addr2
" (because inet_ntoa
returns a pointer to a static buffer overwritten by further calls), but also it is not defined which of these will be the case (because C does not specify order of evaluation in argument lists).
Here are two good expressions that work for most C compilers, yet are ambiguous due to sequence points:
x ^= y ^= x ^= y; // in-place swap of two variables
And also
int i=0;
printf("%d %d %d", ++i, ++i, ++i); // usually prints out 3 2 1... but not for all compilers!
The one I've seen recently was due to programmer's desire to save on class formatting time, completely wrong-headed:
class A
{
public:
...
const char* Format( const string& f ) const
{
fmt = Print( f, value );
return fmt.c_str();
}
operator const char* () const { return fmt.c_str(); }
private:
struct timeval value;
mutable string fmt;
};
A a( ... );
printf( "%s %s\n", a.Format( x ), a.Format( y );
The last line would either always print the same value for both formats (or crash the program since internal string would release the returned memory).
Another one is from some interview I had long ago:
void func( int x, int y, int z )
{
printf( "%d %d %d\n", x, y, z );
}
...
int i = 0;
func( i, ++i, i++ ); /* don't do this in real software :) */
Here is a simple rule from Programming principles and practices using c++ by Bjarne Stroustup
"if you change the value of a variable in an expression.Don't read or write twice in the same expression"
a[i] = i++; //i's value is changed once but read twice
i = i++; //i's value is changed once but written twice
A variation of Dario's example is this:
void Foo(shared_ptr<Bar> a, shared_ptr<Bar> b){ ... }
int main() {
Foo(shared_ptr<Bar>(new Bar), shared_ptr<Bar>(new Bar));
}
which might leak memory. There is no sequence point between the evaluation of the two parameters, so not only may the second argument be evaluated before the first, but both Bar objects may also be created before any of the shared_ptr
's
That is, instead of being evaluated as
Bar* b0 = new Bar();
arg0 = shared_ptr<Bar>(b0);
Bar* b1 = new Bar();
arg1 = shared_ptr<Bar>(b1);
Foo(arg0, arg1);
(which would be safe, because if b0
gets successfully allocated, it gets immediately wrapped in a shared_ptr
), it may be evaluated as:
Bar* b0 = new Bar();
Bar* b1 = new Bar();
arg0 = shared_ptr<Bar>(b0);
arg1 = shared_ptr<Bar>(b1);
Foo(arg0, arg1);
which means that if b0
gets allocated successfully, and b1
throws an exception, then b0
will never be deleted.
There are some ambigous cases concerning the order of execution in parameter lists or e.g. additions.
#include <iostream>
using namespace std;
int a() {
cout << "Eval a" << endl;
return 1;
}
int b() {
cout << "Eval b" << endl;
return 2;
}
int plus(int x, int y) {
return x + y;
}
int main() {
int x = a() + b();
int res = plus(a(), b());
return 0;
}
Is a() or b() executed first? ;-)