I have a program that triggers -Wmaybe-uninitialized
while using any kind of optimizations (-O1
, -O2
, -O3
) the following is the smallest code I could make that reproduces this behavior.
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
struct result {
int res;
};
struct graph {
int vertices[5];
};
// In reality this is a backtracking search.
void computational_search (struct graph *g, struct result *out) {
out->res = 0;
int i;
for (i=0; i<5; i++) {
out->res += g->vertices[i];
}
}
// In reality queries a database of geometrical graphs.
void next_graph (struct graph *g)
{
int i;
for (i=0; i<5; i++) {
g->vertices[i] += rand();
}
}
enum format_t {
FMT = 1,
FMT_1 = 2
};
int main()
{
int val = rand()%10;
int num_graphs = 5;
struct graph g;
struct result res;
uint64_t *count;
if (val & FMT) {
count = malloc(sizeof(uint64_t)*num_graphs);
}
int i;
for (i=0; i<num_graphs; i++) {
next_graph (&g);
computational_search (&g, &res);
if (val & FMT) {
count[i] = res.res; /* ERROR HERE */
}
}
return 0;
}
I'm aware there is an execution path where count
is uninitialized, but it's not being used there. Can the optimizer be doing something that may use count
uninitialized?.
Compilig with gcc -Wall -O1 test.c
outputs this on gcc 5.4.0:
test.c: In function ‘main’:
test.c:43:15: warning: ‘count’ may be used uninitialized in this function [-Wmaybe-uninitialized]
uint64_t *count;
^
The same happens for -O2
and -O3
, but not for -O0
.
Let me say it a bit bluntly: -Wmaybe-uninitialized
sucks. What GCC actually tries to do is first perform a lot of optimizing transformations and then check that every use of a variable is preceded by an initialization (hoping that the optimizer is smart enough to get all impossible paths eliminated). IIRC it also has a couple of simple heuristics. With such an approach you should expect false positives.
For example at -O3
the compiler should unswitch the loop in main
and transform it into something like this:
if (val & FMT) {
for (i=0; i<num_graphs; i++) {
next_graph (&g);
computational_search (&g, &res);
count[i] = res.res;
}
} else {
for (i=0; i<num_graphs; i++) {
next_graph (&g);
computational_search (&g, &res);
}
}
Then it should notice that we already have a val & FMT
check and merge the corresponding branches:
if (val & FMT) {
count = malloc(sizeof(uint64_t)*num_graphs);
for (i=0; i<num_graphs; i++) {
next_graph (&g);
computational_search (&g, &res);
count[i] = res.res;
}
} else {
for (i=0; i<num_graphs; i++) {
next_graph (&g);
computational_search (&g, &res);
}
}
The path involving an uninitialized use is gone. Now, back to the reality: unswitching does not happen (for some reason). If you add -fno-inline
(note: it's just a test, I'm not proposing it as a solution), it does happen and everything works as I described earlier (no warning). IMHO, this warning is very brittle and unpredictable.
Can the optimizer be doing something that may use count uninitialized?.
No, unless it has a bug.