The following C++ example fails to compile with gcc or clang, but only generates a warning with ICC, and nothing at all with MSVC:
int main(int argc, char *argv[])
{
if (argc < 2)
goto clean_up;
#if 1 // FAIL
int i = 0;
#elif 0 // workaround - OK
{
int i = 0;
}
#else // workaround - OK
int i;
i = 0;
#endif
clean_up:
return 0;
}
g++:
init.cpp:13: error: jump to label ‘clean_up’
init.cpp:4: error: from here
init.cpp:7: error: crosses initialization of ‘int i’
clang++:
init.cpp:4:9: error: cannot jump from this goto statement to its label
goto clean_up;
^
init.cpp:7:9: note: jump bypasses variable initialization
int i = 0;
^
ICC:
init.cpp(4): warning #589: transfer of control bypasses initialization of:
variable "i" (declared at line 7)
goto clean_up;
^
I understand the cause of the error, and for a simple example such as this it is fairly easy to work around (I've included a couple of possible workarounds in the example above), but I'm working on a large cross-platform legacy code base that is peppered with error-handling macros which use a similar goto construct. Other developers working with MSVC or ICC keep introducing inline initialisations which subsequently result in errors for gcc or clang builds (and of course they just ignore the warnings they get with MSVC/ICC).
So I need to find a way to either (a) make such cases result in errors on ICC/MSVC or (b) reduce them to warnings with gcc/clang. I tried -fpermissive with gcc but that doesn't seem to help.
For extra credit I'm also curious as to the rationale behind this error for simple scalar initialisation - I can see why jumping over a constructor might be problematic, but initialising an int as in the above example doesn't seem like it could ever be an issue, and simply splitting the definition+initialisation into a definition + assignment makes the error go away?
The MSVC flag for treating a warning as en error is /we n where n is the number of the warning.
For example, /we4326 flags warning number C4326 as an error.
See https://msdn.microsoft.com/en-us/library/thxezb7y.aspx for details.
Related
I have code from over 20 years in C/C++ and one technique used to handle variable data sizes was to let automatic type conversion handle it.
For example:
#define MY_STATUS_UNDEFINED (-1)
Then if it was compared/used against a int64_t it was auto expanded to -1LL, for uint64_t to 0xFFFFFFFFFFFFFFFF, for uint32_t to 0xFFFFFFFF, int16_t to -1, uint16_t to 0xFFFF, etc..
However, now I'm getting errors with the newer gcc versions that complain about narrowing conversion. In this case it was a switch statement with a UINT variable.
switch (myuint) {
case MY_STATUS_UNDEFINED:
break;
}
What is the recommended way to get it to not error out?
What is the easiest way to get it to not error out?
What is the cleanest way so it doesn't error out and doesn't give any warning message?
Essentially, I want it to do the auto type conversion properly but no warning or errors.
Using the following brief example:
#include <cstdint>
#include <iostream>
#define MY_STATUS_UNDEFINED (-1)
void bar()
{
std::cout << "It works\n";
}
void foo(uint32_t n)
{
switch (n) {
case MY_STATUS_UNDEFINED:
bar();
break;
}
}
int main()
{
foo(0xFFFFFFFF);
return 0;
}
You are seeing the following error from gcc:
error: narrowing conversion of ‘-1’ from ‘int’ to ‘unsigned int’ [-Wnarrowing]
Now, pay careful attention to the error message. gcc is telling the exact option to shut this off. See the [-Wnarrowing] annotation? That's the compilation flag that's responsible for producing this error message.
To turn it off, stick a "no-" in front of it:
-Wno-narrowing
All gcc diagnostics use this convention. Now the shown code will compile, run, and produce the expected result:
It works!
Add -Wno-narrowing to your global compilation options.
You should really consider this to be only a temporary band-aid solution. These compilation errors are exactly what you want. They're telling you about real or potential problems. -Wall -Werror -Wsuggest-override -Wreturn-type are my favorite compilation options.
int main()
{
int a = 10;
int b;
a = b;
}
Valgrind can not warn me that b is uninitialized.
Your compiler should warn you regaring the uninitialized variable. If it doesn't, maybe the warnings are turned off?
This is gcc (9.3.0) output (with -Wall -Wextra option) :
prog.cc: In function 'int main()':
prog.cc:3:9: warning: variable 'a' set but not used [-Wunused-but-set-variable]
3 | int a = 10;
| ^
prog.cc:5:7: warning: 'b' is used uninitialized in this function [-Wuninitialized]
5 | a = b;
| ~~^~~
and this clang (10.0.0):
prog.cc:5:9: warning: variable 'b' is uninitialized when used here [-Wuninitialized]
a = b;
^
prog.cc:4:10: note: initialize the variable 'b' to silence this warning
int b;
^
Compile it with -Wall flag
gcc a.c -Wall -o a
Valgrind will only output errors if there is some potential impact on the behaviour of your application. In this case it does not matter that b is uninitialized.
Valgrind is, however, tracking the state of the memory.
If you run
valgrind --vgdb-error=0 ./test_exe
Then open another terminal and follow the instructions that were printed by Valgrind in the 1st terminal, then you can run commands like
mo xb {addess of b} 4
See here for details.
Within the language, there is no way to check for indeterminate values.
In simple cases such as this, compilers can detect it and you can ask to be warned about them. See the manual of your compiler for available warning options.
Compilers also provide "sanitisers" which check for bugs at runtime and are not limited by the complexity of the program as much as the compiletime warnings are. For reads of indeterminate values, a memory sanitiser would be the choice. They don't catch everything though, and the ones I tested did not catch the bug in your program. They could detect it if the indeterminate value was used to control the flow of the program:
int a = 10;
int b;
if (b) // detected by memory sanitiser
b = a;
Visual Studio Community (free) warns:
error C4700: uninitialized local variable 'b' used
The following is nonsensical yet compiles cleanly with g++ -Wall -Wextra -Werror -Winit-self (I tested GCC 4.7.2 and 4.9.0):
#include <iostream>
#include <string>
int main()
{
for (int ii = 0; ii < 1; ++ii)
{
const std::string& str = str; // !!
std::cout << str << std::endl;
}
}
The line marked !! results in undefined behavior, yet is not diagnosed by GCC. However, commenting out the for line makes GCC complain:
error: ‘str’ is used uninitialized in this function [-Werror=uninitialized]
I would like to know: why is GCC so easily fooled here? When the code is not in a loop, GCC knows that it is wrong. But put the same code in a simple loop and GCC doesn't understand anymore. This bothers me because we rely quite a lot on the compiler to notify us when we make silly mistakes in C++, yet it fails for a seemingly trivial case.
Bonus trivia:
If you change std::string to int and turn on optimization, GCC will diagnose the error even with the loop.
If you build the broken code with -O3, GCC literally calls the ostream insert function with a null pointer for the string argument. If you thought you were safe from null references if you didn't do any unsafe casting, think again.
I have filed a GCC bug for this: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63203 - I'd still like to get a better understanding here of what went wrong and how it may impact the reliability of similar diagnostics.
I'd still like to get a better understanding here of what went wrong and how it may impact the reliability of similar diagnostics.
Unlike Clang, GCC doesn't have logic to detect self-initialized references, so getting a warning here relies on the code for detecting use of uninitialized variables, which is quite temperamental and unreliable (see Better Uninitialized Warnings for discussion).
With an int the compiler can figure out that you write an uninitialized int to the stream, but with a std::string there are apparently too many layers of abstraction between an expression of type std::string and getting the const char* it contains, and GCC fails to detect the problem.
e.g. GCC does give a warning for a simpler example with less code between the declaration and use of the variable, as long as you enable some optimization:
extern "C" int printf(const char*, ...);
struct string {
string() : data(99) { }
int data;
void print() const { printf("%d\n", data); }
};
int main()
{
for (int ii = 0; ii < 1; ++ii)
{
const string& str = str; // !!
str.print();
}
}
d.cc: In function ‘int main()’:
d.cc:6:43: warning: ‘str’ is used uninitialized in this function [-Wuninitialized]
void print() const { printf("%d\n", data); }
^
d.cc:13:19: note: ‘str’ was declared here
const string& str = str; // !!
^
I suspect this kind of missing diagnostic is only likely to affect a handful of diagnostics which rely on heuristics to detect problems. These would be the ones that give a warning of the form "may be used uninitialized" or "may violate strict aliasing rules", and probably the "array subscript is above array bounds" warning. Those warnings are not 100% accurate and "complicated" logic like loops(!) can cause the compiler to give up trying to analyse the code and fail to give a diagnostic.
IMHO the solution would be to add checking for self-initialized references at the point of initialization, and not rely on detecting it is uninitialized later when it gets used.
You claim it's undefined behavior, but when I compile the two cases to assembly, I definitely see the function-scoped variable not being initialized on the stack, and the block-scoped variable getting set to NULL.
That's as much of an answer as you're getting from me. I downloaded the C++ spec to definitively settle this, but fell into a Lovecraftian-type fugue when I gazed upon it, to preserve my fragile sanity...
I strongly suspect the block-scoped case is not actually undefined.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int argc, char* argv[])
5 {
6 int bret = 1;
7 bret - 2;
8
9 printf("bret=%d",bret);
10 return 0;
11 }
In line:7, there was no left hand operator to reveice the value, still compiler was not generating any warning, GCC and g++ both. Is there anyintended purpore behind this?
[ADDED/EDIT]
As per comment I shall get warning only after using following flags: -Wall -Wextra
[debd#test]$gcc -Wall -Wextra test2.c
test2.c: In function 'main':
test2.c:7: warning: statement with no effect
test2.c:4: warning: unused parameter 'argc'
test2.c:4: warning: unused parameter 'argv'
[debd#test]$
As far as the language is concerned, there's no error - a statement is not required to have a side effect.
However, since a statement that does nothing is almost certainly a mistake, most compilers will warn about it. Yours will, but only if you enable that warning via the command line arguments.
You can enable just that warning with -Wunused-value, but I suggest you enable a decent set of warnings (including this one) with -Wall -Wextra.
As you found, this will also give a warning that the function parameters are unused. Since this is main, you can easily fix it by changing the signature to not have any parameters:
int main()
More generally, to avoid the warning if you need to ignore parameters, C++ allows you not to name them:
int main(int, char**)
and both languages allow you to explicitly use but ignore the value
(void)argc;
(void)argv;
bret - 2;
is an expression statement and it is has no side-effect.
C does not requires any warning for this statement. The compiler is free to add or not an informative warning to say the statement has no effect. The compiler can optimize out the statement.
I noticed that g++ complain a bit too strictly about crossed initialization and I wonder why these false-positive errors could not be removed just by looking at the SSA form of the program while compiling.
Let me give a very simple example:
#include <cstdlib>
int main ()
{
goto end;
int i = 0; // unused variable declaration
end:
return EXIT_SUCCESS;
}
When compiled with g++ -Wall -Wextra -o example1 example1.cc (g++ 4.8.1), the compiler gives the following error message:
example1.cc: In function ‘int main()’:
example1.cc:10:2: error: jump to label ‘end’ [-fpermissive]
end:
^
example1.cc:6:8: error: from here [-fpermissive]
goto end;
^
example1.cc:8:7: error: crosses initialization of ‘int i’
int i = 0;
^
example1.cc:8:7: warning: unused variable ‘i’ [-Wunused-variable]
So, it will raise an error where there is actually no risk because the variable is unused (the compiler obviously has both information and cannot combine it to deduce that the error is a false-positive).
More strange, I hoped that LLVM was more efficient at analyzing a program. So, I tried clang++ (LLVM) on this simple example with clang++ -Wall -Wextra -o example1 example1.cc (clang++ 3.4). And, I got about the same error message:
example1.cc:8:7: warning: unused variable 'i' [-Wunused-variable]
int i = 0;
^
example1.cc:6:3: error: goto into protected scope
goto end;
^
example1.cc:8:7: note: jump bypasses variable initialization
int i = 0;
^
1 warning and 1 error generated.
So, I am pretty sure that I am missing something important here, a problem that make the detection of this false-positive harder than I though. But, I do not know what is it. Or maybe, the C++ specification specifically says that it must be like this.
If somebody has an idea, feel free to share !
Edit: I also compiled the exact same code in C (gcc or clang), and it went fine just with the warning about i being an unused variable. So, it reinforce the fact that this is more likely linked to the specification of C++ and not a problem detecting this issue at compile time.
There is nothing wrong with the compilers. Your code is ill-formed according to the standard.
In your particular case, the requirement of the standard may not be necessary and the jump could be allowed and the compiler could create valid code. However, this is only because the initialisation of the variable int i has no side effects.
You can make your code valid by simply enclosing the jumped section in its own scope:
#include <cstdlib>
int main ()
{
goto end;
{
int i = 0; // unused variable declaration
}
end:
// cannot use i here, as it's not defined.
return EXIT_SUCCESS;
}
This is disallowed because potentially you'd call destructors for objects that aren't properly constructed. Admittedly, int doesn't have constructor or destructor, but it's making it "fair" for all types of objects. And technically, something at label end: could be using i, and by making the rule strict, it prevents the machine having to check every single code-path (which becomes a "halting problem").