Может ли разница типов между константами 32768 и 0x8000 иметь значение?

Стандарт определяет, что шестнадцатеричные константы, такие как 0x8000 (больше, чем помещается в целое число со знаком), являются беззнаковыми (точно так же, как восьмеричные константы), тогда как десятичные константы, такие как 32768, имеют длинный знак. (Точные типы предполагают 16-битное целое число и 32-битное длинное.) Однако в обычных средах C оба будут иметь одинаковое представление в двоичном формате 1000 0000 0000 0000. Возможна ли ситуация, когда это различие действительно приводит к другому результату? Другими словами, возможна ли ситуация, когда эта разница вообще имеет значение?


person Johan Bezem    schedule 09.11.2011    source источник
comment
Я могу представить себе ситуацию, когда вы пытались вычесть еще большее число из 0x8000, и это не сработало, как ожидалось, потому что оно беззнаковое. Но на самом деле это вряд ли произойдет.   -  person Seth Carnegie    schedule 09.11.2011
comment
@ user786653: Да, именно там, и в таблице на следующей странице у вас есть два столбца, в которых различаются десятичные константы и шестнадцатеричные и восьмеричные константы, где шестнадцатеричные и восьмеричные константы (без суффикса) также имеют беззнаковые варианты, в отличие от десятичные константы. (Комментарий удален; см. open-std.org/JTC1 /SC22/WG14/www/docs/n1256.pdf стр. 55f.)   -  person Johan Bezem    schedule 09.11.2011
comment
@JohanBezem: Да, извините, мне нужно было отредактировать свой комментарий, когда я понял, что был идиотом, вместо того, чтобы удалить его.   -  person user786653    schedule 09.11.2011


Ответы (5)


Да, это может иметь значение. Если ваш процессор имеет 16-битный int и 32-битный long тип, 32768 имеет тип long (поскольку 32767 является наибольшим положительным значением, подходящим для 16-битного int со знаком), тогда как 0x8000 (поскольку он также рассматривается для unsigned int ) по-прежнему умещается в 16-битном unsigned int.

Теперь рассмотрим следующую программу:

int main(int argc, char *argv[])
{
  volatile long long_dec = ((long)~32768);
  volatile long long_hex = ((long)~0x8000);

  return 0;
}

Когда 32768 считается long, отрицание инвертирует 32 бита, в результате получается представление 0xFFFF7FFF с типом long; гипс лишний. Когда 0x8000 считается unsigned int, отрицание инвертирует 16 бит, в результате получается представление 0x7FFF с типом unsigned int; затем приведение к нулю расширится до long значения 0x00007FFF. Взгляните на H & S5, раздел 2.7.1, стр. 24ff.

Лучше всего дополнять константы U, UL или L в зависимости от ситуации.

person Johan Bezem    schedule 09.11.2011
comment
void main? Ты должно быть шутишь. - person ; 09.11.2011
comment
@Fanael: Извини, да, ты прав. Спасибо за исправление! +1 Но во встроенных системах мы часто используем void, поскольку нет среды, в которую можно было бы вернуть int. - person Johan Bezem; 09.11.2011
comment
@OliCharlesworth: для размещенной реализации void main допустимо только в том случае, если реализация явно поддерживает и документирует его; в противном случае это делает поведение программы неопределенным. Для автономных реализаций точка входа определяется реализацией; опять же, реализация должна решить, действительно ли void main. Но int main(void) всегда действителен для размещенных реализаций, и нет разумной причины не использовать его или int main(int argc, char *argv) или аналогичные. - person Keith Thompson; 09.11.2011
comment
@KeithThompson Верно. Но вы пропустили вторую звездочку: char * * argv ;-) - person Johan Bezem; 09.11.2011
comment
@JohanBezem: Ой! Но на самом деле я хотел написать char *argv[] (что в качестве параметра эквивалентно char **argv). - person Keith Thompson; 09.11.2011
comment
@ Кит: Согласен. Меня просто раздражает, когда кто-то пытается прокомментировать void main, когда кто-то использовал его в каком-то случайном фрагменте кода, как в этом случае! - person Oliver Charlesworth; 10.11.2011
comment
@OliCharlesworth: Серьезно, мой совет - избавиться от раздражения. void main - очень распространенная ошибка - и да, в большинстве случаев это ошибка. Обычно это появляется в вопросах новичков, и новички обычно используют размещенные реализации. Указывать на него - это хорошо. (Обычно это означает, что программист учился на плохой книге, написанной автором, который должен был знать лучше.) - person Keith Thompson; 10.11.2011
comment
@KeithThompson И я больше не считаю себя новичком, использую C с 1984 года ... :-) - person Johan Bezem; 10.11.2011
comment
Измените его на void blah (void) и полностью избегайте проблемы, продолжая при этом избегать необходимости в лишних операторах возврата. - person supercat; 21.11.2011
comment
Я только что отредактировал ответ, чтобы он относился к int, а не к integer. int - один из нескольких целочисленных типов. - person Keith Thompson; 21.11.2011
comment
@KeithThompson Хотя изменение целого числа на int допустимо, если оно абсолютно незначительно, добавление форматирования для каждого элемента, возможно, встречающегося в исходном коде, чтобы показать форматирование исходного кода, отрицательно влияет на удобочитаемость IMHO из-за серого цвета фона. - person Johan Bezem; 21.11.2011
comment
@JohanBezem: На мой взгляд, изменение целого числа на int жизненно важно. Слово целое число относится ко всем типам от char до long long; int - это конкретный тип, о котором вы на самом деле говорили. Что касается форматирования, то мне оно нравится больше, чем без него, но я понимаю вашу точку зрения, и если вы захотите вернуть его обратно, я не буду возражать. - person Keith Thompson; 21.11.2011
comment
Я изменил форматирование констант, но оставил его для типов C. Для меня «витал» слишком силен, даже если я в целом с вами согласен. Если мы говорим о процессоре с 16-битным целым числом, никто не будет считать, что его char тип - да и его long тип, если на то пошло, - 16-битный ... А вы? - person Johan Bezem; 21.11.2011

