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

Как включить предупреждение компилятора при сравнении char и unsigned char?

возьмите, например, следующий код C:

int main(int argc, char *argv[])
{
    signed char i;
    unsigned char count = 0xFF;

    for (i=0; i<count;i++)
    {
        printf("%x\n", i);
    }
    return 0;
}

Этот код работает в бесконечном цикле, даже если я его скомпилирую следующим образом:

# gcc -Wall -Wpedantic -Wconversion -Wsign-compare -Wtype-limits -Wsign-conversion test.c -o test

Знает ли кто-нибудь флаг компилятора, который должен предупреждать об этих проблемах?

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

4b9b3361

Ответ 1

i - это signed char, приращение его за пределами SCHAR_MAX имеет эффект, определенный реализацией. Вычисление i + 1 выполняется после продвижения i до int, и оно не переполняется (кроме sizeof(int) == 1 и SCHAR_MAX == INT_MAX). Тем не менее это значение выходит за пределы диапазона i, и поскольку i имеет подписанный тип, либо результат определяется реализацией, либо генерируется сигнал, определяемый реализацией. (C11 6.3.1.3p3 Целочисленные и беззнаковые целые числа).

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

Обратите внимание, что clang также не обнаруживает константный тест, но clang 3.9.0 будет, если count объявлен как const, и он выдает предупреждение, если i < count заменяется на i < 0xff, в отличие от gcc.

Ни один компилятор не жалуется на проблему с подписанным/неподписанным сравнением, потому что перед сравнением оба операнда на самом деле продвигаются до int.

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

Ответ 2

Ошибка -Wconversion не будет ловить ошибку, потому что оба операнда в сравнении: i<count, получают повышение до int с помощью целых рекламных акций.

В gcc нет флага, который бы поймал это.

В стороне, поведение вашего кода undefined, потому что переменная я переполняется, когда она имеет значение 0x7F и увеличивается: i++.

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

Ответ 3

Так как i является signed char, его значение обычно варьируется от -128 to 127. В то время как count, являющееся unsigned char, присваивается значение 255 (0xFF).

Внутри цикла, когда значение i получает значение 127 и снова увеличивается, оно никогда не достигает 128 и переходит на -128, а затем снова возвращается к 127, а затем снова переходит на -128 и т.д. Значение i будет навсегда меньше, чем значение count внутри цикла, и поэтому цикл никогда не может завершиться!

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

EDIT: Из документации GCC,

-Wconversion:     Предупреждать о неявных преобразованиях, которые могут изменить значение.

Здесь мы получаем несогласованность из-за сравнения и не назначения.

Ответ 4

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

  • Как указано в других ответах, сравнительная линия эффективно рассматривается как

    for (i=0; (int)i<(int)count; i++)
    
    • Как описано в Обоснование для языков международного стандартного программирования-C, раздел 6.3.1.1, они выбрали "сохранение значения" подход для неявных преобразований типов при сравнении значений, поскольку он имеет меньше возможностей для неожиданных результатов сравнения.

      • Обратите внимание, что ваша программа работает некорректно, потому что результат арифметического сравнения является ожидаемым.
    • Если бы это были int s, то семантика "сохранение значения" была бы невозможна, потому что int - это, как правило, аппаратный тип (или, по крайней мере, C с учетом этого), и существует несколько (если есть) архитектур, которые позволяют подписать один операнд, а другой - без знака. Это основная причина, по которой компилятор выдает предупреждение "сравнение между подписанным и неподписанным" , если вы замените char на int здесь.

  • Для операции инкремента i++,

    • то же самое вышеописанное обоснование упоминает в нескольких местах, что "молчаливое переполнение" на сегодняшний день является наиболее распространенной и ожидаемой семантикой. Он даже заявил, что это вызвало "отсутствие чувствительности в сообществе C к различиям между подписанной и беззнаковой арифметикой", в первую очередь! Итак, ничего подозрительного здесь тоже.

В конечном счете, что вызвало эту путаницу, это ваше игнорирование семантики продвижения int. Который вызвал поведение кода неожиданно для вас (не чувствую себя плохо, я не знал этого, прежде чем читать другие ответы!). Тем не менее, оказывается, это вполне ожидаемый для стандарта - более того, продиктованный им. "Незнание закона не является оправданием", как говорится.