Как я получил значение размером больше 8 бит из 8-битного целого числа?

Я выследил чрезвычайно неприятную ошибку, скрывающуюся за этим маленьким драгоценным камнем. Я знаю, что в спецификации C++ подписанные переполнения являются неопределенным поведением, но только когда переполнение происходит, когда значение расширено до битовой ширины sizeof(int). Как я понимаю, приращение a char не должно быть неопределенного поведения, пока sizeof(char) < sizeof(int). Но это не объясняет, как!--5--> здесь невозможно значение. Как 8-битное целое число, как может c содержат значения больше, чем его ширина бита?

код

// Compiled with gcc-4.7.2
#include <cstdio>
#include <stdint.h>
#include <climits>

int main()
{
   int8_t c = 0;
   printf("SCHAR_MIN: %in", SCHAR_MIN);
   printf("SCHAR_MAX: %in", SCHAR_MAX);

   for (int32_t i = 0; i <= 300; i++)
      printf("c: %in", c--);

   printf("c: %in", c);

   return 0;
}

выход

SCHAR_MIN: -128
SCHAR_MAX: 127
c: 0
c: -1
c: -2
c: -3
...
c: -127
c: -128  // <= The next value should still be an 8-bit value.
c: -129  // <= What? That's more than 8 bits!
c: -130  // <= Uh...
c: -131
...
c: -297
c: -298  // <= Getting ridiculous now.
c: -299
c: -300
c: -45   // <= ..........

проверьте это на ideone.

9 ответов


это ошибка компилятора.

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

если c определяется как int8_t и int8_t превращается в int, потом c-- предполагается выполнить вычитание c - 1 на int арифметика и преобразовать результат обратно в int8_t. Вычитание в int не переполняется, и преобразование вне диапазона интегральных значений в другой интегральный тип допустимо. Если тип назначения подписан, результат определяется реализацией, но он должен быть допустимым значением для типа назначения. (И если тип назначения unsigned, результат четко определен, но это не применяется здесь.)


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

в этом случае это, по-видимому, ошибка соответствия. Выражение c-- должны манипулировать c таким образом похожие на c = c - 1. Здесь значение c справа повышается до типа int, а затем происходит вычитание. С c в границах int8_t, это вычитание не будет переполняться, но оно может привести к значению, которое находится вне диапазона int8_t. Когда это значение назначено, преобразование происходит обратно в тип int8_t таким образом, результат помещается обратно в c. В случае вне диапазона преобразование имеет значение, определенное реализацией. но значение вне диапазона int8_t не является допустимым значением, определенным реализацией. Реализация не может "определить", что 8-битный тип внезапно содержит 9 или более бит. стоимость реализации означает, что что-то в пределах int8_t производится, и программа продолжается. стандарт C, таким образом, допускает поведение, такое как арифметика насыщения (общая для DSP) или обертывание (основные архитектуры).

компилятор использует более широкий базовый тип машины при манипулировании значениями малых целочисленных типов, таких как int8_t или char. Когда арифметика выполняется, результаты, которые находятся вне диапазона малого целочисленного типа, могут быть надежно зафиксированы в этом более широком типе. Чтобы сохранить внешне видимое поведение, что переменная является 8-битным типом, более широкий результат должен быть усечен в 8-битный диапазон. Явный код необходим для этого, так как места хранения машины (регистры) шире 8 бит и довольны большое значение. Вот, компилятор забыл нормализуют значение и просто передал его printf как есть. Спецификатор преобразования %i на printf понятия не имеет, что аргумент первоначально исходил из int8_t расчеты; он просто работает с


Я не могу поместить это в комментарий, поэтому я публикую его в качестве ответа.

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

я протестировал код, размещенный на Ideone, и заменил c-- С c = c - 1 и значения остались в пределах диапазона [-128 ... 127]:

c: -123
c: -124
c: -125
c: -126
c: -127
c: -128 // about to overflow
c: 127  // woop
c: 126
c: 125
c: 124
c: 123
c: 122

Чумовая Эй? Я мало знаю о том, что компилятор делает с такими выражениями, как i++ или i--. Вероятно, это способствует возвращению значения Ан int и передать его. Это единственный логический вывод, который я могу сделать, потому что вы фактически получаете значения, которые не могут поместиться в 8-битные.


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


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


код ассемблера показывает проблему:

:loop
mov esi, ebx
xor eax, eax
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
sub ebx, 1
call    printf
cmp ebx, -301
jne loop

mov esi, -45
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
xor eax, eax
call    printf

EBX должен быть anded с декрементом FF post, или только BL должен использоваться с оставшейся частью EBX clear. Любопытно, что он использует sub вместо dec. -45 абсолютно загадочный. Это побитовая инверсия 300 & 255 = 44. -45 = ~44. Есть какая-то связь.

Он проходит намного больше работы, используя c = c-1:

mov eax, ebx
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
add ebx, 1
not eax
movsx   ebp, al                 ;uses only the lower 8 bits
xor eax, eax
mov esi, ebp

затем он использует только низкую часть RAX, поэтому он ограничен -128 до 127. Параметры компилятора "- g-O2".

без оптимизации, он производит правильный код:

movzx   eax, BYTE PTR [rbp-1]
sub eax, 1
mov BYTE PTR [rbp-1], al
movsx   edx, BYTE PTR [rbp-1]
mov eax, OFFSET FLAT:.LC2   ;"c: %i\n"
mov esi, edx

Итак, это ошибка в оптимизаторе.


использовать %hhd вместо %i! Должно решить вашу проблему.

то, что вы видите, является результатом оптимизации компилятора в сочетании с тем, что вы говорите printf печатать 32-битное число, а затем нажимаете (предположительно 8-битное) число на стек, который действительно имеет размер указателя, потому что так работает push-код в x86.


Я думаю, что это делается путем оптимизации кода:

for (int32_t i = 0; i <= 300; i++)
      printf("c: %i\n", c--);

компилятор использует int32_t i переменной как для i и c. Выключите оптимизацию или сделайте direct cast printf("c: %i\n", (int8_t)c--);


c сам определяется как int8_t, но при работе ++ или -- над int8_t он неявно преобразуется сначала в int и результат работы вместо внутреннее значение c печатается с printf, который случается int.

посмотреть фактическое значение of c после всего цикла, особенно после последнего декремента

-301 + 256 = -45 (since it revolved entire 8 bit range once)

его правильное значение, которое напоминает поведение -128 + 1 = 127

c начинает использовать int размер памяти, но напечатать как int8_t при печати как себя, используя только 8 bits. Использует все 32 bits при использовании в качестве int

[Компилятор Ошибок]


Я думаю, это произошло потому, что ваш цикл будет идти до тех пор, пока int i не станет 300, А c -300. И последнее значение, потому что

printf("c: %i\n", c);