views:

385

answers:

6

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++;   //2 writes between sequence points

What are other things you have encountered with respect to sequence points?

It is really difficult to find out these issues when the compiler is not able to warn us.

+5  A: 

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? ;-)

Dario
With respect to the direct addition, s/ambiguous/undefined/, as per the standard (for C, at least)
Stephan202
It's also undefined for C++. That's not quite the same as ambiguous :)
jalf
+8  A: 

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.

jalf
+1  A: 

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).

ephemient
This one is about ill specification of the inet_ntoa function. Or about the writer of the client code not having read it :) It's not about sequence points.
xtofl
Well, yes, the issue that it prints the same address twice would indicate failure to read `man inet_ntoa` -- or at least failure to think it through. The problem that you don't know *which* address will be printed twice is due to the lack of sequence points, though. (In Java, similar code would always print the second address, because it does specify that arguments are fully evaluated in the order they appear.)
ephemient
+3  A: 

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
yesraaj
+1  A: 

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!
abelenky
how do u find out whether an expression is undefined or not?any special rule u follow
yesraaj
The in-place swap is just silly. Use std::swap in C++, or a temp variable in C. It _will_ be faster than the clever XOR trick.
bdonlan
+1  A: 
Nikolai N Fetissov
Yeah, that's pretty similar to my example, but worse because of the crashability.
ephemient