Компилятор не обнаруживает явно неинициализированную переменную



все компиляторы C, которые я пробовал, не будут обнаруживать неинициализированные переменные в приведенном ниже фрагменте кода. Тем не менее, случай здесь очевиден.



не беспокойтесь о функциональности этого фрагмента. Это не настоящий код, и я снял его для расследования этого вопроса.



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.
}


С другой стороны, если я закомментировать tauxtrouve = value ;, Я "local variable 'tauxtrouve' used without having been initialized" предупреждение.



Я пробовал эти компиляторы:





  • GCC 4.9.2 с -стена -WExtra

  • Microsoft Visual C++ 2013 со всеми включенными предупреждениями

460   4  
c

4 ответов:

очевидности, с которой эта переменная не инициализируется завышена. Анализ пути стоит времени, и ваши поставщики компиляторов либо не хотели реализовывать эту функцию, либо думали, что это будет стоить вам слишком много времени, или вы просто не выбрали явно.

например,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.

разница во времени выполнения для этих наивных примеров незначительна. Но представьте себе единицу перевода с тысячами строк, десятками функций, каждая из которых имеет петли и тяжелое гнездование. Количество путей быстро соединяется и становится большой нагрузкой для анализа того, будет ли первая итерация через цикл происходить ли назначение до этого сравнения.


EDIT: @Matthieu указывает, что с LLVM/clang анализ пути, необходимый для поиска использования неинициализированного значения, не усложняется по мере увеличения вложенности из-за нотации SSA, используемой IR.

это не так просто, как "-S -emit-llvm" как я надеялся, но я нашел выход SSA-нотации, который он описал. Я буду честен, я недостаточно знаком с LLVM IR, чтобы быть уверенным, но я поверю Матье на слово.

итог: использовать clang С --analyze, или убедить кого-то, чтобы исправить gcc ошибка.

; 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
}

Да, это должно вызвать предупреждение об этой неинициализированной переменной, но это ошибка GCC. Приведенный там пример:

unsigned bmp_iter_set ();
int something (void);

void bitmap_print_value_set (void)
{
    unsigned first;

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

с диагнозом -O2 -W -Wall.

к сожалению, в этом году 10-летний юбилей этой ошибки!

этот ответ касается только GCC.

после дальнейшего расследования и комментариев, происходит больше, чем в моем предыдущем ответе. Этот фрагмент кода имеет две неинициализированные переменные, и каждая из них не обнаружена по другой причине.

прежде всего,документация GCC на говорит:

поскольку эти предупреждения зависят от оптимизации, точных переменных или элементов, для которых существуют предупреждения зависит от точных параметров оптимизации и используемой версии GCC.

предыдущие версии руководства GCC сформулировали это более явно. Вот отрывок из руководство для GCC 3.3.6:

эти предупреждения возможны только при оптимизации компиляции, поскольку они требуют информации о потоке данных, которая вычисляется только при оптимизации. Если вы не укажете-O, вы просто не получите эти предупреждения.

It кажется, текущая версия может дать некоторые предупреждения без неинициализированных переменных без -O, но вы все равно получите гораздо лучшие результаты с ним.

если я скомпилирую ваш пример с помощью gcc -std=c99 -Wall -O, Я:

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
                     ^

(обратите внимание, что это с GCC 4.8.2, поскольку у меня нет 4.9.x установлен, но принцип должен быть таким же.)

так, что обнаруживает тот факт, что tauxtrouve инициализирован.

однако, если мы частично исправим код, добавив инициализатор для tauxtrouve (но не totaldiff), то gcc -std=c99 -Wall -O принимает его без каких-либо предупреждений. Это, по-видимому, является экземпляром" ошибки", приведенной в haccks это.

есть некоторые вопросы относительно того, следует ли это действительно считать ошибкой: GCC не обещает поймать каждый возможный экземпляр неинициализированной переменной. Действительно, он не может сделать это с идеальной точностью, потому что это проблема останова. Поэтому такие предупреждения могут быть полезны, когда они работают, но отсутствие предупреждений не доказывает, что ваш код свободен от неинициализированных переменных! Они действительно не являются заменой для тщательной проверки Вашего собственного кода.

на отчет об ошибке, связанный с haccks, существует много дискуссий о том, является ли ошибка даже исправимой, или попытка обнаружить эту конкретную конструкцию приведет к неприемлемой ложной положительной скорости для другого правильного кода.

Майкл, я не знаю, какую версию Visual Studio 2013 Вы пробовали, но она, безусловно, устарела. Visual Studio 2013 Update 4 правильно выдает следующее сообщение об ошибке при первом использовании totaldiff:

error C4700: uninitialized local variable 'totaldiff' used

вы должны рассмотреть возможность обновления рабочей среды.

кстати, вот что я вижу прямо в Редакторе:

Visual Studio 2013 caught the error

Comments

    Ничего не найдено.