Гарантированно ли (uint64_t)-1 даст 0xffffffffffffffff?

Я знаю, что в стандарте C четко определено, что (unsigned)-1 должно давать 2^n-1, т.е. е. целое число без знака со всеми установленными битами. То же самое касается (uint64_t)-1ll. Однако я не могу найти что-то в стандарте C11, определяющее, как интерпретируется (uint64_t)-1.

Итак, вопрос: есть ли в стандарте C какая-либо гарантия, что из следующего верно?

(uint64_t)-1 == (uint64_t)(unsigned)-1   //0x00000000ffffffff
(uint64_t)-1 == (uint64_t)(int64_t)-1    //0xffffffffffffffff

person cmaster - reinstate monica    schedule 18.08.2013    source источник
comment
какова ваша конечная цель? получить максимальное значение, которое может быть представлено целым числом без знака?   -  person user2485710    schedule 19.08.2013
comment
Нет, просто любопытны стандартные гарантии в данной конкретной ситуации. Я могу придумать другие способы получения максимальных целочисленных значений :-)   -  person cmaster - reinstate monica    schedule 19.08.2013


Ответы (4)


Да. См. C11 6.3.1.3 Целые числа со знаком и без знака:

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

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

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

60) Правила описывают арифметические действия над математическим значением, а не над значением данного типа выражения.

Применяется случай 2, поэтому -1 уменьшается по модулю 0x10000000000000000, чтобы получить 0xffffffffffffffffff.

person R.. GitHub STOP HELPING ICE    schedule 18.08.2013
comment
Я думаю, что ваш комментарий относится ко второму выражению в вопросе; это должно быть правдой. Это не относится к RHS первого выражения; (unsigned)-1 равно 0xFFFF_FFFF_FFFF_FFFF, если sizeof(unsigned) == 8, и 0xFFFF_FFFF, если sizeof(unsigned) == 4, а значение может быть представлено в uint64_t. Таким образом, стандарт не гарантирует, что первое выражение будет истинным. - person Jonathan Leffler; 19.08.2013
comment
Я отвечал на вопрос в теме. Я не касался проблемы (uint64_t)(unsigned)-1, которая явно отличается, если только unsigned не является 64-битной. - person R.. GitHub STOP HELPING ICE; 19.08.2013

Выражения 1 и -1 имеют тип int. При преобразовании в uint64_t применяется принцип прибавления или вычитания 2n до тех пор, пока значение не окажется в диапазоне, поэтому результатом всегда будет 2n-1, в данном случае с п=64. Следовательно, (uint64_t)-1 всегда равно 264-1..

Выражение (int64_t)-1 оценивается как -1, поэтому те же рассуждения применимы к выражению (uint64_t)(int64_t)-1, которое тоже всегда дает 264-1.

С другой стороны, (unsigned)-1 является положительным значением типа unsigned int, которое может быть 216-1, 232-1, 264- 1 или различные другие значения в зависимости от платформы компиляции. Эти значения могут не давать 264-1 при преобразовании в uint64_t.

