Почему GCC не выдает предупреждение при присвоении знакового литерала беззнаковому типу?

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

/* foo.c */
#include <stdio.h>
int main(void)
{
    unsigned int x=20, y=-30;
    if (x > y) {
        printf("%d > %d\n", x, y);
    } else {
        printf("%d <= %d\n", x, y);
    }
    return 0;
}

Компиляция с GCC 4.2.1, как показано ниже, не приводит к выводу на консоль:

gcc -Werror -Wall -Wextra -pedantic foo.c -o foo

Полученный исполняемый файл генерирует следующий вывод:

$ ./foo
20 <= -30

Есть ли какая-то причина, по которой GCC не генерирует никаких предупреждений или сообщений об ошибках при присвоении знакового значения -30 целочисленной переменной без знака y?


person maerics    schedule 05.05.2010    source источник
comment
Обратите внимание, что вы должны использовать %u для печати unsigned int.   -  person Bastien Léonard    schedule 05.05.2010
comment
@ Бастьен, действительно; возможно, более интересно то, что, как указал AndreyT в своем ответе ниже, printf("%d") показывает значение со знаком вместо значения без знака (4294967266), которое используется оператором сравнения!   -  person maerics    schedule 05.05.2010
comment
Это связано с представлением подписанного int в дополнении 2: en.wikipedia.org/wiki/Two%27s_complement (это представление не гарантируется стандартом C, но на практике я никогда не слышал о другом представлении, которое все еще используется)   -  person Bastien Léonard    schedule 05.05.2010


Ответы (3)


Используйте -Wconversion:

~/src> gcc -Wconversion -Werror -Wall -Wextra -pedantic -o signwarn signwarn.c
cc1: warnings being treated as errors
signwarn.c: In function 'main':
signwarn.c:5: error: negative integer implicitly converted to unsigned type

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

person unwind    schedule 05.05.2010
comment
Отлично, спасибо! Заставляет меня желать, чтобы существовал Wallall ;) - person niCk cAMel; 26.03.2018
comment
Обратите внимание, что -Wsign-conversion не включено по умолчанию в -Wconversion в C++, поэтому лучше всегда добавлять его явно. - person yugr; 22.02.2020

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

Что касается того, что выводит ваша программа... Использование спецификатора формата %d для printf со значением без знака, лежащим за пределами диапазона типа int, приводит к неопределенному поведению, которое вы действительно наблюдали в своем эксперименте.

person AnT    schedule 05.05.2010
comment
Эй, хороший улов. printf("%d", y); находится в совершенно другой категории, чем y=-30;. - person Pascal Cuoq; 05.05.2010
comment
Отлично, я мог представить, например, использование отрицательного значения для создания желаемого битового поля. - person maerics; 05.05.2010
comment
Да, преобразование отрицательных # в беззнаковые определено для получения результата «по модулю» размера беззнакового целого числа, поэтому (без знака)-1 - это совершенно хороший способ сказать без знака со всеми 1 в нем. Когда числа со знаком представлены с дополнением до 2, это «преобразование» представляет собой просто тот же битовый шаблон, что и знаковый. - person greggo; 04.04.2014

Использование (unsigned)-1 часто используется для установки всех битов и иногда даже цитируется как причина этой (неправильной) функции C даже людьми, которые должны знать лучше. Это не очевидно и не переносимо — выражение, которое вы хотите использовать для установки всех битов, равно ~0.

person GuentherT    schedule 17.08.2016
comment
Я считаю значение uint32_t x=-1;` очевидным, Стандарт определяет точное поведение, и он более переносим, ​​чем uint32_t x=~0; или uint32_t x=~0u; [в системе знаковых величин форма ~0 установила бы x в 0xFFFF8001ul; в любой 16-битной системе форма ~0u установит для x значение 0x0000FFFFul; форма с использованием -1 будет работать в любой системе, которая определяет uint32_t.] - person supercat; 18.08.2016