programing tip

8 비트 정수에서 8 비트보다 큰 값을 얻으려면 어떻게해야합니까?

itbloger 2020. 7. 17. 20:58
반응형

8 비트 정수에서 8 비트보다 큰 값을 얻으려면 어떻게해야합니까?


나는이 작은 보석 뒤에 숨어있는 매우 불쾌한 버그를 추적했습니다. C ++ 사양에 따라 서명 된 오버플로는 정의되지 않은 동작이지만 값이 bit-width로 확장 될 때 오버플로가 발생하는 경우에만 발생합니다 sizeof(int). 내가 이해하는 것처럼을 char늘리는 것으로 정의되지 않은 동작을해서는 안됩니다 sizeof(char) < sizeof(int). 그러나 이것이 어떻게 불가능한 가치를 c얻는 지 설명하지 못합니다 . 8 비트 정수로서 어떻게 비트 너비보다 큰 값 보유 할 수 있습니까?c

암호

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

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

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

   printf("c: %i\n", 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   // <= ..........

아이디어를 확인하십시오.


이것은 컴파일러 버그입니다.

정의되지 않은 동작에 대해 불가능한 결과를 얻는 것이 올바른 결과이지만 실제로는 코드에 정의되지 않은 동작이 없습니다. 일어나고있는 일은 컴파일러 동작이 정의되지 않았다고 생각 하고 그에 따라 최적화 한다는 것 입니다.

경우 c로 정의 int8_t하고, int8_t에 촉진 int, 다음 c--뺄셈을 수행하도록되어 c - 1있는 int연산과에 결과 다시 변환 int8_t. 빼기 int가 넘치지 않으며 범위를 벗어난 정수 값을 다른 정수 유형으로 변환하는 것이 유효합니다. 대상 유형이 서명 된 경우 결과는 구현에 따라 정의되지만 대상 유형에 유효한 값이어야합니다. 대상 유형이 서명되지 않은 경우 결과는 잘 정의되어 있지만 여기에는 적용되지 않습니다.


컴파일러에는 다른 요구 사항이 있기 때문에 표준과 호환되지 않는 버그가있을 수 있습니다. 컴파일러는 다른 버전의 자체와 호환 가능해야합니다. 또한 다른 컴파일러와 호환 될 수 있으며 대부분의 사용자 기반이 보유한 동작에 대한 일부 신념을 준수해야합니다.

이 경우 적합성 버그 인 것 같습니다. 표현은 c--조작해야 c유사한 방법으로 c = c - 1. 여기서 c오른쪽 의 값이 type으로 승격 된 int다음 빼기가 발생합니다. 보낸 c의 범위이고 int8_t,이 감산이 오버 플로우되지 않으며, 그러나의 범위를 벗어난 값을 생성 할 수있다 int8_t. 이 값이 지정되면 변환이 유형으로 int8_t다시 돌아가서 결과가에 적합 c합니다. 범위를 벗어난 경우 변환에 구현 정의 값이 있습니다. 그러나 범위를 벗어난 값 int8_t은 유효한 구현 정의 값이 아닙니다. 구현에서는 8 비트 유형이 갑자기 9 개 이상의 비트를 보유하도록 "정의"할 수 없습니다.구현 정의 값이되는 것은 범위 내의 무언가 int8_t가 생성되고 프로그램이 계속됨을 의미합니다. 따라서 C 표준은 포화 산술 (DSP에서 일반적 임) 또는 랩 어라운드 (주류 아키텍처)와 같은 동작을 허용합니다.

The compiler is using a wider underlying machine type when manipulating values of small integer types like int8_t or char. When arithmetic is performed, results which are out of range of the small integer type can be captured reliably in this wider type. To preserve the externally visible behavior that the variable is an 8 bit type, the wider result has to be truncated into the 8 bit range. Explicit code is required to do that since the machine storage locations (registers) are wider than 8 bits and happy with the larger values. Here, the compiler neglected to normalize the value and simply passed it to printf as is. The conversion specifier %i in printf has no idea that the argument originally came from int8_t calculations; it is just working with an int argument.


I can't fit this in a comment, so I'm posting it as an answer.

For some very odd reason, the -- operator happens to be the culprit.

I tested the code posted on Ideone and replaced c-- with c = c - 1 and the values remained within the range [-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

Freaky ey? I don't know much about what the compiler does to expressions like i++ or i--. It's likely promoting the return value to an int and passing it. That's the only logical conclusion I can come up with because you ARE in fact getting values that cannot fit into 8-bits.


I guess that the underlying hardware is still using a 32-bit register to hold that int8_t. Since the specification does not impose a behaviour for overflow, the implementation does not check for overflow and allows larger values to be stored as well.


If you mark the local variable as volatile you are forcing to use memory for it and consequently obtain the expected values within the range.


The assembler code reveals the problem:

: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 should be anded with FF post decrement, or only BL should be used with the remainder of EBX clear. Curious that it uses sub instead of dec. The -45 is flat-out mysterious. It's the bitwise inversion of 300 & 255 = 44. -45 = ~44. There's a connection somewhere.

It goes through a lot more work using 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

It then uses only the low portion of RAX, so it's restricted to -128 thru 127. Compiler options "-g -O2".

With no optimization, it produces correct code:

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

So it's a bug in the optimizer.


Use %hhd instead of %i! Should solve your problem.

What you see there is the result of compiler optimizations combined with you telling printf to print a 32bit number and then pushing a (supposedly 8bit) number onto the stack, which is really pointer sized, because this is how the push opcode in x86 works.


I think this is doing by optimization of the code:

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

The compilator use the int32_t i variable both for i and c. Turn off optimization or make direct cast printf("c: %i\n", (int8_t)c--);


c is itself defined as int8_t, but when operating ++ or -- over int8_t it is implicitly converted first to int and the result of operation instead the internal value of c is printed with printf which happens to be int.

See the actual value of c after entire loop, especially after last decrement

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

its the correct value which resembles the behaviour -128 + 1 = 127

c starts to use int size memory but printed as int8_t when printed as itself using only 8 bits. Utilizes all 32 bits when used as int

[Compiler Bug]


I think it happened because your loop will go until the int i will become 300 and c become -300. And the last value is because

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

참고URL : https://stackoverflow.com/questions/16125660/how-did-i-get-a-value-larger-than-8-bits-in-size-from-an-8-bit-integer

반응형