Clang -Wconditional-неинициализированный при назначении массива члена структуры - PullRequest
0 голосов
/ 06 сентября 2018

Я столкнулся с каким-то необычным поведением, связанным с Клангом -Wconditional-uninitialized. Рассмотрим следующий пример:

typedef struct { int x[1]; } test_t;
test_t foo(void);
test_t foo() {
    test_t t;
    for(int i = 0; i < 1; i++) t.x[i] = 0;
    return t;
}
int main() { }

Компиляция, например,

clang -o test -Weverything test.c

выдает следующие предупреждения:

test.c:6:12: warning: variable 't' may be uninitialized when used here [-Wconditional-uninitialized]
    return t;
           ^
test.c:4:5: note: variable 't' is declared here
    test_t t;
    ^
1 warning generated.

Однако, переключение линии:

    for(int i = 0; i < 1; i++) t.x[i] = 0;

с

    t.x[0] = 0;

не выдает предупреждений. Обратите внимание, что достаточно использовать -Wconditional-uninitialized и не обязательно передавать -Weverything. Оптимизированная сборка -O3 в обоих случаях идентична, с одним xorl %eax, %eax, испускаемым в обоих случаях. Это минимальный пример, который я мог бы создать, демонстрирующий такое поведение.

Я беру некоторые из выводов Кланга -Weverything с крошкой соли, но это мне кажется ошибкой, и я довольно запутался. Это вызывает какое-то неопределенное поведение, которое я вызываю, или это ложный положительный результат? Это хорошая или плохая идея - оставить включенным -Wconditional-uninitialized? Я вижу такое же поведение в Clang 3.8.1 в Linux и (Apple) 9.1.0 в macOS.

1 Ответ

0 голосов
/ 07 сентября 2018

Как предложено выше @R .. в комментариях, я сообщил об этом как ошибка LLVM 38856 . Видимо, по их словам:

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

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

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

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

#include <stdio.h>

typedef struct { int x[2]; } test_t;

test_t foo_3(void);

test_t foo_3() {
    test_t t;
    t.x[0] = 0;
    return t;
}

int main() {
    test_t t;

    t = foo_3();
    printf("%i %i\n", t.x[0], t.x[1]);
}

Который получил мне ответ:

Как я уже упоминал в комментарии № 1, анализ, который мы сейчас выполняем, не может определить разницу между записью в один элемент массива и записью во весь массив. В простых случаях это должно быть относительно просто улучшить, поэтому я думаю, что по этой причине эта ошибка допустима (но с низким приоритетом, учитывая, что это предупреждение будет иметь ложные срабатывания).

Итак, -Wconditional-uninitialized известен тем, что дает ложные срабатывания. По-видимому, статический анализатор не такой тщательный, как я ожидал. Я удивлен, что раньше не сталкивался ни с чем подобным, учитывая более сложные сценарии, в которых анализатор работал лучше, но, как отмечает @toohonestforthissite в комментариях выше, передача массивов по значению через структуры, подобные этой, не является стандартная вещь, чтобы сделать в C.

-Wno-conditional-uninitialized или #pragma clang diagnostic ignored "-Wconditional-uninitialized" должны заставить замолчать это предупреждение для тех, кто хочет использовать -Weverything в таком случае.

...