Предупреждение «Неинициализированное использование» в компиляторе g ++ - PullRequest
8 голосов
/ 06 февраля 2011

Я использую g ++ с уровнем предупреждения -Wall -Wextra и отношу предупреждения к ошибкам (-Werror).

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

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

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

Точная ошибка:

'cmpres' может использоваться неинициализированным в этой функции

И я пометил строку с ошибкой * ниже.

for (; ;) {
    int cmpres; // *
    while (b <= c and (cmpres = cmp(b, pivot)) <= 0) {
        if (cmpres == 0)
            ::std::iter_swap(a++, b);
        ++b;
    }
    while (c >= b and (cmpres = cmp(c, pivot)) >= 0) {
        if (cmpres == 0)
            ::std::iter_swap(d--, c);
        --c;
    }
    if (b > c) break;
    ::std::iter_swap(b++, c--);
}

(cmp - это функтор, который принимает два указателя x и y и возвращает -1, 0 или +1, если *x < *y, *x == *y или *x > *y соответственно. Остальные переменные являются указателями на тот же массив.)

Этот фрагмент кода является частью более крупной функции, но используется переменная cmpres , больше нигде . Поэтому я не понимаю, почему генерируется это предупреждение. Более того, компилятор, очевидно, понимает , что cmpres никогда не будет читаться неинициализированным (или, по крайней мере, он не всегда предупреждает, см. Выше).

Теперь у меня есть два вопроса:

  1. Почему противоречивое поведение? Это предупреждение генерируется эвристикой? (Это правдоподобно, поскольку для выдачи этого предупреждения требуется анализ потока управления, который в общем случае является NP-сложным и не всегда может быть выполнен.)

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

Ответы [ 4 ]

13 голосов
/ 06 февраля 2011

Алгоритм, который диагностирует неинициализированные переменные без ложных негативов или позитивов, должен (в качестве подпрограммы) включать алгоритм, который решает проблему остановки .Что означает , такого алгоритма нет . невозможно для компьютера, чтобы сделать это правильно 100% времени.

Я не знаю, как именно работает анализ неинициализированных переменных GCC, но я знаю, что он очень чувствителен к тому, чторанние этапы оптимизации сделали с кодом.Так что я совсем не удивлен, что иногда вы получаете ложные срабатывания.Он отличает случаи, когда он определен, от случаев, когда он не может быть определен -

int foo() { int a; return a; }

выдает «предупреждение:« a » используется неинициализированным в этой функции» (выделено мое).

РЕДАКТИРОВАТЬ: Я обнаружил случай, когда последние версии GCC (4.3 и более поздние версии) не удалось диагностировать неинициализированную переменную:

int foo(int x)
{
    int a;
    return x ? a : 0;
}

Ранние оптимизации отмечают, что если x не равен нулю, поведение функции не определено, поэтому они предполагают, что x должно быть равно нулю и заменяют весь текст функции на "return 0;". Это происходитзадолго до прохода, который генерирует использованные неинициализированные предупреждения, поэтому диагностики нет.См. ошибка GCC 18501 для подробностей.

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

3 голосов
/ 06 февраля 2011

На этой неделе состоялось интересное обсуждение списка рассылки clang, связанного с этими эвристиками.

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

По-видимому (из обсуждения), gcc использует базовый подход предикатов, но, учитывая ваш опыт, кажется, что этого не всегда достаточно.

Я подозреваю, что это как-то связано с тем, что назначениесмешанный в пределах условия (и после оператора короткого замыкания в этом ...).Вы пробовали без него?

Я думаю, что этот пример очень заинтересовал бы и людей gcc, и clang, так как это довольно распространенная практика в C или C ++ и, таким образом, он мог бы извлечь пользу из некоторой настройки.

2 голосов
/ 06 февраля 2011

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

1 голос
/ 06 февраля 2011

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

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

...