Warning "might be clobbered" on C++ object with setjmp
#include <setjmp.h> #include <vector> int main(i开发者_开发问答nt 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.
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.
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.
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'.
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
精彩评论