person Pascal Cuoq    schedule 18.08.2013
comment
не лучше ли просто выбрать UINT64_MAX или я что-то упускаю из вопроса ОП? зачем приводить число со знаком к беззнаковому? - person user2485710; 19.08.2013
comment
@user2485710 user2485710 Разве ты не должен спросить ОП? Да, использование UINT64_MAX кажется менее подверженным ошибкам, но вопрос не в этом. - person Pascal Cuoq; 19.08.2013
comment
Вопрос относится к любому целому числу со знаком, которое одновременно приводится к а) большему типу и б) беззнаковому типу. Как в (unsigned)(signed char)-123. - person cmaster - reinstate monica; 19.08.2013
comment
@cmaster: ваш пример - это два последовательных приведения - нет такого понятия, как два «одновременных» приведения. Есть только одно приведение к типу, который является одновременно большим и (не)знаковым, который хорошо определен, или есть серия приведений в определенном порядке, который одинаково хорошо определен (но может быть другим). - person Chris Dodd; 19.08.2013
comment
@ChrisDodd +1 Однако сложная версия вопроса будет (uint64_t)-1U. Тот же результат, что и (uint64_t)(unsigned)-1, в основном по той же причине, но хорошо скрытой. Я должен отметить это для следующего IOCCC. - person Pascal Cuoq; 19.08.2013
comment
@ChrisDodd Это одно приведение для получения signed char и другое приведение для одновременного создания 32 бит и преобразования в беззнаковое. Да, неприятный пример... - person cmaster - reinstate monica; 19.08.2013
comment
@PascalCuoq: (uint64_t)-1U совпадает с (uint64_t)(-((unsigned)1)), поскольку константные литералы никогда не включают знаки в C (или C++), за исключением экспоненциальной части константы с плавающей запятой. Таким образом, - будет операцией унарного префикса над константой. - person Chris Dodd; 19.08.2013
comment
@ChrisDodd Я знаю, но это ничего здесь не меняет: -1U - это выражение типа unsigned int, которое оценивается как UINT_MAX, и преобразование этого выражения в uint64_t может не быть 2 ^ 64-1 в зависимости от платформы компиляции. - person Pascal Cuoq; 19.08.2013
comment
@cmaster Какая часть того же результата, что и (uint64_t)(unsigned)-1, по той же причине сбивает с толку? - person Pascal Cuoq; 19.08.2013
comment
@cmaster Кстати, вы спрашиваете о стандарте C, но вы отметили свой вопрос «C++» и тестируете с помощью g++? C и C++ - разные языки. - person Pascal Cuoq; 19.08.2013
comment
@PascalCuoq Aiii, я перепутал то, что вы с Крисом Доддом сказали, конечно, g++ поддерживает вашу версию. Я удалю неправильный комментарий. Что касается вас в сторону: было бы также интересно узнать, есть ли какие-либо различия между языками в этом отношении, но я думаю, что их нет, поэтому мне все равно, какой компилятор я использую. - person cmaster - reinstate monica; 19.08.2013

Я предполагаю, что вы пишете (uint64_t)-1 вместо -1ULL, потому что не хотите делать предположения о размере unsigned long long? Если так, то это хорошо. Однако есть альтернатива, которая еще не упоминалась (и на самом деле не отвечает на ваш вопрос), но может избавить вас от беспокойства, обходя вопрос:

Альтернатива

Хорошая привычка — всегда использовать UINT64_C(x) вместо (uint64_t)x. Это макрос, определенный в <stdint.h>, который автоматически добавляет U, UL или ULL по мере необходимости. Таким образом, UINT64_C(-1) преобразуется в -1U, -1UL или -1ULL, в зависимости от вашей цели. Это гарантированно всегда работает правильно.

Опасности приведения типов

Обратите внимание, что (uint64_t)x на самом деле вообще не работает корректно. Например,

(uint64_t)2147483648                // RISKY

генерирует предупреждение на некоторых компиляторах, потому что значение 2147483648 (2^31) слишком велико для 32-битного целого числа, а следующее даже отдаленно не работает:

(uint64_t)1000000000000000000       // RISKY

Однако, если вместо этого вы используете UINT64_C(), то все золото:

UINT64_C(2147483648)                // GOOD

UINT64_C(1000000000000000000)       // GOOD

UINT64_C(-1)                        // GOOD

Заметки:

  • Суффикс _C означает «постоянный».
  • В <stdint.h> есть также 8-, 16- и 32-битные версии для значений со знаком и без знака.
  • Для особого случая –1 вы также можете просто написать UINT64_MAX.
person Todd Lehman    schedule 08.08.2015
comment
Что ж, ваше предположение было неверным; этот вопрос был действительно о том, как стандарты C/C++ определяют работу приведения, которая изменяет как знак, так и размер целого числа за одну операцию. Тем не менее, макрос UINT64_C() является интересным дополнением, хотя на самом деле он не отвечает на вопрос. Кстати: long long int гарантированно будет не менее 64 бит, поэтому (uint64_t)2147483648ull гарантированно даст правильный результат. - person cmaster - reinstate monica; 08.08.2015
comment
Хотя следует иметь в виду, что -1u не преобразуется -1 в беззнаковое, а оператор- применяется к 1u, и, следовательно, для его работы применяются другие правила: brnz.org/hbr/?p=1433 - person Trass3r; 14.03.2018
comment
Целочисленные литералы IIRC, С++ будут иметь тип, достаточно широкий для представления значения. Таким образом, если int является 32-битным типом, 2147483648 будет иметь тип long или long long. На самом деле это не опасно. (Если я не ошибаюсь или есть разница в C/C++). Однако 1024*1024*1024*1024 рискованно, потому что при последнем умножении оно переполнит 32-битное int! - person Peter Cordes; 30.03.2020
comment
UINT64_C(-1) не определено (диагностика не требуется); согласно C11 7.20.4/2 аргумент должен быть целочисленной константой без суффикса со значением в диапазоне для типа. -1 не является константой, это унарное минус-выражение. -UINT64_C(1) будет правильной альтернативой - person M.M; 04.06.2020
comment
Две строки, которые вы пометили как RISKY, требуются стандартом для работы (если, конечно, существует uint64_t) - person M.M; 04.06.2020

