Compiler not detecting obviously uninitialized variable

C

C Problem Overview


All C compilers I've tried won't detect uninitialized variables in the code snippet below. Yet the case is obvious here.

Don't bother about the functionality of this snippet. It's not real code, and I stripped it down for the investigation of this issue.

BOOL NearEqual (int tauxprecis, int max, int value)
{
  int tauxtrouve;      // Not initialized at this point
  int totaldiff;       // Not initialized at this point

  for (int i = 0; i < max; i++)
  {
    if (2 < totaldiff)  // At this point totaldiff is not initialized
    {
      totaldiff = 2;
      tauxtrouve = value;  // Commenting this line out will produce warning
    }
  }

  return tauxtrouve == tauxprecis ;  // At this point tauxtrouve is potentially
                                     // not initialized.
}

On the other hand, if I comment out tauxtrouve = value ;, I get the "local variable 'tauxtrouve' used without having been initialized" warning.

I tried these compilers:

  • GCC 4.9.2 with -Wall -WExtra
  • Microsoft Visual C++ 2013 with all warnings enabled

C Solutions


Solution 1 - C

The obviousness with which this variable is not initialized is overstated. Path analysis costs time and your compiler vendors either didn't want to implement the feature or thought it would cost you too much time -- or you just didn't explicitly opt-in.

For example, with clang:

$ clang -Wall -Wextra -c obvious.c 
$ clang -Wall -Wextra --analyze -c obvious.c 
obvious.c:9:11: warning: The right operand of '<' is a garbage value
    if (2 < totaldiff)  // at this point totaldiff is not initialized
          ^ ~~~~~~~~~
obvious.c:16:21: warning: The left operand of '==' is a garbage value
  return tauxtrouve == tauxprecis ;  // at this point tauxtrouve is potentially
         ~~~~~~~~~~ ^
2 warnings generated.

The difference in execution time for these naïve examples is negligible. But imagine a translation unit with thousands of lines, tens of functions, each with loops and heavy nesting. The number of paths quickly compounds and becomes a large burden to analyze whether or not the first iteration through the loop whether the assignment will occur prior to that comparison.


EDIT: @Matthieu points out that with LLVM/clang, the path analysis required to find use-of-uninitialized value does not compound as nesting increases because of the SSA notation used by the IR.

It wasn't as simple as "-S -emit-llvm" like I'd hoped, but I found the SSA-notation output he described. I'll be honest, I'm not familiar enough with LLVM IR to be sure, but I'll take Matthieu's word for it.

Bottom line: use clang with --analyze, or convince someone to fix the gcc bug.

; Function Attrs: nounwind uwtable
define i32 @NearEqual(i32 %tauxprecis, i32 %max, i32 %value) #0 {
  br label %1

; <label>:1                                       ; preds = %7, %0
  %tauxtrouve.0 = phi i32 [ undef, %0 ], [ %tauxtrouve.1, %7 ]
  %i.0 = phi i32 [ 0, %0 ], [ %8, %7 ]
  %2 = icmp slt i32 %i.0, %max
  br i1 %2, label %3, label %9

; <label>:3                                       ; preds = %1
  %4 = icmp slt i32 2, 2
  br i1 %4, label %5, label %6

; <label>:5                                       ; preds = %3
  br label %6

; <label>:6                                       ; preds = %5, %3
  %tauxtrouve.1 = phi i32 [ %value, %5 ], [ %tauxtrouve.0, %3 ]
  br label %7

; <label>:7                                       ; preds = %6
  %8 = add nsw i32 %i.0, 1
  br label %1

; <label>:9                                       ; preds = %1
  %10 = icmp eq i32 %tauxtrouve.0, %tauxprecis
  %11 = zext i1 %10 to i32
  ret i32 %11
}

Solution 2 - C

Yes, it should raise a warning about that uninitialized variable, but it's a GCC bug. The example given there is:

unsigned bmp_iter_set ();
int something (void);

void bitmap_print_value_set (void)
{
    unsigned first;

    for (; bmp_iter_set (); )
    {
        if (!first)
            something ();
        first = 0;
    }
}

And diagnosed with -O2 -W -Wall.

Unfortunately, this year is the 10 year anniversary of this bug!

Solution 3 - C

This answer only addresses GCC.

After further investigation and comments, there is more going on than in my previous answer. This code snippet has two uninitialized variables, and each of them is undetected for a different reason.

First of all, the GCC documentation for the -Wuninitialized option says:

> Because these warnings depend on optimization, the exact variables or elements for which there are warnings depends on the precise optimization options and version of GCC used.

Previous versions of the GCC manual worded this more explicitly. Here's an excerpt from the manual for GCC 3.3.6:

> These warnings are possible only in optimizing compilation, because they require data flow information that is computed only when optimizing. If you don't specify -O, you simply won't get these warnings.

It seems the current version may give some warnings without uninitialized variables without -O, but you still get much better results with it.

If I compile your example using gcc -std=c99 -Wall -O, I get:

foo.c: In function ‘NearEqual’:
foo.c:15:21: warning: ‘tauxtrouve’ is used uninitialized in this function [-Wuninitialized]
   return tauxtrouve == tauxprecis ;  // at this point tauxtrouve is potentially
                     ^

(Note this is with GCC 4.8.2 as I don't have 4.9.x installed, but the principle should be the same.)

So that detects the fact that tauxtrouve is uninitialized.

However, if we partially fix the code by adding an initializer for tauxtrouve (but not for totaldiff), then gcc -std=c99 -Wall -O accepts it without any warnings. This would appear to be an instance of the "bug" cited in haccks's answer.

There is some question as to whether this should really be considered a bug: GCC doesn't promise to catch every possible instance of an uninitialized variable. Indeed, it can't do so with perfect accuracy, because that's the halting problem. So warnings like this can be helpful when they work, but the absence of warnings does not prove that your code is free of uninitialized variables! They are really not a substitute for carefully checking your own code.

In the bug report linked by haccks, there is much discussion as to whether the bug is even fixable, or whether trying to detect this particular construct would result in an unacceptable false positive rate for other correct code.

Solution 4 - C

Michael, I don't know which version of Visual Studio 2013 you tried this on, but it is most certainly outdated. Visual Studio 2013 Update 4 correctly produces the following error message on the first use of totaldiff:

error C4700: uninitialized local variable 'totaldiff' used

You should consider updating your work environment.

By the way, here is what I see directly in the editor:

Visual Studio 2013 caught the error

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJabberwockyView Question on Stackoverflow
Solution 1 - CBrian CainView Answer on Stackoverflow
Solution 2 - ChaccksView Answer on Stackoverflow
Solution 3 - CNate EldredgeView Answer on Stackoverflow
Solution 4 - CPierre ArnaudView Answer on Stackoverflow