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

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

-(sizeof(struct foo))

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

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

6 ответов

Решение

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

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

...

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

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

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

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


В ИСО 14882: 2003 5.3.1.7:

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

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

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

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

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

size_t является определяемым реализацией целочисленным типом без знака.

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

За исключением одного: одинарный - Оператор применяет целочисленные продвижения (C) или интегральные продвижения (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-битный short32-битный intи 64-битный size_t) является:

sizeof (foo) = 4
-sizeof (foo) = 18446744073709551612
sizeof_foo = 4
-sizeof_foo = -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;

Переполнение это особенность!

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

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

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

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

Другие вопросы по тегам