tags:

views:

470

answers:

4
#include <setjmp.h>
#include <vector>

int main(int argc, char**) {
 std::vector<int> foo(argc);
 jmp_buf env;
 if (setjmp(env)) return 1;
}

Compiling the above code with GCC 4.4.1, g++ test.cc -Wextra -O1, gives this confusing warning:

/usr/include/c++/4.4/bits/stl_vector.h: In function ‘int main(int, char**)’:
/usr/include/c++/4.4/bits/stl_vector.h:1035: warning: variable ‘__first’ might be clobbered by ‘longjmp’ or ‘vfork’

Line 1035 of stl_vector.h is in a helper function used by the vector(n, value) constructor that I invoke while constructing foo. The warning disappears if the compiler can figure out the argument value (e.g. it is a numeric literal), so I use argc in this test case because the compiler cannot determine the value of that.

I guess the warning might be because of compiler optimizing the vector construction so that it actually happens after the setjmp landing point (which seems to be the case here when the constructor argument depends on a parameter of the function).

How can I avoid the problem, preferably without having to break the setjmp part to another function?

Not using setjmp is not an option because I am stuck with a bunch of C libraries that require using it for error handling.

+2  A: 

As evidenced by the line number 1035 in the error message, your code snippet has considerably simplified the actual problem code. You went too far. There is no clue as to how you are using 'first'. The problem is that the compiler can't figure that out even in the real code. It is afraid that the value of 'first' after a non-zero return from 'setjmp' may not be what you think it is. This is because you changed its value both before and after the first call (zero return) to 'setjmp'. If the variable was stored in a register, the value will probably be different than if it were stored in memory. So the compiler is being conservative by giving you the warning.

To take a blind leap and answer the question, you may be able to get rid of the warning message by qualifying the declaration of 'first' with 'volatile'. You could also try making 'first' global. Perhaps by dropping the optimization level (-O flag), you could cause the compiler to keep variables in memory. These are quick fixes, and may actually hide a bug.

You should really take a look at your code, and how you are using 'first'. I'll take another wild guess, and say you may be able to eliminate that variable. Could that name, 'first', mean you are using it to indicate a first call (zero return) to 'setjmp'? If so, get rid of it - redesign your logic.

If the real code just exits on a non-zero return from 'setjmp' (as in the snippet), then the value of 'first' doesn't matter in that logic path. Don't use it on both sides of the 'setjmp'.

gary
Ooops - just noticed that the error refers to the library file. I assumed you had just provided a sample snippet because I didn't get a warning on that code, and saw no reason for it to cause one. Maybe you should provide some platform info, and compiler flags. There really isn't any more of your code required to cause the warning....?
gary
+4  A: 

This is not a warning that you should ignore, longjmp() and C++ objects don't get along with each other. The problem is that the compiler automatically emits a destructor call for your foo object. A longjmp() can bypass the destructor call.

C++ exceptions unwind stack frames too but they guarantee that destructors of local objects will be called. No such guarantee from longjmp(). Finding out if longjmp() is going to byte you requires carefully analyzing the local variables in each function which might be terminated early due to the longjmp(). That's not easy.

Hans Passant
I don't think that what you are describing is happening here since foo is defined before the setjmp, so that no objects would be destructed on longjmp.
Tronic
I'm sure we're not looking at the real code.
Hans Passant
The code posted gives the error. The real code is at git://git.performous.org/gitroot/performous/performous (game/image.hh), but I don't think it really helps viewing the exact same problem in a far more complex environment.
Tronic
A: 

