Есть ли в C11 механизм для обеспечения ожидаемых типов отрицательных целочисленных констант?

Я не могу найти нигде в стандарте C, который бы оправдал следующее:

int n = -0x80000000 // set n to -2^31

Предположим, что в реализации int - 32 бита. Очевидная проблема заключается в том, что целочисленная константа имеет тип unsigned int, как указано в таблице в проекте стандарта комитета в п. 6.4.4.1, параграф 5. Затем вычисляется отрицание в соответствии с 6.5.3.3 параграф 3:

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

Выполнение целочисленных рекламных акций не меняет тип (unsigned int остается unsigned int). Потом снимается негатив. Поскольку в результате сохраняется повышенный тип, он сокращается по модулю 2 ^ 32, получая 2 ^ 31 (поэтому отрицание не имеет никакого эффекта).

Назначение значения вне допустимого диапазона для типа int покрывается следующим:

6.3.1.3 Целые числа со знаком и без знака

1 Когда значение с целочисленным типом преобразуется в другой целочисленный тип, отличный от _Bool, если значение может быть представлено новым типом, оно не изменяется.

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

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

Итак, в конце концов, мы получаем поведение, определяемое реализацией, когда пытаемся присвоить действительное значение int объекту int (при условии, что 2-дополнение без представления ловушки).

Следующее будет стандартным, гарантированно дающим ожидаемый результат:

int n = -(long long)0x80000000 // set n to -2^31

Итак, вам действительно нужно выполнить приведение, чтобы правильно выполнить назначение в диапазоне, или мне что-то не хватает?


