programing

long 2147483647 + 1 = -2147483648은 왜 길까요?

procenter 2022. 8. 17. 23:16
반응형

long 2147483647 + 1 = -2147483648은 왜 길까요?

왜 이 코드에는 같은 번호가 인쇄되지 않는 거죠?:

long long a, b;
a = 2147483647 + 1;
b = 2147483648;
printf("%lld\n", a);
printf("%lld\n", b);

int 변수가 4바이트이기 때문에 int 변수의 최대 수는 2147483647인 것으로 알고 있습니다.하지만 제가 알기로는 긴 변수는 8바이트인데 왜 그런 코드일까요?

2147483647 + 1 개의 2면 됩니다.ints오버플로우입니다.

2147483648 수 없다int이 명령어를 " Unity"로 합니다.long a)long longMSVC(MSVC))따라서 오버플로우하지 않습니다.

long long적절한 상수 접미사를 사용합니다.

a = 2147483647LL + 1;

이 서명된 정수 오버플로는 C/C++에서 항상처럼 정의되지 않은 동작입니다.

정의되지 않은 동작에 대해 모든 C 프로그래머가 알아야 할 사항

서명된 정수 오버플로를 2의 보완 줄바꿈으로 잘 정의하기 위해 또는 그와 동등한 것으로 컴파일하지 않는 한.와 함께gcc -fwrapv또는 정수 오버플로 = 랩어라운드를 정의하는 다른 구현에서는 실제로 볼 수 있는 랩핑이 잘 정의되어 있으며 정수 리터럴 유형 및 평가식에 대한 다른 ISO C 규칙에 따라 수행됩니다.

T var = expression는 이 으로 타입 「으로 변환합니다.T 표준 규칙에 따라 표현을 평가한 후.맘에 들다(T)(expression)(int64_t)2147483647 + (int64_t)1.

컴파일러는 이 실행 경로가 절대 도달하지 않고 불법 명령이나 다른 무언가를 방출한다고 가정할 수 있습니다.상수 표현식의 오버플로우에서 2의 보완 랩어라운드를 구현하는 것은 일부/대부분의 컴파일러가 선택하는 것에 불과합니다.


ISO C 표준에서는 값이 너무 커서 맞지 않거나(16진수일 경우 부호 없음, 길거나 길 수 있음) 크기 오버라이드가 사용되는 경우를 제외하고 숫자 리터럴에 유형이 지정됩니다.그런 다음 다음과 같은 이진 연산자에 일반적인 정수 승격 규칙이 적용됩니다.+ ★★★★★★★★★★★★★★★★★」*컴파일 시간 상수 표현의 일부인지 여부에 관계없이 상관없습니다.

이는 컴파일러가 제한된 머신 상에서 실행되어야 했던 초기 C에서도 컴파일러가 구현하기 쉬운 단순하고 일관된 규칙입니다.

ISO C에서는 C/C++가 사용됩니다.2147483647 + 132비트를 사용하는 구현에서 정의되지 않은 동작입니다.int. (따라서 부호 있는 음수로 값을 감싼다) 취급하는 것은 표현의 유형에 대한 ISO C 규칙과 비오버플로우 사례에 대한 일반 평가 규칙에서 자연스럽게 따라옵니다.현재의 컴파일러는 동작의 정의를 이것과 달리 선택하지 않습니다.

ISO C/C++는 정의되어 있지 않기 때문에, 실장은 C/C++ 표준을 위반하지 않고, 문자 그대로(비음 악마를 포함한다) 모든 것을 선택할 수 있습니다.실제로 이 동작(랩+경고)은 그다지 불쾌하지 않은 동작 중 하나이며, 서명된 정수 오버플로를 랩으로 처리한 후 실행 시 자주 발생합니다.

