2014-11-21 3 views
58

Все компиляторы 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 C++ 2013 с все предупреждения включен
+2

Я понятия не имею, но, возможно, его оптимизация компилятора? Я также хочу знать. Надеюсь, мы скоро ответим. –

+0

Что произойдет, если вы добавите флаг '-pedantic' в gcc? – avgvstvs

+1

Я не знаком с тем, как тестируется инициализация, но проверьте https://gcc.gnu.org/wiki/Better_Uninitialized_Warnings, которые я нашел при поиске по Google. Особенно «CCP принимает значение для неинициализированных переменных». Если бы я был уверен в себе, я бы сделал этот комментарий и ответ. –

ответ

65

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

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

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


РЕДАКТИРОВАТЬ: @Matthieu указывает на то, что с LLVM/звоном, анализ путей, необходимых для найти применение-в-неинициализированным значения не соединение в виде вложенности увеличивается из обозначений 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 
} 
+7

У вас есть цифры относительно деградации? Для вашей информации LLVM IR основан на нотации SSA, которая, как следует из названия, допускает только одно назначение для любой отдельной переменной (часто имя переменной в исходном коде повторно используется с индексом). В результате, пути * обязательно * материализованы, и проверка того, потенциально ли используется переменная, неинициализированная, таким образом, тривиальна: для каждой переменной при вычислении ее назначения проверяйте, является ли она определенно инициализированной, потенциально неинициализированной или определенно неинициализированной. При использовании предоставьте соответствующее предупреждение. –

+1

@Matthieu О, действительно, далеко не так тривиально, как можно было бы подумать. [См. Здесь] (https://gist.github.com/voo42/293d3cafa820f8c86e54 для простого примера, где ваше предложение будет давать ложные предупреждения. Конечно, выполнимо, но просто использовать форму SSA недостаточно. На самом деле я не уверен, есть ли общее решение, которое не включает проверку всех возможных путей с помощью функции. – Voo

+1

@ Voo нет ни одного, который не включает запуск в HP. Рассмотрим 'if (foo (0)) {i = 0} if (bar (0)) {j = i} '. Но для этого требуется, чтобы' bar (0) => foo (0) ', поэтому нам нужно знать значения' bar (0) 'и' foo (0) 'оценивается как - который невозможен без фактического запуска программы (и надеется, что она прекратится) из-за HP. –

50

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

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 лет с этой ошибки!

+4

Geez .. десятилетняя ошибка со списком комментариев, который читается как воплощение худших опасений «snark-iness» в проекте OSS –

+3

@PeterM; К сожалению, эта 10-летняя ошибка еще не исправлена! – haccks

+3

#WontFix #NotSexyEnoughToWorryAbout –

11

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

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

Прежде всего, GCC documentation для варианта -Wuninitialized говорит:

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

В предыдущих версиях руководства GCC это более четко указано. Вот отрывок из manual for 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's answer.

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

В bug report linked by haccks обсуждается вопрос о том, является ли ошибка даже фиксированной, или если попытка обнаружить эту конкретную конструкцию приведет к неприемлемой ложной положительной скорости для другого правильного кода.

+1

Я считаю, что OP больше беспокоится о том, что «totaldiff» не инициализируется. –

+0

@PeterM: Хорошая точка. Здесь есть два отдельных вопроса, и я переписал свой ответ, чтобы обратиться к обоим. –

2

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

error C4700: uninitialized local variable 'totaldiff' used 

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

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

Visual Studio 2013 caught the error