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 long
MSVC(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 + 1
32비트를 사용하는 구현에서 정의되지 않은 동작입니다.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가 경고하는 것은 -Wall
MSVC의 경우, , MSVC의 경우, MSVC의 경우, MSVC의 경우, MSVC의 경우, 를 나타내는 .stdio.h
'vfwprintf': unreferenced inline function has been removed
MSVC를 사용하다
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
.)
공평하게 말하면, 그건 이미 문제예요.-2147483648
C/C++ 소스의 표현으로서 타입이 있습니다.long
또는long long
라고 해석됩니다.2147483648
단항과는 별도로-
오퍼레이터 및2147483648
32비트 서명에 맞지 않습니다.int
따라서 값을 나타낼 수 있는 유형 다음으로 큽니다.
그러나 이 확대의 영향을 받는 프로그램에는 UB가 없어도 UB가 있을 수 있으며 확대로 인해 코드가 작동하게 될 가능성이 높아집니다.설계 철학에 관한 문제가 있습니다.너무 많은 레이어의 「일할 일」과 용서하는 동작은, 무엇인가가 기능하는 이유를 정확하게 이해하기 어렵게 합니다.또, 다른 타입의 폭을 가지는 실장에도 휴대할 수 있는 것을 검증하는 것도 어렵습니다.Java와 같은 "안전한" 언어와는 달리 C는 매우 안전하지 않고 플랫폼마다 구현 정의 사항이 다릅니다. 그러나 많은 개발자들은 테스트할 수 있는 구현이 하나뿐입니다(특히 인터넷과 온라인 연속 통합 테스트 이전).
ISO C는 동작을 정의하지 않기 때문에 컴파일러는 UB가 없는 프로그램과의 호환성을 해치지 않고 새로운 동작을 확장으로 정의할 수 있습니다.그러나 모든 컴파일러가 지원하지 않는 한 휴대용 C 프로그램에서는 사용할 수 없습니다.적어도 gcc/clang/ICC에 의해 지원되는 GNU 확장으로 상상할 수 있었습니다.
또, 그러한 옵션은, 다음과 같이 약간 모순될 수 있습니다.-fwrapv
행동을 규정하는 거죠전체적으로 리터럴 타입을 지정하기 위한 편리한 구문이 있기 때문에 채택될 가능성은 낮다고 생각합니다.0x7fffffffUL + 1
를 제공하다unsigned long
32비트 부호 없는 정수로서 그 값에 충분한 폭이 보증됩니다).
그러나 이것은 현재의 디자인이 아닌 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 long
variable 및 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 int
2위와 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
'programing' 카테고리의 다른 글
포인터를 거부하는 배열 크기 매크로 (0) | 2022.08.17 |
---|---|
Ionic Vue 체크박스 (0) | 2022.08.17 |
데이터 제거 기능을 사용하여 중첩된 배열 '항목'의 데이터를 추가 및 삭제하는 방법 (0) | 2022.08.17 |
롱 API 호출이 완료되기 전에 경로를 변경하면 vuex 저장소를 덮어씁니다. (0) | 2022.08.17 |
C: 문자 포인터와 배열의 차이점 (0) | 2022.08.17 |