또한 일부 컴파일러는 컴파일 시간 상수 식뿐만 아니라 모든 경우에 대해 실제로 동작을 정의할 수 있는 옵션을 가지고 있습니다.(gcc -fwrapv를 참조해 주세요.


컴파일러는 이에 대해 경고합니다.

좋은 컴파일러는 컴파일 시 이를 포함한 다양한 형태의 UB에 대해 경고합니다.GCC 및 clang 경고:-Wall. Godbolt 컴파일러 탐색기에서:

  clang
<source>:5:20: warning: overflow in expression; result is -2147483648 with type 'int' [-Winteger-overflow]
    a = 2147483647 + 1;
                   ^
  gcc
<source>: In function 'void foo()':
<source>:5:20: warning: integer overflow in expression of type 'int' results in '-2147483648' [-Woverflow]
    5 |     a = 2147483647 + 1;
      |         ~~~~~~~~~~~^~~

GCC는 2006년 적어도 GCC4.1(Godbolt의 가장 오래된 버전)부터 이 경고를 디폴트로 유효하게 하고 있으며, 3.3부터는 쨍그랑 소리를 내고 있습니다.

MSVC가 경고하는 것은 -WallMSVC의 경우, , MSVC의 경우, MSVC의 경우, MSVC의 경우, MSVC의 경우, 를 나타내는 .stdio.h 'vfwprintf': unreferenced inline function has been removedMSVC를 사용하다

  MSVC -Wall
<source>(5): warning C4307: '+': signed integral constant overflow

@Human JHawkins는 왜 이렇게 설계되었는지 물었다.

이 질문은 왜 컴파일러는 연산 결과에 맞는 최소 데이터 타입을 사용하지 않는가 하는 것입니다.정수 리터럴을 사용하면, 컴파일시에 오버플로 에러가 발생하고 있는 것을 알 수 있습니다.그러나 컴파일러는 이를 알고 처리하려고 하지 않습니다.왜 그런 것일까요?

"그럴 필요가 없다"컴파일러는 오버플로를 검출하여 경고합니다.은 'ISO C의 법칙' C의 법칙은 C의 법칙입니다.int + int타입이 있다int숫자 리터럴에는 각각 유형이 있습니다.int컴파일러는 의도적인 랩을 선택했을 뿐이며, 사용자가 예상하는 것과 다른 타입의 표현을 사용하는 것이 아닙니다.(전적으로 UB 때문에 구제하는 것이 아니라)

루프에서는 컴파일러가 적극적으로 최적화되지만 런타임에 서명된 오버플로가 발생할 경우 래핑이 일반적입니다.int i/array[i]반복할 마다 다시 서명하는 것을 피할있습니다.

확대로 인해 다음과 같은 (작은) 함정이 발생할 수 있습니다.printf("%d %d\n", 2147483647 + 1, 2147483647);형식 문자열과의 타입 불일치로 인해 정의되지 않은 동작(및 32비트머신에서는 실제로 동작하지 않음)이 발생합니다.한다면2147483647 + 1암묵적으로 승진하다long long, 필요한 것은,%lld포맷 문자열. (64비트 int는 일반적으로 32비트 머신의 2개의 arg-passing 슬롯에 통과하기 때문에 두 번째 int는 중단됩니다.%d아마 전반전 후반전을 볼 수 있을 것이다long long.)

공평하게 말하면, 그건 이미 문제예요.-2147483648C/C++ 소스의 표현으로서 타입이 있습니다.long또는long long라고 해석됩니다.2147483648단항과는 별도로-오퍼레이터 및214748364832비트 서명에 맞지 않습니다.int따라서 값을 나타낼 수 있는 유형 다음으로 큽니다.

그러나 이 확대의 영향을 받는 프로그램에는 UB가 없어도 UB가 있을 수 있으며 확대로 인해 코드가 작동하게 될 가능성이 높아집니다.설계 철학에 관한 문제가 있습니다.너무 많은 레이어의 「일할 일」과 용서하는 동작은, 무엇인가가 기능하는 이유를 정확하게 이해하기 어렵게 합니다.또, 다른 타입의 폭을 가지는 실장에도 휴대할 수 있는 것을 검증하는 것도 어렵습니다.Java와 같은 "안전한" 언어와는 달리 C는 매우 안전하지 않고 플랫폼마다 구현 정의 사항이 다릅니다. 그러나 많은 개발자들은 테스트할 수 있는 구현이 하나뿐입니다(특히 인터넷과 온라인 연속 통합 테스트 이전).


ISO C는 동작을 정의하지 않기 때문에 컴파일러는 UB가 없는 프로그램과의 호환성을 해치지 않고 새로운 동작을 확장으로 정의할 수 있습니다.그러나 모든 컴파일러가 지원하지 않는 한 휴대용 C 프로그램에서는 사용할 수 없습니다.적어도 gcc/clang/ICC에 의해 지원되는 GNU 확장으로 상상할 수 있었습니다.

또, 그러한 옵션은, 다음과 같이 약간 모순될 수 있습니다.-fwrapv행동을 규정하는 거죠전체적으로 리터럴 타입을 지정하기 위한 편리한 구문이 있기 때문에 채택될 가능성은 낮다고 생각합니다.0x7fffffffUL + 1를 제공하다unsigned long32비트 부호 없는 정수로서 그 값에 충분한 폭이 보증됩니다).

그러나 이것은 현재의 디자인이 아닌 C의 선택이라고 생각해 봅시다.

가능한 설계 하나는 임의의 정밀도로 계산한 값에서 정수 상수식의 유형을 추론하는 것입니다.정확도가 다른 이유long long또는unsigned long long? 다음 이유로 최종값이 작을 경우 표현의 중간 부분에 대해 충분히 크지 않을 수 있습니다./,>>,-, 또는&오퍼레이터.

