Подтвердить что ты не робот

Что происходит, когда 32-битное целое переполняется на 64-битной машине?

Ситуация такова:

  • 32-битное целочисленное переполнение
  • malloc, ожидающий, что 64-битное целое использует это целое число как входное

Теперь на 64-битной машине, какой оператор корректен (если вообще есть):

Скажем, что подписанное двоичное целое 11111111001101100000101011001000 просто отрицательно из-за переполнения. Это практическая существующая проблема, поскольку вы можете выделить больше байтов, чем вы можете описать в 32-битном целочисленном. Но затем он считывается как 64-битное целое число.

  • Malloc читает это как 64-битное целое число, нахожу 11111111001101100000101011001000################################, где # - бит подстановки, представляющий любые данные, хранящиеся после исходного целого. Другими словами, он читает результат, близкий к его максимальному значению 2 ^ 64, и пытается выделить несколько квантов. Он терпит неудачу.
  • Malloc читает это как 64-битное целое число, отбрасывая на 0000000000000000000000000000000011111111001101100000101011001000, возможно, потому, что он загружается в регистр, оставляя много битов ноль. Он не терпит неудачу, но выделяет отрицательную память, как будто читает положительное значение без знака.
  • Malloc читает это как 64-битное целое число, отбрасывая на ################################11111111001101100000101011001000, возможно, потому, что он загружается в регистр С# подстановочным знаком, представляющим любые данные ранее в регистре. Он не вполне непредсказуем в зависимости от последнего значения.
  • Целое число не переполняется вообще, потому что, хотя оно 32 бит, оно все еще находится в 64-битном регистре и поэтому malloc отлично работает.

Я действительно протестировал это, в результате чего malloc failing (что означало бы, что 1 или 3 будет правильным). Я предполагаю, что 1 является наиболее логичным ответом. Я также знаю исправление (используя size_t как вход вместо int).

Я просто хочу знать, что на самом деле происходит. По какой-то причине я не нахожу каких-либо разъяснений о том, как 32-битные целые числа фактически обрабатываются на 64-битных машинах для такого неожиданного "броска". Я даже не уверен, действительно ли это в регистре.

4b9b3361

Ответ 1

Как только целое число переполняется, использование его значения приводит к поведению undefined. Программа, использующая результат int после переполнения, недействительна в соответствии со стандартом - по существу все ставки о ее поведении отключены.

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

Однако, согласно стандарту С++, тип аргумента malloc, т.е. size_t, всегда без знака. Когда вы конвертируете отрицательное число в беззнаковое число, он получает расширенный знак (см. Этот ответ для обсуждения и ссылку на стандарт), что означает, что наиболее значимые бит оригинала (который равен 1 для всех отрицательных чисел) устанавливается в верхние 32 бита результата без знака.

Следовательно, вы получаете модифицированную версию вашего третьего случая, за исключением того, что вместо "подстановочного бита #" он имеет все пути к вершине. Результатом является гигантское число без знака (примерно 16 exbibytes или так); естественно malloc не может выделить столько памяти.

Ответ 2

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

Это, к сожалению, не так: поведение undefined означает, что что-то может произойти, и особенно то, что компиляторы могут оптимизировать, как будто этого никогда не может произойти.

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

  • Возможный вывод заключается в том, что компилятор исключает выделение, потому что этого не может быть.
  • Возможный вывод состоит в том, что результирующее значение является 0-расширенным или расширенным знаком (в зависимости от того, известно ли оно как положительное или нет) и интерпретируется как целое число без знака. Вы можете получить что-либо от 0 до size_t(-1) и, следовательно, могут выделять слишком мало или слишком много памяти или даже не выделять,...
  • ...

Undefined Поведение = > Все ставки отключены

Ответ 3

Итак, если у нас есть конкретный пример кода, конкретный компилятор и платформа, мы можем, вероятно, определить, что делает компилятор. Какой подход используется в Deep C, но даже тогда он может быть не вполне предсказуемым, что является отличительной чертой поведения undefined, обобщая около undefined поведение не является хорошей идеей.

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

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

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

В идеале самый безопасный подход заключается в том, чтобы полностью исключить целочисленное переполнение целых чисел. [...]

В конце дня это поведение undefined и поэтому непредсказуемо в общем случае, но в случае gcc, в разделе их реализации, указанном в Integer говорит, что целочисленное переполнение обтекает:

Для преобразования в тип ширины N значение уменьшается по модулю 2 ^ N, чтобы быть в пределах диапазона типа; сигнал не поднимается.

но в своем совете о переполнении целых чисел они объясняют, как оптимизация может вызвать проблемы с оберткой:

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

Итак, это быстро усложняется.