person Kyle    schedule 05.07.2016    source источник
comment
Примечание о стандарте гарантированно дает ожидаемый результат - ›отзыв int может быть 16-битным.   -  person chux - Reinstate Monica    schedule 06.07.2016
comment
Да, я предполагаю 32-битное int.   -  person Kyle    schedule 06.07.2016
comment
Проблема на самом деле проста, не используйте шестнадцатеричный код для описания значений, которые могут стать отрицательными. Шестнадцатеричные значения - хороший инструмент, когда вы хотите описать битовые шаблоны, чтобы их можно было использовать в качестве масок в битовых операциях и т.п. Когда вы хотите описать значения для знаковой арифметики, битовое представление не имеет значения.   -  person Jens Gustedt    schedule 06.07.2016
comment
Поздравляем, вы обнаружили, что ваш код int n = -0x80000000 не имеет смысла. Если вы напишете действительно странный код, вы также часто будете запускать действительно странное стандартное поведение C.   -  person Lundin    schedule 06.07.2016
comment
@Lundin, цель заключалась в том, чтобы подтвердить мое понимание того, что '-' ненадежно применяется к произвольным аргументам целочисленного типа, а не для записи этой конкретной строки. И макросы, использующие '-', не представляют собой действительно странного кода.   -  person Kyle    schedule 06.07.2016
comment
@Kyle Скорее, любой оператор ненадежен при применении к ненадежному целочисленному типу. Целочисленные литералы без каких-либо суффиксов, таких как L и / или U, могут иметь произвольный тип (произвольный размер и / или знак), в зависимости от их значения. Это вызвано несколько неясными методами, которые язык C использует для выбора типов для целочисленных литералов, а не унарным оператором -. Шестнадцатеричные литералы особенно неприятны: возьмите, например, эти 3 значения в 16-битной системе: 0x00007FFF, 0x00008000, 0x00010000. Все они будут иметь разные типы: int, unsigned int и long соответственно.   -  person Lundin    schedule 06.07.2016
comment
в качестве примечания, вы, возможно, помните (если у вас есть какой-либо фон машинного кода, изменение положительного числа на отрицательное число просто инвертирует все биты, а затем добавляет 1. Это для двух операций дополнения   -  person user3629249    schedule 07.07.2016
comment
компилятор изменит значение, включая литералы, через implicit conversion на тип целевой переменной   -  person user3629249    schedule 07.07.2016


Ответы (5)


Боюсь, вы заметили, что Назначение значения вне диапазона для типа int покрывается следующим: не применяется к

unsigned int n = -0x80000000 // set n to -2^31

n имеет тип unsigned int, а значение 2 ^ 31 не выходит за пределы диапазона для 32 бит unsigned int.

ИЗМЕНИТЬ: так как вы изменили вопрос и сделали n int, то 3 применяется для 32-битных и младших int, а комментарий неверен для больших int типов:

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

РЕДАКТИРОВАТЬ: второй фрагмент кода int n = -(long long)0x80000000 // set n to -2^31 верен, поскольку значение -2147483648 подходит для 32-битного int.

Обратите внимание, что правильный способ инициализировать n этим значением (при условии 32-битных целых чисел):

int n = -2147483647 - 1;  // set n to -2^31

Этот вариант используется в стандартных заголовках <limits.h> и <stdint.h>. Он не использует приведение типов, поэтому ответ на ваш последний вопрос: нет, вам действительно не нужно выполнять приведение, чтобы правильно выполнить присвоение диапазона от -2 ^ 31 до 32-битного int.

person chqrlie    schedule 05.07.2016
comment
Хорошая уловка, это была случайность, я исправил ее на тип int. - person Kyle; 06.07.2016
comment
@Kyle: второй фрагмент кода тоже использует unsigned int. Вычисление отличается, но результат идентичен и верен, если unsigned int имеет как минимум 32 бита значения. - person chqrlie; 06.07.2016

Если INT_MAX равно 0x7fffffff, то как шестнадцатеричный литерал 0x80000000 имеет тип unsigned int или больше, и применение - к нему безопасно. Это было бы неверно для десятичных литералов. Если бы INT_MAX было больше 0x7fffffff, тогда отрицание уже было бы безопасным как int.

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

person R.. GitHub STOP HELPING ICE    schedule 05.07.2016
comment
Да, применить - безопасно, но вопрос заключается в присвоении результата типу int. - person Kyle; 06.07.2016
comment
Мой ответ здесь не имел смысла без поправки, которую я только что внес в вопрос. Предполагается, что n имеет тип int, а не unsigned int. - person Kyle; 06.07.2016
comment
На практике это всегда определяется ... не совсем! Современные компиляторы выполняют противоречащие интуиции оптимизации в присутствии потенциального неопределенного поведения, которое может сделать это предположение с треском провалом. - person chqrlie; 06.07.2016
comment
@chqrlie: Это не неопределено. Это определяется реализацией. Как ясно объясняет мой ответ. Это требует, чтобы поведение было задокументировано и согласовано. - person R.. GitHub STOP HELPING ICE; 06.07.2016
comment
Otoh переполнения не определено, но переполнения здесь не происходит. Все, что происходит, - это преобразование, определяемое реализацией. - person R.. GitHub STOP HELPING ICE; 06.07.2016
comment
@R., Знаете ли вы о ссылке в стандарте для требований согласованности, определяемых реализацией? Реализация не может определить поведение, чтобы просто выдать допустимый результат (то есть не ловушку) или что-то в этом роде? - person Kyle; 06.07.2016
comment
@R .: Хорошо, я поправлюсь. Мое замечание может не применяться здесь, но применимо в аналогичных ситуациях, когда происходит переполнение. Преобразование, определяемое реализацией, доставляет почти такую ​​же боль, как и неопределенное поведение при попытке написать правильный код. - person chqrlie; 06.07.2016
comment
@chqrlie: Я бы не назвал это проблемой при попытке написать просто правильный код, только при попытке написать код, переносимый для намеренно непонятных реализаций. По крайней мере, тестирование показывает, что код с поведением, определяемым реализацией, делает то, что вы ожидаете. А в случае преобразования целочисленных значений вне диапазона в целочисленные типы со знаком, по сути, существует одно универсальное определение, согласованное всеми практическими реализациями: модульное сокращение до диапазона. - person R.. GitHub STOP HELPING ICE; 06.07.2016
comment
@R ..: Единственное, что делает вышеупомянутое преобразование безопасным, - это то, что нет других реализаций поведения, которые можно было бы эффективно заменить при соблюдении требований детерминированного поведения. С другой стороны, парадоксально, что Стандарт предписывает точное поведение в тех случаях, когда это приведет к увеличению затрат на многие реализации, но не определяет никаких поведенческих ограничений в случаях, которые часто были бы более полезными, но более дешевыми (пусть переполненные int вычисления дают результат значение, которое может вести себя недетерминированно как числа вне диапазона int, но без других побочных эффектов). - person supercat; 07.07.2016
comment
@supercat: Нет, все традиционные разработчики согласились и установили ожидание, что преобразованию будет дано очевидное, разумное определение, и поэтому любой компилятор-новичок, который попытался выбрать другое определение, будет непригоден для большинства практических целей и, таким образом, отклонен. Это тот же самый тип процесса консенсуса / установленной практики, который должен иметь место при стандартизации, за исключением того, что он происходил вне стандартного процесса, потому что WG14 по-прежнему хочет делать вид, что дополнение возможно. - person R.. GitHub STOP HELPING ICE; 07.07.2016
comment
@R ..: Неужели менее очевидно, что нормальные реализации на оборудовании с обходом без звука с дополнением до двух должны иметь поведение целочисленного переполнения, которое, по крайней мере, несколько ограничено? Авторы C89, похоже, так думали, но некоторые современные разработчики компиляторов не очень. - person supercat; 07.07.2016
comment
@supercat: Спецификация всегда делала различие, что переполнение не определено, тогда как преобразование значений вне диапазона определяется (реализацией). Я не вижу оснований утверждать, что авторы C89, предназначенного для переполнения, ограничены в своем поведении. В C89 3.3.6 это ясно сказано: Как и в случае любого другого арифметического переполнения, если результат не помещается в отведенное пространство, поведение не определено. - person R.. GitHub STOP HELPING ICE; 07.07.2016
comment
@R ..: Прочтите объяснение того, почему короткие неподписанные типы превращаются в подписанные. Мотивирующим фактором при принятии решения было поведение безмолвных реализаций дополнения до двух (которые, по мнению авторов, составляли большинство [на тот момент] текущих реализаций) в случаях, когда арифметическое значение результата будет между INT_MAX + 1u и UINT_MAX. Как вы думаете, почему они сказали бы об этом, если бы не считали такое поведение одновременно определенным и желательным? - person supercat; 07.07.2016
comment
@R ..: Авторы Стандарта явно не намеревались требовать какого-либо конкретного поведения на оборудовании, семантика переполнения которого может быть непредсказуемой, но это не означает, что они предполагали, что переполнение должно отрицать законы времени и причинно-следственной связи на оборудовании. который реализует 100% предсказуемую и последовательную семантику безмолвного переноса до двух. - person supercat; 07.07.2016

int n = -(long long)0x80000000

вам действительно нужно выполнить приведение, чтобы правильно выполнить назначение в диапазоне, или мне что-то не хватает?

Как насчет: int n = -0x80000000LL;

Нет актерского состава.

person Kaz    schedule 06.07.2016
comment
Хороший момент, но все еще существует очень похожая проблема с присвоением -2 ^ 63 long long, чего нельзя избежать таким образом. Можно было сделать - (2 ^ 63-1) -1, но все равно громоздко. Во всяком случае, проблема заключается в ненадежности знака «-», что, по-видимому, я прав в своем понимании его ненадежности. - person Kyle; 06.07.2016
comment
Это то, что вы получаете при разработке языка программирования, в котором - является оператором, а не частью числовой константы. Лисп: (- 123) --- унарный минус, примененный к 123; (+ -123 1) --- добавьте 1 к константе -123. Если у вас шестнадцатеричный формат, знак идет после десятичного префикса #x-FF --- -255. - person Kaz; 06.07.2016

Просто сделайте int n = INT_MIN.

Или если вы должны int n = -2147483648 (то же самое, что и при использовании long long).

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

мы получаем поведение, определяемое реализацией, когда пытаемся присвоить действительное значение int объекту int

Если вас беспокоит поведение, определяемое реализацией, то почему вы используете наивный тип int, а не int32_t?

int имеет размер и формат подписи, определяемый реализацией, что является корнем проблемы. int32_t гарантированно будет 32-битным дополнением до двух.

person Lundin    schedule 06.07.2016
comment
Спасибо, но есть и другие проблемы, которые возникают из-за этой проблемы, это просто упрощенный пример, чтобы указать на вопрос. В тех других случаях обойтись не так-то просто. Например, из-за такого поведения макрос, который отрицает аргумент целочисленного типа, принципиально ненадежен, если предполагается, что он принимает типы со знаком. - person Kyle; 06.07.2016
comment
@Kyle Макрос, который отрицает аргумент, но принимает беззнаковые типы в качестве аргумента, действительно ошибочен и опасен. Такие макросы желательно переписать в функции, чтобы повысить безопасность типов. - person Lundin; 06.07.2016

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

На самом деле нет никакого переносимого способа гарантировать, что выражения, содержащие целочисленные константы, будут работать ожидаемым образом, за исключением того, чтобы вручную гарантировать, что они переведены в тип, который гарантированно достаточно большой, чтобы содержать все промежуточные значения; если вам не нужно ничего, кроме 64 бит, достаточно либо long long, либо unsigned long long; значения могут быть принудительно присвоены этим типам с помощью суффикса UL или ULL.

person supercat    schedule 06.07.2016