На 32-битной платформе с 64-битной long, a и b в следующем коде будут иметь разные значения:

int x = 2;
long a = x * 0x80000000; /* multiplication done in unsigned -> 0           */
long b = x * 2147483648; /* multiplication done in long     -> 0x100000000 */
person undur_gongor    schedule 09.11.2011
comment
Между прочим, есть неприятная ошибка, связанная с вышеизложенным: если long x;, как эффект x &= ~0x80000000; по сравнению с эффектом x &= ~0x100000000; или x &= ~0x40000000;? - person supercat; 28.06.2012

Еще одно исследование, которое еще не дано: сравните (с операторами больше или меньше) -1 как с 32768, так и с 0x8000. Или, если на то пошло, попробуйте сравнить каждый из них на равенство с переменной int, равной -32768.

person supercat    schedule 20.11.2011
comment
Имейте в виду, что -1 - это постоянная «единица» с унарным минусом. Применяются обычные унарные и двоичные преобразования, поэтому вы не увидите никакого любопытного поведения ни в одном из ваших случаев, ИМХО. - person Johan Bezem; 21.11.2011
comment
@JohanBezem: это константа с унарным минусом, но результат унарного минуса будет иметь тип (подписанный) int. Сравнение -1 с 32768 даст сравнение со знаком между длинным значением -1 и длинным значением 32768. Сравнение -1 с 0x8000 даст беззнаковое сравнение между 0xFFFF и 0x8000. Невозможно определить литерал int, равный -32768, поэтому я указал переменную. - person supercat; 21.11.2011
comment
О, я вижу. Да, вы правы, но необходимые преобразования вне диапазона целевого типа технически не определены AFAIK (H & S5 6.2.3), даже если большинство / все практические реализации будут использовать дополнение до двух и давать «любопытные» результаты. Спасибо! - person Johan Bezem; 21.11.2011
comment
@JohanBezem: Какие конверсии не определены? Преобразование -1 в unsigned int требуется для получения значения unsigned int, которое при добавлении к 1 даст ноль. Точно так же, если реализация допускает существование переменной int, равной -32768, преобразование ее в unsigned int должно дать значение unsigned int, которое при добавлении к 32768 даст ноль. Дополнение до двух не имеет к этому ничего. - person supercat; 29.07.2015

Предположим, что int - 16 бит, а long - 32 бита (что на самом деле довольно необычно в наши дни; int чаще всего 32 бита):

printf("%ld\n", 32768);  // prints "32768"
printf("%ld\n", 0x8000); // has undefined behavior

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

person Keith Thompson    schedule 20.11.2011
comment
Сегодня существует множество встроенных процессоров с 16-битным int, которые активно используются в разработке. Таким образом, ваш первый printf использует длинную целочисленную константу, напечатанную как длинное десятичное значение, без проблем (как вы указали); ИМХО второй printf тоже не проблема, так как преобразование из unsigned int в signed long определено и сохраняет значение. Так что, на мой взгляд, во втором выражении нет неопределенного поведения. - person Johan Bezem; 21.11.2011
comment
@JohanBezem: Хороший отзыв о встроенных процессорах. Аргументы с переменным числом аргументов (т. Е. Аргументы, соответствующие , ... в объявлении функции) не преобразуются, за исключением продвижений аргументов по умолчанию, поскольку компилятор не знает, что ожидается long int. Для вызова printf, если тип продвинутого аргумента не соответствует типу, заданному форматом, поведение не определено; C99 7.19.6.1p9 говорит об этом прямо. - person Keith Thompson; 21.11.2011

Разница была бы в том, что если бы вы попытались добавить значение к 16-битному int, он не смог бы этого сделать, потому что он выйдет за границы переменной, тогда как если бы вы использовали 32-битную длину, вы могли бы добавить любое число, которое менее 2 ^ 16 к нему.

person yoyomommy    schedule 09.11.2011