Подтвердить что ты не робот

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

Все компиляторы 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 с -Wall -WExtra
  • Microsoft Visual С++ 2013 с включенными предупреждениями
4b9b3361

Ответ 1

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

Например, с 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
}

Ответ 2

Да, это должно вызвать предупреждение об этой неинициализированной переменной, но ошибка 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 лет с этой ошибки!

Ответ 3

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

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

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

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

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

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

Кажется, что текущая версия может давать некоторые предупреждения без неинициализированных переменных без -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 answer.

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

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

Ответ 4

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

error C4700: uninitialized local variable 'totaldiff' used

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

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

Visual Studio 2013 caught the error