Что должно произойти с отрицанием size_t (т.е. `-sizeof (struct foo)`))?

Я имею дело с некоторым кодом на работе, который включает выражение формы

-(sizeof(struct foo))

то есть. отрицание a size_t, и я не понимаю, что требования C и С++ требуют от компиляторов, когда они видят это. В частности, при поиске здесь и в другом месте sizeof возвращает неподписанное целочисленное значение типа size_t. Я не могу найти четкую ссылку для указанного поведения при отрицании целого числа без знака. Есть ли какие-либо, и если да, то что это такое?

Edit: Хорошо, поэтому есть некоторые хорошие ответы относительно арифметики на неподписанные типы, но неясно, что это на самом деле такое. Когда это отрицает, работает ли оно беззнаковым целым или преобразуется в подписанный тип и что-то делает с этим? Является ли поведение ожиданием от стандартов "представьте себе отрицательное число одинаковой величины, а затем примените правила" переполнения "для неподписанных значений"?

Ответ 1

Оба стандарта ISO C и ISO С++ гарантируют, что арифметика без знака является по модулю 2 n - то есть для любого переполнения или нижнего потока она "обертывается". Для ISO С++ это 3.9.1 [basic.fundamental]/4:

Беззнаковые целые числа, объявленные unsigned, должны подчиняться законам арифметики по модулю 2 n где n - количество бит в представлении значений этого конкретного размера целых чисел. 41

...

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

Для ISO C (99) это 6.2.5/9:

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

Это означает, что результат гарантированно будет таким же, как SIZE_MAX - (sizeof(struct foo)) + 1.


В ИСО 14882: 2003 5.3.1.7:

[...] Отрицание без знака количество вычисляется путем вычитания его значение от 2 n где n - количество бит в обработанный операнд. Тип результатом является тип операнд.

Ответ 2

http://msdn.microsoft.com/en-us/library/wxxx8d2t%28VS.80%29.aspx

Унарное отрицание неподписанных величин выполняется путем вычитания значения операнда от 2 n где n - количество бит в объекте заданный беззнаковый тип. (Microsoft С++ работает на процессорах, которые используют арифметика с двумя дополнениями. На других процессоры, алгоритм отрицания может отличаться.)

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

Ответ 3

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

Ответ 4

Единственное, о чем я могу думать, так неправильно, что у меня болит голова...

size_t size_of_stuff = sizeof(stuff);

if(I want to subtract the size)
    size_of_stuff = -sizeof(stuff);

size_t total_size = size_of_stuff + other_sizes;

Переполнение - это функция!

Ответ 5

Из текущий проект стандарта С++, раздел 5.3.1 предложение 8:

Операнд унарного оператора - должен иметь тип арифметики или перечисления, а результат - отрицание его операнда. Интегральное продвижение выполняется на интегральных или перечисляющих операндах. Отрицание неподписанной величины вычисляется путем вычитания ее значения из 2 n где n - количество бит в продвинутом операнде. Тип результата - тип продвинутого операнда.

Таким образом, полученное выражение по-прежнему без знака и вычисляется, как описано.

Пользователь @outis упомянул об этом в комментарии, но я собираюсь поставить его в ответ, поскольку outis этого не сделал. Если outis вернется и ответит, я соглашусь с этим.

Ответ 6

size_t - это целочисленный тип без знака, определенный реализацией.

Отрицание значения size_t, вероятно, дает результат типа size_t с обычным поведением без надписей. Например, если предположить, что size_t - 32 бита и sizeof(struct foo) == 4, то -sizeof(struct foo) == 4294967292 или 2 32 -4.

За исключением одного: Унарный оператор - применяет к его операнду целые поощрения (C) или целые акции (С++) (они, по сути, одно и то же). Если size_t не меньше ширины int, то эта акция ничего не делает, а результат имеет тип size_t. Но если int шире, чем size_t, так что INT_MAX >= SIZE_MAX, то операнд - "продвигается" от size_t до int. В этом маловероятном случае -sizeof(struct foo) == -4.

Если вы присвоите это значение объекту size_t, он будет преобразован обратно в size_t, что даст ожидаемое значение SIZE_MAX-4. Но без такого преобразования вы можете получить неожиданные результаты.

Теперь я никогда не слышал о реализации, где size_t уже, чем int, поэтому вы вряд ли столкнетесь с этим. Но здесь тестовый пример, используя unsigned short в качестве подставки для гипотетического узкого типа size_t, который иллюстрирует потенциальную проблему:

#include <iostream>
int main() {
    typedef unsigned short tiny_size_t;
    struct foo { char data[4]; };
    tiny_size_t sizeof_foo = sizeof (foo);
    std::cout << "sizeof (foo) = " << sizeof (foo) << "\n";
    std::cout << "-sizeof (foo) = " << -sizeof (foo) << "\n";
    std::cout << "sizeof_foo = " << sizeof_foo << "\n";
    std::cout << "-sizeof_foo = " << -sizeof_foo << "\n";
}

Выходной сигнал на моей системе (который имеет 16-разрядный short, 32-разрядный int и 64-бит size_t):

sizeof (foo) = 4
-sizeof (foo) = 18446744073709551612
sizeof_foo = 4
-sizeof_foo = -4