Clang -Wconditional-uninitialized on struct member array assignment
Clang -Wconditional-uninitialized on struct member array assignment
I've run into some unusual behavior involving Clang -Wconditional-uninitialized
. Consider the following example:
-Wconditional-uninitialized
typedef struct int x[1]; test_t;
test_t foo(void);
test_t foo()
test_t t;
for(int i = 0; i < 1; i++) t.x[i] = 0;
return t;
int main()
Compiling with, e.g.
clang -o test -Weverything test.c
gives the following warnings:
test.c:6:12: warning: variable 't' may be uninitialized when used here [-Wconditional-uninitialized]
return t;
^
test.c:4:5: note: variable 't' is declared here
test_t t;
^
1 warning generated.
However, switching the line:
for(int i = 0; i < 1; i++) t.x[i] = 0;
with
t.x[0] = 0;
yields no warnings. Note that it is sufficient to use -Wconditional-uninitialized
and not necessary to pass -Weverything
. The -O3
optimized assembly from both cases is identical, with a single xorl %eax, %eax
emitted in both cases. This is the minimum example I could create that showed this behavior.
-Wconditional-uninitialized
-Weverything
-O3
xorl %eax, %eax
I take some of Clang's -Weverything
output with a grain of salt, but this seems to me like a bug and I'm rather confused. Is there some kind of undefined behavior I'm invoking that's causing this, or is this a false positive? Is it in general a good or bad idea to keep -Wconditional-uninitialized
enabled? I see the same behavior on Clang 3.8.1 on Linux and (Apple) 9.1.0 on macOS.
-Weverything
-Wconditional-uninitialized
struct
struct
struct
@toohonestforthissite The example here is intentionally extremely simple. It is still unclear to me how the compiler can't figure out the outcome of a loop with a constant, well-defined number of iterations. The same happens for more than 1 element. If the structure is passed into
foo()
as a pointer and modified instead of getting created and returned, there is no warning as one might expect. I know that is the typical way but I thought the above setup should still be valid, which is why the behavior is odd to me.– EE_
Sep 6 '18 at 0:48
foo()
This is just a bug and should be reported as such.
– R..
Sep 6 '18 at 10:27
Because a compiler is not an interpreter. To understand why the compiler does not figure this out, you should learn how compilers work and how they actually figure out when a variable is initialised. If you are sure the code is correct you can disable the warning around these lines or for this function. You might also have overread the "may be" part of the warning. For more read the following comment, too.
– too honest for this site
Sep 6 '18 at 10:54
And @R.., It's not a bug. Spurious warnings like that can happen, because the compiler can't figure out all possible situations something is initialised. A compiler is not a static code analyser and even those can't test all sutuations. If you have a solution for the Halting Problem, you should spread it. Fame is wating for you. In fact those warnings are a goodie, the compiler is not required to warn about uninitialised variables.
– too honest for this site
Sep 6 '18 at 10:54
1 Answer
1
As @R.. suggested above in the comments, I've reported this as LLVM bug 38856. Apparently, according to them:
-Wconditional-uninitialized is expected to have false positives: it warns in cases where a simple control flow analysis cannot prove that a variable was written to prior to each use. It is not field-sensitive nor data flow sensitive, so it cannot tell the difference between writing to one array element and writing to the whole array, and doesn't know that the loop actually always executes at least once.
-Winitialized, by contrast, only warns when we can prove your program has a bug (or contains dead code).
There are a few improvements we could make to reduce the false positive rate here (some crude field sensitivity might be relatively straightforward to add), but we don't want to recreate the full intelligence of the clang static analyser here, and keeping this warning simple enough that we can run it as part of normal compilations is a goal.
I also noted in that report that the following example gives no warnings even though one of the array members is clearly uninitialized:
#include <stdio.h>
typedef struct int x[2]; test_t;
test_t foo_3(void);
test_t foo_3()
test_t t;
t.x[0] = 0;
return t;
int main()
test_t t;
t = foo_3();
printf("%i %in", t.x[0], t.x[1]);
Which got me the reply:
As I mentioned in comment#1, the analysis we currently perform cannot tell the difference between writing to one array element and writing to the whole array. In simple cases, this should be relatively straightforward to improve, so I think for that reason this bug is valid (but of low priority, given that this warning is expected to have false positives).
So -Wconditional-uninitialized
is known for giving false positives. Apparently the static analyzer isn't as thorough as I would have expected. I'm surprised I hadn't run into anything like this before given more complicated scenarios where the analyzer seemed to have worked better, but as @toohonestforthissite also notes in the comments above, passing arrays around by value via structs like this is not a standard thing to do in C.
-Wconditional-uninitialized
-Wno-conditional-uninitialized
or #pragma clang diagnostic ignored "-Wconditional-uninitialized"
should silence that warning for those who want to use -Weverything
in a case like this.
-Wno-conditional-uninitialized
#pragma clang diagnostic ignored "-Wconditional-uninitialized"
-Weverything
Thanks for contributing an answer to Stack Overflow!
But avoid …
To learn more, see our tips on writing great answers.
Required, but never shown
Required, but never shown
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
The warning is unnecessary here, but understandable. The compiler can't be expected to know the loop condition at compile time to check if the variable is accessed at al, just consider the many possible ways to write such a loop. If you only want to clear the array and setup the
struct
, an initialiser like 0 would be the easier and more clear way. Sidenote: passingstruct
s is pretty uncommon in C (at least for non-trivialstruct
s like two scalars), as the language is completely pass-by-value, i.e. the whole data is copied everywhere. Cannonical is to pass pointers to the objects.– too honest for this site
Sep 6 '18 at 0:35