Несоответствие знака/беззнака при сравнении двух значений без знака с использованием условного оператора

У меня есть следующий код C:

unsigned int a;
unsigned char b, c;
void test(void) {
    if (a < b)
        return;
    if (a < (b ? b : c))
        return;
}

Когда я компилирую его (с Microsoft cl, из MS SDK 7, уровень предупреждения -W3), второе сравнение выдает предупреждение: C4018, несоответствие подписанного/неподписанного. Первое сравнение не выдает предупреждения.

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

UPD: протестировано с gcc -Wall -Wextra -pedantic и не получило никаких предупреждений.


person Mikhail Edoshin    schedule 11.10.2012    source источник
comment
Все ли предупреждения компилятора включены в настройках вашего компилятора?   -  person cowboydan    schedule 11.10.2012
comment
да, неудобная ошибка/фича. Я просмотрел это и бросил, если ВК пожалуется, и больше не думай об этом.   -  person Peter Miehle    schedule 11.10.2012
comment
@cowboydan Да, -W3. Если я не укажу уровень предупреждения, то предупреждения вообще не будет.   -  person Mikhail Edoshin    schedule 11.10.2012
comment
gcc не выдает никаких предупреждений.   -  person Grijesh Chauhan    schedule 11.10.2012


Ответы (3)


Вероятно, это связано с правилами арифметического преобразования: во-первых, любой целочисленный тип ранга преобразования меньше int (например, unsigned char) будет повышаться до int или unsigned int.

Будет ли результатом быть int или unsigned int, зависит (напрямую) не от знака исходного типа, а от его диапазона: int используется даже для беззнаковых типов, если могут быть представлены все значения, что имеет место для unsigned char в основном архитектуры.

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

Семантически ваши выражения читаются

a < (unsigned int)(int)b

а также

a < (unsigned int)(b ? (int)b : (int)c)

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

Комментарий Стива Джессопа прекрасно объясняет, как это могло произойти:

Я предполагаю, что в первом случае компилятор думает: «У меня есть оператор сравнения, типы операндов которого unsigned int и unsigned char. Предупреждение не нужно, теперь применим продвижение с последующим обычным преобразованием».

Во втором случае он думает: «У меня есть оператор сравнения, типы операндов которого unsigned int и int (которые я вывел как тип условного выражения в RHS). Лучше предупредить об этом!».

person Christoph    schedule 11.10.2012
comment
Строго говоря, второй случай эквивалентен a < (unsigned int)(int)(b ? (int)b : (int)c) из-за особого странного правила в условном операторе. 6.5.15/3 If both the second and third operands have arithmetic type, the result type that would be determined by the usual arithmetic conversions, were they applied to those two operands, is the type of the result. Хотя, конечно, в данном конкретном случае это не имеет значения для исхода. - person Lundin; 11.10.2012
comment
@Lundin: я действительно думал о включении этого преобразования (а также о продвижении первого b), но отказался от него по учебным причинам; ваш ответ действительно более полный, поэтому +1 от меня - person Christoph; 11.10.2012
comment
Я бы предположил, что в первом случае компилятор думает, что у меня есть операнд сравнения, типы которого unsigned int и unsigned char. Предупреждать не надо, теперь применим раскрутку с обычной конверсией. Во втором случае он считает, что у меня есть операнд сравнения, типы которого unsigned int и int (которые я вывел как тип условного выражения в RHS). Лучше предупредить об этом!. - person Steve Jessop; 11.10.2012
comment
@SteveJessop: надеюсь, ты не возражаешь, что я украл твое красивое объяснение - person Christoph; 11.10.2012
comment
@SteveJessop На самом деле это может быть так, потому что компилятор не обязан выполнять все неявные повышения, если он может сказать, что они не повлияют на результат выражения. Предположим, он транслирует этот код непосредственно в show a и b в 32-битные регистры ЦП, а затем сравнит. - person Lundin; 11.10.2012
comment
@Christoph: совсем не против, хотя я дважды опечатал операнд вместо оператора! Может быть, скажем, оператор сравнения, типы операндов которого... - person Steve Jessop; 11.10.2012

if (a < b) равно псевдоif (unsigned int < unsigned char).

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

if (unsigned int < int).

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

Итак, ваше первое выражение преобразуется в

if (unsigned int < unsigned int)

прежде чем что-либо будет сделано.


Во втором выражении у нас есть if (a < (b ? b : c)), что равно псевдо

if (unsigned int < (unsigned char ? unsigned char : unsigned char)).

Целочисленные преобразования выполняются для всех символов, поэтому они неявно преобразуются в

if (unsigned int < (int ? int : int)).

Затем странное, неясное правило для условного оператора диктует, что 2-й и 3-й операторы оператора ?: должны быть сбалансированы с обычными арифметическими преобразованиями. В этом случае они уже однотипные, так что ничего не происходит. Мы заканчиваем с

if (unsigned int < int)

Балансировка происходит снова, результат будет оцениваться как

if (unsigned int < unsigned int)


Когда я скомпилирую его с помощью Microsoft

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

person Lundin    schedule 11.10.2012

Правила различаются между C и C++. Подходящий стандарт может быть трудно определить при компиляции C с помощью MSVC, но, к счастью, в этом случае C89 и C99 совпадают.

In C89 3.3.15:

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

In C99 6.5.15/5:

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

In C++03 5.16/4:

Если второй и третий операнды являются lvalue и имеют один и тот же тип, результат имеет этот тип и является lvalue

Поэтому, когда вы говорите: «Если оба операнда одного типа, результат будет одного типа, поэтому он должен работать как первое сравнение», это применимо только к C++, а не к C. В C тип операнда Правая сторона этого сравнения — int. В C++ RHS будет lvalue типа unsigned char, как вы и ожидали.

person Community    schedule 11.10.2012
comment
не будет ли целочисленное продвижение применяться даже в C++ после преобразования lvalue-to-rvalue? - person Christoph; 11.10.2012
comment
@Кристоф: Да. В C++ типы операндов < будут unsigned int и unsigned char. Затем к операндам применяются продвижение и преобразование. В C типы операндов < - это unsigned int и int, тогда к операндам применяются продвижение и преобразование. - person Steve Jessop; 11.10.2012
comment
На самом деле мой ответ, вероятно, должен быть комментарием. Это говорит о том, что если оба операнда одного типа, результат будет одного типа ... я что-то упустил? (взято из документов MS), а не подразумеваемый фактический вопрос, почему я получаю это предупреждение? Но нет никакого способа поместить эти стандартные цитаты в комментарий, и поскольку единственный вопрос, который вы на самом деле задаете, это я что-то упустил? тогда ответ на ваш вопрос - да ;-) - person Steve Jessop; 11.10.2012
comment
@SteveJessop Вы абсолютно правы; если я скомпилирую его в режиме C++ (с -Tp), я не получу здесь предупреждения. Спасибо! - person Mikhail Edoshin; 11.10.2012