Это вопрос, на который можно ответить с помощью нескольких строк кода.

#include <stdio.h>

main()
{
    int          x;
    unsigned int y;

    x = -1;
    printf("\n0x%08x", x);

    y = (unsigned int) -1;
    printf("\n0x%08x", y);

}

Запуск этого кода на компиляторе Eclipse/Microsoft C дает:

0xffffffff
0xffffffff

Аналогичная программа может показать вам, что производит uint64_t.

Наконец, если вы понимаете, как компьютеры используют числа с дополнением до 2 для сложения чисел, то вы поймете, что -1 для слов с любым количеством битов (8, 32, 64 и т. д.) всегда представляет собой все ff для каждого байта в слове/. двойное слово и др.

person JackCColeman    schedule 18.08.2013
comment
Это не тот вопрос, на который можно ответить, проверив и посмотрев. - person R.. GitHub STOP HELPING ICE; 19.08.2013
comment
Для справки, стандарт не гарантирует дополнение до 2 для целых типов со знаком и не гарантирует, что преобразование в тип со знаком сохраняет младшие значащие биты. Они оба определяются реализацией. Представление с дополнением до 2 и преобразование путем отбрасывания старших битов являются обычными вариантами реализации. - person Pascal Cuoq; 19.08.2013
comment
С каких это пор компьютеры, которые мы используем, НЕ используют дополнение до 2 для выполнения арифметических операций? Вы слишком много читаете в спецификации языка. - person JackCColeman; 19.08.2013
comment
Я думаю, что вопрос касается «гарантий [s] в стандарте C», но, возможно, я слишком много читаю. - person Pascal Cuoq; 19.08.2013
comment
@PascalCuoq, я полагаю, что хорошо написанный компилятор реализует стандарт и что это гарантия, о которой идет речь. - person JackCColeman; 19.08.2013
comment
@Р.. — Хм. Если результаты положительные (нет двусмысленности), то проверка и проверка точно не помогут доказать это. Но все, что нужно, это один отрицательный результат от одного компилятора, чтобы доказать, что (uint64)-1 не гарантирует получение 0xFFFFFFFFFFFFFFFF... поэтому теоретически стоит попробовать, на случай, если это не удастся. Это, конечно, полностью отличается от того, указывает ли стандарт, должен ли он работать. - person Todd Lehman; 08.08.2015
comment
@Jack: Да, компиляторы обычно реализуют стандарт, но стандарт оставляет некоторые вещи определяемыми реализацией, а многие другие вещи являются неопределенным поведением. C++ — это не Java Обычно что-то происходит, но нет гарантии, что то же самое произойдет и в других реализациях. например. преобразование без знака в C работает, как и ожидалось, на x86, но не на ARM. См. также Что должен знать каждый программист C о Undefined Поведение. Так же есть целый тег для undefined-behavior - person Peter Cordes; 30.03.2020
comment
@PeterCordes, вы говорите так, будто непоследовательное поведение неизбежно, реальность такова, что различия заключаются в том, что люди несовершенны и непоследовательны, есть старая пословица о том, что для создания или создания хорошей операционной системы требуется 20 лет, то есть требуется которые долго решают непоследовательное неопределенное поведение, сказав все, что зачем принимать менее чем полностью последовательные, четко определенные программы - - person JackCColeman; 01.04.2020
comment
Я думаю, вы до сих пор не поняли, что C намеренно позволяет реализациям делать разные выборы в некоторых вещах и намеренно оставляет поведение неопределенным в некоторых случаях, чтобы компиляторы могли быстрее выполнять код, который должен работать только для четко определенных случаев. Так что в основном ответ на ваш, почему принять это? это производительность и переносимость на разные процессоры, где эффективны разные вещи. Одним из очевидных примеров является то, что запись вне границ массива — это Undefined Behavior; ISO C ничего не говорит о расположении локальных переменных рядом друг с другом. - person Peter Cordes; 01.04.2020