I am having a problem with some C code built with gcc compiler. The code in question has an enum, whose values are used as cases in a switch statement to configure an object. Fairly standard stuff.
When I compile the code using the -O0 option flag, everything builds and runs correctly no problem. However, when the flag is set to -O2 the code no longer works as expected.
When I step through the code and put a watch on the local variables, the enum, which should be only be one of three enum values, is actually -104! This causes the program to fail to configure the object.
Has anyone encountered this before who could provide some guidance? I haven't encountered this before and would appreciate if someone could explain why the compiler does this so I can make any necessary changes.
Snippet of code in question:
value = 0u;
switch(test_config) {
case DISABLE:
break;
case INTERNAL:
value = 1u;
break;
case EXTERNAL:
value = 2u;
break;
default:
valid = FALSE;
break;
}
if (valid) {
configure_test(value);
}
Enum in question:
typedef enum {
DISABLE,
INTERNAL,
EXTERNAL
} test_config_t;
This is the code that is causing the problem. I initially didn't include it because I didn't want the question to be please fix my code, rather I have been googling looking for reasons why gcc optimisation flags would produce different results for the same piece of code and haven't found anything particularly helpful. Also I am not at my computer and had to type this on my phone which also doesn't help. So I came here because there are experts here who know way more than me that could point me in the right direction.
Some more info that I probably should have included. The code runs on hardware which also might be the problem and I am looking into that as well. When ran from FSBL the code works with -O0, but not with -O2. So it may be hardware, but then I don't know why it works one way not the other.
You don't give enough details (since your question don't show any actual code, it should have some MCVE) but you very probably have some undefined behavior and you should be scared.
Remember that C11 or C99 (like most programming languages) is defined by an explicit specification (not only by the concrete behaviour observed on your code) written in English and partly defining the runtime behaviour of a valid C program. Read n1570.
I strongly recommend reading Lattner's blog What Every C programmer should know about Undefined Behavior before even touching or compiling your source code.
I recommend at least compiling with (nearly) all warnings and debug info, e.g. with gcc -Wall -Wextra -g, then improve the code to get no warnings, and run it under the gdb debugger and valgrind. Read more about Invoking GCC. You may also use (temporarily) some sanitizer instrumentation options, notably -fsanitize=undefined and -fsanitize=address. You could also add -std=gnu99 and -pedantic to your compiler flags. Notice that gdb watchpoints are a very useful debugger feature to find why a value has changed or is unexpected.
When you compile for release or for benchmarking with optimizations enabled, keep also the warning flags (so compile with gcc -O2 -Wall -Wextra); optimizations might give extra warnings which you should also correct. BTW, GCC accepts both -O2 and -g at the same time.
When you observe such issues, question first your own code before suspecting the compiler (because compilers are very well tested; I found only one compiler bug in almost 40 years of programming).
Related
The carefully crafted, self-including code in this IOCCC winning entry from 1988:
http://www.ioccc.org/years.html#1988_isaak
...was still too much for certain systems back then. Also, ANSI C was finally emerging as a stable alternative to the chaotic K&R ecosystem. As a result, the IOCCC judges also provided an ANSI version of this entry:
http://www.ioccc.org/1988/isaak.ansi.c
Its main attraction is its gimmick of including <stdio.h> in the last line (!) with well-thought-out #defines, both inside the source and at compile time, to only allow certain parts of the code into the right level. This is what allows the <stdio.h> header to be ultimately included at the latest stage possible, just before it is necessary, in the source fed to the compiler.
However, this version still fails to produce its output when compiled today, with the provided compiler settings:
gcc -std=c89 -DI=B -DO=- -Dy isaak.ansi.c
tcc -DI=B -DO=- -Dy isaak.ansi.c
Versions used: GCC 9.3.0, TCC 0.9.27
There isn't any evident reliance on the compiled binary filename, hence I left it to the compiler's choice. Even when using -o isaak or -o isaak.ansi, the same result happens: no output.
What is causing this? How are the output functions failing? What can be done to correct this?
Thanks in advance!
NOTE: The IOCCC judges, realising that this entry had portability issues that would detract from its obfuscation value, decided to also include a UUENCODEd version of the code's output:
http://www.ioccc.org/1988/isaak.encode
There is nothing remotely portable about this program. As I see it tries to overwrite the exit standard library function with its own code, expecting that return from empty main() would call that exit(), which is not true. And even then, such behaviour is not standard-conforming - even C89 said it would have undefined behaviour.
You can "fix" the program on modern GCC / Linux by actually calling exit(); inside main - just change the first line to
main(){exit(0);}
I compiled gcc -std=c89 -DI=B -DO=- -Dy isaak.ansi.c and run ./a.out and got sensible output out.
I've gotten a piece of software working, and am now trying to tune it up so it runs faster. I discovered something that struck as well - just bizarre. It's no longer relevant, because I switched to using a pointer instead of indexing an array (it's faster with the pointers), but I'd still like to know what is going on.
Here's the code:
short mask_num_vals(short mask)
{
short count = 0;
for(short val=0;val<NUM_VALS;val++)
if(mask & val_masks[val])
count++;
return count;
}
This small piece of code is called many many times. What really surprised me is that this code runs significantly faster than its predecessor, which simply had the two arguments to the "&" operation reversed.
Now, I would have thought the two versions would be, for all practical purposes, identical, and they do produce the same result. But the version above is faster - noticeably faster. It makes about a 5% difference in the running time of the overall code that uses it. My attempt to measure the amount of time spent in the function above failed completely - measuring the time used up far more time than actually executing the rest of the code. (A version of Heisenberg's principle for software, I guess.)
So my picture here is, the compiled code evaluates the two arguments, and then does a bitwise "and" on them. Who cares which order the arguments are in? Apparently the compiler or the computer does.
My completely unsupported conjecture is that the compiled code must be evaluating "val_masks[val]" for each bit. If "val_masks[val]" comes first, it evaluates it for every bit, if "mask" comes first, it doesn't bother with "val_masks[val]" if that particular bit in "mask" is zero. I have no evidence whatsoever to support this conjecture; I just can't think of anything else that might cause this behaviour.
Does this seem likely? This behaviour just seemed weird to me, and I think points to some difference in my picture of how the compiled code works, and how it actually works. Again, not all that relevant any more, as I've evolved the code further (using pointers instead of arrays). But I'd still be interested in knowing what is causing this.
Hardware is an Apple MacBook Pro 15-inch 2018, MacOS 10.15.5. Software is gcc compiler, and "gcc --version" produces the following output.
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 11.0.3 (clang-1103.0.32.62)
Target: x86_64-apple-darwin19.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
Compiled with the command "gcc -c -Wall 'C filename'", linked with "gcc -o -Wall 'object filenames'".
Code optimizers are often unpredictable. Their output can change after small meaningless tweaks in code, or after changing command-line options, or after upgrading the compiler. You cannot always explain why the compiler does some optimization in one case but not in another; you can guess all you want, but only experience can show.
One powerful technique in determining what is going on: convert your two versions of code to assembly language and compare.
GCC could be invoked with the command-line switch -S for that.
gcc -S -Wall -O -fverbose-asm your-c-source.c
which produces a textual assembler file your-c-source.s (you could glance into it using a pager like less or a source code editor like GNU emacs) from the C file your-c-source.c
The Clang compiler has similar options.
I know that one should always compile with both -Wall and -Wextra as they enable warnings and help us to understand our mistake, if any.
I've read that the -Wextra compiler flag is not recommended to use because it is too verbose with a lot of false positives.
I was quite surprised on reading this. So I started googling about it but I didn't get any answer as all the search results showed was "what does the -Wextra flag do?".
So, my questions are
In which all situations does the -Wextra flag emit uneccessary warnings?
Is it possible to stop the -Wextra flag from enabling the other flags that cause GCC from emitting these types of warnings?
The point about the usefulness of -Wextra warnings is that the corresponding warnings have these properties (more or less):
They generate false positives.
The false positives are relatively frequent.
Adapting the code to avoid false positives is a nuisance.
The consequence is, that many projects that switch on -Wall -Wextra give up trying to avoid all the warnings. Consequently, when you compile such a project, you see hundreds and hundreds of warnings, almost all about legit code. And the one warning that you should have seen slips unnoticed in the endless warning stream. Worse: the fact that a normal compilation spits out so many warnings makes you get sloppy about avoiding the new warnings that your new code introduces. Once a project reaches a few tens of warnings, the fight is usually over; it will require a heroic effort to bring the warning count back to zero.
Here is an example of some perfectly legitimate code that is barked at by -Wextra:
void myClass_destruct(MyClass* me) {}
It's a destructor and it's empty. Yet, it should be there simply to facilitate calling it at the appropriate points (subclass destructors), so that it is easy to add stuff to MyClass that needs cleanup. However, -Wextra will bark at the unused me parameter. This forces programmers to write code like this:
void myClass_destruct(MyClass* me) {
(void)me; //shut up the compiler barking about the unused parameter
}
This is plain noise. And it gets annoying to write such code. However, in order to achieve a zero warning count under a -Wextra regime, this noise needs to be added to the code. So, you can pick any two of these three:
Clean code
Zero warning count
-Wextra warnings
Choose wisely which one you want to drop, you won't get all three.
Some of -Wextra warnings are enforcing appropriate coding style which can conflict with some people processes. This is the only reason to avoid it. My solution in this case is to 'correct' exact set using -Wno-* syntax.
For example -Wextra implies -Wempty-body which warns you about empty bodies of some control structures like if, else and while. But if you follow from one side 'iterative' implementation style and from another want serious amount of warnings, this will be uncomfortable for you.
Here is evolving code example:
void doSomething()
{
if (validatePreConditions())
{
//TODO Handle it some time.
}
// ... Here you have real code.
}
If you have -Werror, and want to start testing without pre-condition check implemeneted, you are in trouble.
But here is pitfall, what is acceptable for 'in-development' code is to be fixed by the time code is going to production. So my option for such cases is to have different set of warnings and different build profiles. So, for example, 'developer' build is OK to have empty bodies but anything going to be merged into main-line MUST NOT have such gaps.
Another example is -Wignored-qualifiers which is included into -Wextra. In some cases you know compiler will ignore your const qualifier but you're adding it just for your own comfort (I personally don't support these qualifiers on return type but some people think they do good job with such marks).
In which all situations does the -Wextra flag emit uneccessary
warnings?
What is your definition of unnecessary? You can consult the manual (this is for 4.9.2) to see exactly what the warning flags do. Then you can decide for yourself whether or not it's right for your code base.
Is it possible to stop the -Wextra flag from enabling the other flags
that cause GCC from emitting these types of warnings?
Stick -Wno- in front of a warning in order to disable it. Not all warnings have a warning flag, which means you probably have to stick with it. You can also generally find more information by browsing their bugtracker by searching for certain warning flags and seeing if any discussions exist. If it's not in the documentation, it might be there. The developers might also express rationale for why things are the way they are.
In order to address the comment you linked to in the comments,
...or warnings about missing field initializers (when you're
intentionally initializing a structure with { 0 }, making the compiler
zero-initialize everything else automagically).
This argument is moot because it's already been fixed. For example, the following code produces no missing initializer fields warning:
typedef struct { int a; int b; } S;
int main()
{
S s = { 0 };
}
It's very simple;
A useless warning is one that never points out an actual bug.
Of course this is undecidable, it's impossible to know beforehand if a particular warning will ever 'save the bacon'.
All GCC warnings have at sometime or other pointed out real bugs, so the general theme with the GCC messages are that -Wall warnings are likely to point at errors and are easy to suppress if they don't. So this plays well with -Werror.
The -Wextra messages, OTOH point at real errors less often or may be point out constructs that are common practice in some older code or are just difficult to change to an alternative way of expressing the code. So sometimes they may indeed be "too verbose".
For new code you will normally want -Wextra on.
For old code it should be turned on if it's not "too verbose".
There are basically three types of warnings, and GCC is not very good about grouping them or making this clear:
Warnings that indicate something formally invalid at the source level that should not even compile, but that's allowed for whatever reason (usually compatibility with legacy code). These are mandated by the language standard.
Warnings indicating that your program will necessarily invoke undefined behavior if it reaches the point in the code where the warning occurs. Your program could still be valid if the code is unreachable, and in fact this will likely occur with things like:
int i = 1;
if (INT_MAX > 0x7fffffff) <<= 31;
Warnings that are purely heuristic, and do not by themselves indicate any programming error or invalidity of your program, where the compiler is simply warning you that you may have intended something different from what you wrote.
-Wextra turns on a lot of "type 3" warnings.
Generally everyone agrees types 1 and 2 are valuable, but as mentioned above, even type 2 can have false positives. Type 3 generally has lots of false positives; some of them can be suppressed with harmless changes to the code, while others require actually making your code worse (or turning off the warning locally) to avoid them.
Here's where people will disagree. To some programmers, you just turn on -Wall -Wextra and locally work around or disable warnings where false positives turn up. Unless you do that, though, false positives create a lot of noise and potentially lower the signal to noise ratio, causing you not to notice the more-serious warnings by getting you used to seeing warnings when you compile.
There are (at least) two camps of C programmers: those that think the compiler may sometimes know better, and those that think who is the compiler to tell me how to write my code, after all, I know what I'd doing.
Camp 1 enables warnings as much as they can, use lint and other code checkers to even get more hints about questionable or potentially broken code.
Camp 2 doesn't like their sloppiness being pointed out all the time by a machine, let alone spend extra effort fixing what they think is perfect as-is.
Camp 1 is sought by the industry, because they produce less buggy code, less defects, less customer bug reports. If you're programming launch vehicles, satellite equipment, anything that needs safety, controls really expensive equipment, this camp's mindset is what is needed.
Camp 2 obviously gets away far too often and the general software quality of many applications you use (on your computer, smart phone, appliance, ...) is an indication of this.
What camp do you want to belong to? The disadvantages of -Wextra depend on this answer. One man's disadvantage is another man's advantage.
I just started building some code with the intel c compiler -- icc. Our configure script likes to add the -ffast-math flag and maybe a couple others which seem to be GCC specific. Invoking icc with -ffast-math produces the following warning which I would like to silence:
icc: command line warning #10006: ignoring unknown option '-ffast-math'
As far as I see it, there are 2 ways it could be silenced (But I'd love to see other solutions). First, I could turn that warning into an error which would tell configure that -ffast-math isn't a valid option. I would hope that when configure tries to add that to the commandline, it would then see it isn't able to and decide that maybe adding it was a bad idea after all ... The second option (which I don't think is quite as clean) is to just tell icc to silence that kind of warning ...
Responding to the comments, here's the relevant portion of configure.ac:
# add -ffast-math etc if possible
AX_CHECK_COMPILER_FLAGS([-ffast-math],
[CFLAGS="$CFLAGS -ffast-math"
])
AX_CHECK_COMPILER_FLAGS([-mtune=native -march=native],
[CFLAGS="$CFLAGS -mtune=native -march=native"
])
That m4 macro appears to have been taken from here
I suppose that fixing that to be smarter would be the "holy-grail" -- But as icc returns a successful exit status even when -ffast-math is passed (or -mtune=native etc.), I don't really think that there is too much that can be done there (feel free to prove me wrong). that said, I don't want to hard-code checks for intel into the configure script.... That seems overly messy.
Just curious. Using gcc/gdb under Ubuntu 9.10.
Reading a C book that also often gives the disassembly of the object file. When reading in January, my disassembly looks a lot like the book's; now, it's quite different - possibly more optimized (I notice some re-arrangements in the assembly code now that, at least in the files I checked, look optimized). I have used optimization options -O1 - -O3 for gcc between the first and second read, but not before the first.
(1) Is the use of optimization options persistent, aka, if you use them once, you'll use them until switching them off? That would be weird (browsed man file and at least did not see anything to that sort). In the unlikely case that it is true, how do you switch them off?
(2) Has gcc's assembly changed through any recent upgrade?
(3) Does gcc sometimes produce (significantly) different assembly code although same compile options are chosen?
Thanks much.
1) No, the options don't persist.
2) Yes, the optimisers are constantly being changed and improved. If your gcc packages have been upgraded, then it is quite likely that the assembly generated for a particular source file will change.
3) Compiling with gcc is a deterministic process; if you compile the same source with the same version of gcc and the same options for the same target, then the assembly produced should be the same (modulo some symbol names).