Compiler not detecting obviously uninitialized variable

前端 未结 4 1050
一整个雨季
一整个雨季 2020-12-01 11:50

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 t

相关标签:
4条回答
  • 2020-12-01 12:08

    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

    0 讨论(0)
  • 2020-12-01 12:16

    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!

    0 讨论(0)
  • 2020-12-01 12:22

    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.

    0 讨论(0)
  • 2020-12-01 12:25

    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
    }
    
    0 讨论(0)
提交回复
热议问题