또는 C 프리프로세서와 같이 일정한 정수 표현식이 최소 64비트 등의 고정 구현 정의 폭에서 평가되는 단순한 설계입니다(단, 최종 값 또는 식에서 가장 넓은 임시 값을 기반으로 유형을 할당하시겠습니까?).그러나 그것은 컴파일러가 내부적으로 머신의 기본 정수 폭을 사용할 수 있는 경우보다 컴파일 시간 식을 평가하는데 더 느리게 만드는 16비트 머신의 초기 C에 명백한 단점이 있습니다.int표현.

정수 상수 표현식은 이미 C에서 다소 특수하며, 를 들어 다음과 같은 일부 컨텍스트에서 컴파일평가되어야 합니다.static int array[1024 * 1024 * 1024];(16비트 int의 실장에서는, 멀티플이 오버플로우 합니다).

분명 프로모션 규칙을 일정하지 않은 표현으로 효율적으로 확장할 수 없습니다.(a*b)/c평가하지 않으면 안 될 수도 있다a*b~하듯이long long대신int(예를 들어 x86의 64비트 / 32비트 => 32비트 분할 명령의 장애는 결과를 묵묵히 잘라내는 대신 32비트 분할 명령의 오버플로우에서 발생합니다.따라서 심지어 결과를 에 할당하기도 합니다.int컴파일러가 경우에 따라서는 최적화가 잘 되지 않는 경우가 있습니다.

또, 의 행동/정의성이, 그 유무에 따라 달라지는 것을 정말로 원합니까?컴파일 시간 평가 규칙을 일정하지 않은 표현에 대한 규칙과 일치시키는 것은 일반적으로 좋은 것 같습니다. 비록 이러한 위험한 함정이 남기는 하지만요.하지만, 이것은 좋은 컴파일러가 끊임없는 표현으로 경고할 수 있는 것입니다.


이 C gotcha의 다른 일반적인 경우들은 다음과 같습니다.1<<40대신1ULL << 40비트 플래그를 정의하거나 1T를 다음과 같이 씁니다.1024*1024*1024*1024.

좋은 질문입니다.다른 사람들이 말했듯이 기본적으로 숫자는int의 조작은 다음과 같습니다.a둘에 작용하다int및 오버플로우제가 이걸 재현해서 조금 더 길게 해서 숫자를 넣으려고 했는데long longvariable 및 variable을 추가합니다.1에 대해서c다음 예:

$ cat test.c 
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>

void main() {
  long long a, b, c;

  a = 2147483647 + 1;
  b = 2147483648;

  c = 2147483647;
  c = c + 1;

  printf("%lld\n", a);
  printf("%lld\n", b);
  printf("%lld\n", c);
}

컴파일러는 오버플로우 BTW에 대해 경고합니다.일반적으로 프로덕션 코드는 다음과 같이 컴파일해야 합니다.-Werror -Wall다음과 같은 사고를 피하기 위해:

$ gcc -m64 test.c -o test
test.c: In function 'main':
test.c:8:16: warning: integer overflow in expression [-Woverflow]
 a = 2147483647 + 1;
                ^

마지막으로 테스트 결과는 예상대로입니다.int첫 번째 경우 오버플로우,long long int2위와 3위) :

$ ./test 
-2147483648
2147483648
2147483648

또 다른 gcc 버전에서는 더 많은 경고가 있습니다.

test.c: In function ‘main’:
test.c:8:16: warning: integer overflow in expression [-Woverflow]
 a = 2147483647 + 1;
                ^
test.c:9:1: warning: this decimal constant is unsigned only in ISO C90
 b = 2147483648;
 ^

또, 기술적으로는int그리고.long그 차이는 아키텍처에 따라 다르므로 비트 길이가 다를 수 있습니다.예측 가능한 사이즈의 타입의 경우,int64_t,uint32_t최신 컴파일러 및 시스템 헤더에 일반적으로 정의되어 있기 때문에 애플리케이션이 어떤 비트용으로 구축되어 있어도 데이터 유형은 예측 가능한 상태로 유지됩니다.또, 이러한 값의 인쇄와 스캔은, 다음과 같은 매크로에 의해서 복합됩니다.PRIu64기타.

C/C++의 int 범위는-2147483648로.+2147483647.

따라서 추가 시1의 최대 제한을 초과합니다.int.

이해를 돕기 위해 모든 범위의int적절한 순서로 원을 그립니다.

2147483647 + 1 == -2147483648

2147483647 + 2 == -2147483647

이 문제를 극복하고 싶다면long long대신int.

언급URL : https://stackoverflow.com/questions/61624859/why-does-long-long-2147483647-1-2147483648

반응형