joelkp If all your compiler is doing in the name of "optimization" is inlining a few functions behind your back, then you don't have any problems. Consider, instead, this case of aggressive compiler optimization which bit me some time back. The compiler in question is GCC (version doesn't matter--as you'll see presently).
There was this network-packet processing code I'd written--standard stuff: read an ethernet packet from the card, do some munging on it, pass it up to the main program. I made some trivial change, and then the program started crashing on some inputs. After a day or two of hair-pulling, I discovered that my code was OK (arguably--you'll see why further on), and that this was a case of code improvement by the compiler aka. optimization. Here's a test code which demonstrates what was going on:
$ cat -n t.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int
6 main(int argc, char* argv[])
7 {
8 enum { N = 32 };
9 char buf[N];
10 char *s = NULL; /* Initialize */
11 size_t n = 0; /* " */
12
13 if (argc > 1) {
14 s = argv[1];
15 if ((n = strlen(s)) >= N) {
16 fprintf(stderr, "%s: arg too long\n", *argv);
17 exit(EXIT_FAILURE);
18 }
19 }
20
21 memmove(buf, s, n);
22 buf[n] = 0;
23
24 if (s == NULL)
25 printf("`s' is null--compiler OK.\n");
26 else /* GCC says s is not NULL when it is! */
27 printf("compiler says `s' is not NULL when it is!\n");
28
29 return 0;
30 }
$
Notice that if no argument is passed to the program, s
is not touched. Let's run it:
$ gcc -O -o t t.c
$ ./t
compiler says `s' is not NULL when it is!
$
Huh? I mean: WTF!?
Try w/o optimizatons:
$ gcc -o t t.c
$ ./t
`s' is null--compiler OK.
$
That worked. But, what's going on here? Compiling with -fsanitize=undefined
gives a clue (in the old days, before ubiquitous static analysis, sanitizers and such-like, we used to look at the assembly output):
$ gcc -O -fsanitize=undefined -o t t.c
$ ./t
t.c:21:2: runtime error: null pointer passed as argument 2, which is declared to never be null
`s' is null--compiler OK.
$
Notice how program behaviour has changed. Line 21 is the memmove
. Check declaration of memmove
:
$ fgrep -A1 memmove /usr/include/string.h
extern void *memmove (void *__dest, const void *__src, size_t __n)
__THROW __nonnull ((1, 2));
$
Ahh! memmove
is annotated. It says: args. 1 and 2 to the function can be assumed to be nonnull
.
So, what gcc has done is look at that annotation and gone: Oh! I see you've called memmove
, so I can therefore assume that s
is not-NULL--even if n
is 0
and memmove
hasn't done anything--and optimize the other case out completely (and throw away your initialization statement too).
This is the kind of (infamous) GCC "optimization" that led to a root exploit in the Linux kernel a few years ago.
What about NetBSD, where memmove
(and other similar functions) are not annotated (at least, I couldn't find it)? We'll, GCC still optimizes away my useful code.
What gets my goat is that other people have also stumbled on this GCC issue. And the GCC folks won't fix it because: well... standards, annotations, droit du compiler, whatever... Even the latest GCC, 10.2.0, does this.
clang
, BTW, does the correct thing here. Try it.