The quick answer: drop the -O1 flag or regress the compiler to an earlier version. Either one made the warning disappear on my system. I had to build and use gcc4.4 to get the warning in the first place. (darn that's a huge system)

No? I thought not.

I really don't understand everything C++ does with its objects, and exactly how they are deallocated. Yet OP's comment that the problem didn't occur if a constant value were used in place of 'argc' for the vector size gives me an opportunity to stick my neck out. I'll hazard a guess that C++ uses the '__first' pointer on deallocation only when the initial allocation is not a constant. At the higher level of optimization, the compiler uses the registers more and there is a conflict between the pre- and post-setjmp allocations ... I don't know, it makes no sense.

The general meaning of this warning is "Are you sure you know what you are doing?" The compiler doesn't know if you know what the value of '_first' will be when you do the longjmp, and get a non-zero return from 'setjmp'. The question is whether its value after the (non-zero) return is the value that was put into the save buffer, or the value that you created after the save. In this case, it's confusing because you didn't know you were using '_first', and because in such a simple program, there is no (explicit) change to '__first'

The compiler can't analyze logic flow in a complex program, so it apparently doesn't even try for any program. It allows for the possibility that you did change the value. So it just gives you a friendly 'heads-up'. The compiler is second guessing you, trying to be helpful.

If you are stubborn with your choice of compiler and optimization, there is a programming fix. Save the environment before the allocation of the vector. Move the 'setjmp' up to the top of the program. Depending on the vector use and the error logic in the real program, this may require other changes.

edit 1/21 -------

my justification (using g++-mp-4.4 -Wextra -O1 main.cpp):

#include <setjmp.h>
#include <vector>
#include <iostream>

int main(int argc, char**) {
    jmp_buf env;
    int id = -1, idd = -2;

    if ((id=setjmp(env)))
        idd = 1;
    else 
        idd = 0;
    std::cout<<"Start with "<< id << " " << idd <<std::endl;
    std::vector<int> foo(argc );

    if(id != 4)
        longjmp(env, id+1);

    std::cout<<"End with "<< id << " " << idd <<std::endl;
}

No warnings; a.out produced:

Start with 0 0
Start with 1 1
Start with 2 1
Start with 3 1
Start with 4 1
End with 4 1

gary
longjmp over the construction of a C++ object is UB and therefore your suggestion is incorrect. 18.7.4: "The function signature longjmp(jmp_buf jbuf, int val) has more restricted behavior in this International Standard. If any automatic objects would be destroyed by a thrown exception transferring control to another (destination) point in the program, then a call to longjmp(jbuf, val) at the throw point that transfers control to the same (destination) point has undefined behavior."
Tronic
The code you added in the edit leaks memory: (valgrind) definitely lost: 16 bytes in 4 blocks
Tronic
+3  A: 

The rule is that any non-volatile, non-static local variable in the stack frame calling setjmp might be clobbered by a call to longjmp. The easiest way to deal with it is to ensure that the frame you call setjmp doesn't contain any such variables you care about. This can usually be done by putting the setjmp into a function by itself and passing in references to things that have been declared in another function that doesn't call setjmp:

#include <setjmp.h>
#include <vector>

int wrap_libcall(std::vector<int> &foo)
{
  jmp_buf env;
  // no other local vars
  if (setjmp(env)) return 1;
  // do stuff with your library that might call longjmp
  return 0;
}

int main(int argc, char**) { 
  std::vector<int> foo(argc);
  return wrap_libcall(foo);  
}

Note also that in this context, clobbering really just means resetting to the value it had when setjmp was called. So if longjmp can never be called after a modification of a local, you're ok too.

Edit

The exact quote from the C99 spec on setjmp is:

All accessible objects have values, and all other components of the abstract machine have state, as of the time the longjmp function was called, except that the values of objects of automatic storage duration that are local to the function containing the invocation of the corresponding setjmp macro that do not have volatile-qualified type and have been changed between the setjmp invocation and longjmp call are indeterminate.

Chris Dodd
Thanks, this is in line with I found in testing and you explain the situation well. I would have preferred avoiding breaking the code into an extra function (e.g. by enclosing it in {} instead, but that didn't work). I'll give the question one more day and if no-one can come up with a better solution, I'll mark this as an accepted answer.
Tronic
Btw, is there a risk of this failing too if the compiler chooses to inline wrap_libcall? The standard does not define what a stack frame is.
Tronic
The C spec refers to "objects of automatic storage duration that are local to the function containing the invocation of setjmp", so stuff outside of wrap_libcall should be safe even if its inlined. This strikes me as something that might easily be subject to compiler bugs, however.
Chris Dodd