정수 오버플로로 인해 메모리 파손으로 정의되지 않은 동작이 발생합니까?
최근 C 및 C++에서 부호화된 정수 오버플로가 정의되지 않은 동작을 일으킨다는 것을 알게 되었습니다.
식을 평가하는 동안 결과가 수학적으로 정의되지 않았거나 해당 유형의 대표 가능한 값 범위에 포함되지 않은 경우 동작은 정의되지 않습니다.
저는 현재 정의되지 않은 동작의 이유를 이해하려고 합니다.여기서 정의되지 않은 동작이 발생하는 것은 정수가 너무 커서 기본 유형에 맞지 않을 때 자기 주변의 메모리를 조작하기 시작했기 때문이라고 생각했습니다.
그래서 저는 Visual Studio 2015에서 다음과 같은 코드로 이론을 테스트하기 위한 작은 테스트 프로그램을 만들기로 했습니다.
#include <stdio.h>
#include <limits.h>
struct TestStruct
{
char pad1[50];
int testVal;
char pad2[50];
};
int main()
{
TestStruct test;
memset(&test, 0, sizeof(test));
for (test.testVal = 0; ; test.testVal++)
{
if (test.testVal == INT_MAX)
printf("Overflowing\r\n");
}
return 0;
}
디버깅 모드에서 Visual Studio의 보호 문제를 방지하기 위해 스택 변수의 임시 패딩과 같은 구조를 사용했습니다.엔드리스 루프는 몇 가지 오버플로우를 일으킵니다.test.testVal
그리고 그것은 사실이지만, 넘쳐나는 것 그 자체 말고는 아무런 결과도 없다.
오버플로우 테스트를 실행하면서 메모리 덤프를 확인했는데 다음과 같은 결과가 나왔습니다.test.testVal
메모리 주소를 가지고 있다0x001CFAFC
):
0x001CFAE5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001CFAFC 94 53 ca d8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
보시는 바와 같이 지속적으로 오버플로가 발생하는 int 주변 메모리는 손상되지 않은 상태로 남아 있습니다.같은 출력으로 여러 번 테스트했습니다.오버플로우 메모리 주변에는 파손된 적이 없다.
여기서 무슨 일이 벌어지나요?변수 주변의 메모리에 손상이 없는 이유는 무엇입니까?test.testVal
이것이 어떻게 정의되지 않은 동작을 일으킬 수 있습니까?
저는 제 실수를 이해하려고 노력하고 있으며, 정수 오버플로우 중에 메모리 손상이 발생하지 않는 이유를 이해하려고 합니다.
당신은 정의되지 않은 행동의 이유를 오해하고 있다.그 이유는 정수 주변의 메모리 손상이 아니라 정수가 차지하는 크기와 항상 같은 크기를 차지하기 때문입니다. 그러나 기본 산술입니다.
부호 있는 정수는 2의 보수로 부호화할 필요가 없기 때문에 오버플로우 시에 어떤 일이 일어날지에 대한 구체적인 가이던스가 있을 수 없습니다.인코딩이나 CPU 동작이 다르면 트랩에 의한 프로그램 종료 등 오버플로의 결과가 달라질 수 있습니다.
정의되지 않은 모든 동작과 마찬가지로 하드웨어가 산술에 2의 덧셈을 사용하고 오버플로우 규칙을 정의한 경우에도 컴파일러는 이러한 규칙에 구속되지 않습니다.예를 들어, GCC는 오랫동안 2의 보완 환경에서만 실현되는 모든 체크를 최적화했습니다.예를 들어.if (x > x + 1) f()
서명된 오버플로는 정의되지 않은 동작이기 때문에 최적화된 코드에서 삭제됩니다(컴파일러의 관점에서 볼 때 프로그램은 정의되지 않은 동작을 생성하는 코드를 포함하지 않습니다).x
보다 더 클 수 없다x + 1
.
일부 하드웨어 플랫폼이 예측할 수 없는 결과를 초래할 수 있는 방법(임의의 코드 실행과 그에 따른 메모리 손상 포함)으로 덫에 걸릴 수 있기 때문에 이 기준서의 작성자들은 정수 오버플로를 정의하지 않았다.예측 가능한 사일런트 랩 오버플로우 처리를 갖춘 2개의 보완 하드웨어는 C89 표준이 발표될 무렵에는 거의 표준으로 확립되어 있었지만(제가 조사한 많은 재프로그래밍 가능한 마이크로컴퓨터 아키텍처 중 다른 아키텍처는 전혀 사용되지 않습니다) 이 표준의 저자들은 누구도 C 임플렘을 생성하지 못하도록 하고 싶지 않았습니다.오래된 기계에 부착되어 있습니다.
일반적인 두 가지 보완 사일런트 랩 시멘틱스를 구현한 구현에서는 다음과 같은 코드와
int test(int x)
{
int temp = (x==INT_MAX);
if (x+1 <= 23) temp+=2;
return temp;
}
INT_MAX에 1을 더하면 INT_MIN이 생성되기 때문에 INT_MAX 값을 넘었을 때 100% 확실하게 3이 반환됩니다.
1990년대에 컴파일러는 정수 오버플로는 2의 보완 래핑으로 정의되는 것이 아니라 정의되지 않은 동작이라는 사실을 사용하여 다양한 최적화를 가능하게 했다. 즉, 오버플로우된 계산의 정확한 결과는 예측할 수 없지만 정확한 결과에 의존하지 않는 동작의 양상은 r에 머무르게 된다.위의 코드를 가진 1990년대 컴파일러는 INT_MAX에 1을 더하면 INT_MAX보다 큰 값이 수치적으로 산출되는 것처럼 처리될 수 있습니다.따라서 함수는 3이 아닌 1을 반환하거나 오래된 컴파일러처럼 동작하여 3을 산출할 수 있습니다.(x+1 <= 23)은 (x <= 22)와 같기 때문에 위의 코드에서 이러한 처리는 많은 플랫폼에서 명령을 저장할 수 있습니다.컴파일러는 1 또는 3 중 하나를 선택할 때 일관성이 없을 수 있지만 생성된 코드는 이러한 값 중 하나를 산출하는 것 이외에는 아무것도 수행하지 않습니다.
그러나 그 이후로 컴파일러가 정수 오버플로우(결과 예측이 불가능한 하드웨어의 존재로 인한 오류)의 경우 프로그램 동작에 대한 어떠한 요구사항도 부과하지 않는 것이 더 유행하게 되었다.넘칩니다.최신 컴파일러는 x==의 경우 프로그램이 정의되지 않은 동작을 호출한다는 것을 알아차릴 수 있습니다.INT_MAX, 즉 함수가 이 값을 전달받지 못할 것이라는 결론을 내립니다.함수가 이 값을 통과하지 않으면 INT_MAX와의 비교를 생략할 수 있습니다.위의 함수가 x==의 다른 번역 유닛에서 호출된 경우따라서 INT_MAX는 0 또는 2를 반환할 수 있습니다.같은 변환 유닛 내에서 호출되면 컴파일러가 x에 대한 추론을 다시 발신자에게까지 확장하기 때문에 그 효과는 더욱 기괴할 수 있습니다.
오버플로로 인해 메모리 파손이 발생하는지 여부에 대해서는 일부 오래된 하드웨어에서 발생할 수 있습니다.최신 하드웨어에서 실행되는 오래된 컴파일러에서는 그렇지 않습니다.초현대 컴파일러에서 오버플로는 시간과 인과관계를 부정하기 때문에 모든 베팅은 무효입니다.x+1 평가의 오버플로에 의해 INT_MAX와의 이전 비교에서 볼 수 있던x 값이 실질적으로 파손되어 메모리 내의 x 값이 파손된 것처럼 동작할 수 있습니다.게다가 이러한 컴파일러 동작에 의해서, 다른 종류의 메모리 파손을 막을 수 있는 조건부 로직이 삭제되는 경우가 많아, 임의의 메모리 파손이 발생합니다.
정의되지 않은 동작이 정의되지 않았습니다.프로그램이 중단될 수 있습니다.그것은 전혀 효과가 없을지도 모른다.당신이 기대했던 대로 될 수도 있어요.코의 악마를 불러올지도 모른다.모든 파일이 삭제될 수 있습니다.컴파일러는 정의되지 않은 동작이 발생했을 때 원하는 코드(또는 전혀 없음)를 자유롭게 내보낼 수 있습니다.
정의되지 않은 동작의 인스턴스는 정의되지 않은 조작뿐만 아니라 프로그램 전체를 정의하지 못하게 합니다.따라서 컴파일러는 프로그램의 어느 부분에 대해서도 원하는 대로 할 수 있습니다.시간 여행 포함:정의되지 않은 행동은 시간 여행을 초래할 수 있습니다(다른 것들 중에서도 시간 여행이 가장 재미있습니다).
정의되지 않은 행동에 대한 많은 답변과 블로그 게시물이 있지만, 제가 좋아하는 것은 다음과 같습니다.주제에 대해 더 알고 싶다면 그것들을 읽을 것을 제안합니다.
난해한 최적화의 결과 외에도 최적화되지 않은 컴파일러가 발생하기를 순진하게 기대하는 코드에서도 다른 문제를 고려해야 합니다.
2개의 보완(또는 기타) 아키텍처를 알고 있더라도 오버플로 작업이 예상대로 플래그를 설정하지 않을 수 있으므로 다음과 같은 문장이 있습니다.
if(a + b < 0)
두 개의 큰 양의 숫자를 더하면 그 값이 넘치고, 따라서 두 개의 수정 순수주의자들은 음수라고 주장하지만, 덧셈 명령이 실제로 음의 플래그를 설정하지 않을 수 있습니다.)다단계 연산은 각 단계에서 잘리지 않고 sizeof(int)보다 넓은 레지스터에서 일어났을 수 있으며, 따라서 다음과 같은 식입니다.
(x << 5) >> 5
나머지 5비트를 예상대로 잘라내지 못할 수도 있습니다.곱셈 및 나눗셈 연산은 제품의 추가 비트 및 배당에 보조 레지스터를 사용할 수 있습니다.다중 "할 수 없는" 오버플로우인 경우 컴파일러는 세컨더리 레지스터를 0(부정 제품의 경우 -1)이라고 가정하고 분할하기 전에 리셋하지 않습니다.그래서 이런 표현은
x * y / z
예상보다 넓은 중간 제품을 사용할 수 있습니다.
이들 중 일부는 높은 정확도로 들리지만, 이는 예상할 수 없고, 예측할 수도 없고, 신뢰할 수도 없으며, "각 연산은 N비트의 2개의 보완 연산자를 받아들여 다음 연산을 위해 결과의 최하위 N비트를 반환한다"는 사고 모델에 위배됩니다.
정수 오버플로 동작은 C++ 표준으로 정의되어 있지 않습니다.즉, C++의 실장은 자유롭게 실행할 수 있습니다.
실제로 이는 구현자에게 가장 편리한 것을 의미합니다.그리고 대부분의 구현자가 다음과 같이 처리하기 때문에int
두 개의 계산값으로서, 오늘날 가장 일반적인 실행은 두 개의 양수의 넘치는 합이 진정한 결과와 어느 정도 관련이 있는 음수라고 말하는 것이다.이것은 오답이고 표준에서는 무엇이든 허용하기 때문에 표준에서는 허용됩니다.
정수 오버플로는 정수 나눗셈을 0으로 하는 것과 마찬가지로 오류로 취급해야 한다는 주장이 있다.86년식 아키텍처는INTO
오버플로우 예외를 발생시키는 명령입니다.이 인수는 어느 시점에서 메인스트림 컴파일러로 만들기에 충분한 무게가 부여되어 정수 오버플로로 인해 크래시가 발생할 수 있습니다.이것은, 실장에서는 어떠한 조작도 가능하게 하는 C++ 규격에도 준거하고 있습니다.
숫자가 리틀 엔디언 방식으로 늘 종단 문자열로 표현되고 0바이트가 "번호 끝"을 나타내는 아키텍처를 상상할 수 있습니다.0바이트에 도달할 때까지 바이트 단위로 추가함으로써 추가할 수 있습니다.이러한 아키텍처에서 정수 오버플로는 후행 0을 1로 덮어쓰기 때문에 결과가 훨씬 더 길어 보이고 미래에 데이터가 손상될 수 있습니다.이것은 C++ 규격에도 준거하고 있습니다.
마지막으로, 일부 응답에서 지적된 바와 같이, 많은 코드 생성과 최적화는 생성되는 코드와 그 실행 방법에 대한 컴파일러의 추론에 의존합니다.정수 오버플로의 경우, (a) 큰 양의 숫자를 더했을 때 부정적인 결과를 주는 덧셈용 코드를 생성하는 것과 (b) 큰 양의 숫자를 더하면 긍정적인 결과를 준다는 것을 알고 그 코드 생성에 통지하는 것은 전적으로 컴파일러에 대한 라이선스이다.예를 들어
if (a+b>0) x=a+b;
둘 다 a
★★★★★★★★★★★★★★★★★」b
가 없습니다. 무조건 「」, 「검사」, 「검사」, 「검사」를 추가합니다.a
로로 합니다.b
그 를 리그 and and and에...x
이 2개의 보완기에 될 수 있습니다.x
이치노이치노
이 어떤 .int
당신이 생각했던 것처럼 기억 속에 '오버플로'는 없습니다.
언급URL : https://stackoverflow.com/questions/37325524/does-integer-overflow-cause-undefined-behavior-because-of-memory-corruption
'programing' 카테고리의 다른 글
Vuex를 사용하여 항목이 개체 배열의 일부인 경우 어레이에서 항목을 제거하려면 어떻게 해야 합니까? (0) | 2022.07.12 |
---|---|
클릭 시 v-date-picker v-menu를 열고 v-text-field에 초점을 맞춥니다. (0) | 2022.07.12 |
C의 int에 long int를 할당하면 어떻게 됩니까? (0) | 2022.07.12 |
autoconf 및 autotools 대체 방법 (0) | 2022.07.12 |
vuetify 데이터 테이블에서 행 내부의 버튼 클릭 이벤트와 행 클릭 이벤트를 분리하는 방법 (0) | 2022.07.12 |