루프의 어느 시점에서 정수 오버플로가 정의되지 않은 동작이 됩니까?
이 예는 여기에 올릴 수 없는 훨씬 더 복잡한 코드와 관련된 제 질문을 설명하기 위한 것입니다.
#include <stdio.h>
int main()
{
int a = 0;
for (int i = 0; i < 3; i++)
{
printf("Hello\n");
a = a + 1000000000;
}
}
이 프로그램에는 다음 이유로 플랫폼에서 정의되지 않은 동작이 포함되어 있습니다.a
3번째 루프에서는 오버플로가 발생합니다.
그러면 프로그램 전체가 정의되지 않은 동작을 하게 됩니까, 아니면 실제로 오버플로가 발생한 후에만 발생합니까?컴파일러가 잠재적으로 해결할 수 있을까요?a
루프 전체가 정의되어 있지 않다고 선언할 수 있도록 오버플로우 전에 printfs가 모두 발생하더라도 printfs는 오버플로우 전에 발생합니다.
(태그 부착 C와 C++는 다르지만, 양쪽 언어의 답변에 관심이 있기 때문에)
순수하게 이론적인 답변에 관심이 있는 경우, C++ 표준은 정의되지 않은 동작을 "시간 여행"할 수 있도록 합니다.
[intro.execution]/5:
적절한 형식의 프로그램을 실행하는 적합 실장은 동일한 프로그램 및 동일한 입력을 가진 추상기계의 해당 인스턴스의 가능한 실행 중 하나와 동일한 관찰 가능한 동작을 생성해야 한다.단, 이러한 실행이 정의되지 않은 동작을 포함하는 경우, 본 국제표준은 해당 입력으로 프로그램을 실행하는 구현에 대해 어떠한 요구사항도 두지 않는다(첫 번째 정의되지 않은 동작 이전의 동작에 대해서도 마찬가지).
따라서 프로그램에 정의되지 않은 동작이 포함되어 있으면 전체 프로그램의 동작이 정의되지 않습니다.
먼저 이 질문의 제목을 수정하겠습니다.
정의되지 않은 동작은 (구체적으로) 실행 영역의 것이 아닙니다.
정의되지 않은 동작은 컴파일, 링크, 로드 및 실행 등 모든 단계에 영향을 미칩니다.
이를 강화하기 위한 몇 가지 예는 모든 섹션이 완전하지 않다는 점에 유의하십시오.
- 컴파일러는 정의되지 않은 동작을 포함하는 코드의 일부가 절대 실행되지 않는다고 가정할 수 있으며, 따라서 그것들을 이끄는 실행 경로가 데드 코드라고 가정할 수 있습니다.다름 아닌 Chris Ratner의 정의되지 않은 동작에 대해 모든 C 프로그래머가 알아야 할 것을 참조하십시오.
- 링커는 약한 기호(이름으로 인식됨)의 여러 정의가 존재하는 경우 단일 정의 규칙 덕분에 모든 정의가 동일하다고 가정할 수 있다.
- 로더(다이나믹라이브러리를 사용하는 경우)는 같은 것을 상정할 수 있기 때문에, 최초로 검출된 기호를 선택할 수 있습니다.이것은 통상, 다음의 방법을 사용해 콜을 대행 수신하기 위해서 사용됩니다.
LD_PRELOAD
Unix에서의 트릭 - 덩글링 포인터를 사용하면 SIGSEV(실행 실패)가 발생할 수 있음
이것이 정의되지 않은 동작의 가장 무서운 점입니다.어떤 동작이 일어날지 미리 예측하는 것은 거의 불가능하며, 이 예측은 툴 체인, 기본 OS 등의 업데이트 시마다 재검토해야 합니다.
Michael Spencer(LLVM 개발자)의 이 비디오를 볼 것을 권장합니다.CppCon 2016: My Little Optimizer: Undefined Behavior is Magic.
16비트를 타깃으로 한 적극적으로 최적화된 C 또는 C++ 컴파일러int
추가 시 동작이1000000000
에 대해서int
유형이 정의되지 않았습니다.
어느 기준에서든 프로그램 전체를 삭제하는 것을 포함하여 원하는 모든 작업을 수행할 수 있습니다.int main(){}
.
하지만 더 큰 것은?int
s? 아직 컴파일러를 할 수 있는 것은 모릅니다(그리고 저는 C와 C++ 컴파일러 설계의 전문가는 아닙니다).하지만 언젠가 32비트를 대상으로 하는 컴파일러가int
또는 그 이상은 루프가 무한하다는 것을 알게 됩니다(i
변하지 않는다) 등a
결국엔 넘치게 됩니다.따라서 다시 한 번 출력을 최적화하여int main(){}
여기서 말씀드리고 싶은 것은 컴파일러 최적화가 점점 더 공격적으로 진행됨에 따라 점점 더 많은 정의되지 않은 동작 구조가 예기치 않은 방식으로 나타나고 있다는 것입니다.
루프 본체의 표준 출력에 쓰고 있기 때문에 루프가 무한하다는 사실 자체는 정의되지 않습니다.
엄밀히 말하면 C++ 규격에서는 프로그램에 정의되지 않은 동작이 포함되어 있으면 컴파일 시(프로그램 실행 전)에도 프로그램 전체의 동작이 정의되지 않는다.
실제로 컴파일러는 (최적화의 일부로서) 오버플로가 발생하지 않는다고 가정할 수 있기 때문에 적어도 루프의 세 번째 반복(32비트 머신을 상정)에 대한 프로그램의 동작은 정의되지 않습니다.다만, 세 번째 반복 전에 올바른 결과를 얻을 가능성이 있습니다.그러나, 프로그램 전체의 동작이 기술적으로 정의되어 있지 않기 때문에, 프로그램이 완전히 잘못된 출력을 생성하거나(출력 없음), 실행중의 어느 시점에서 크래시 하거나(정의되지 않은 동작이 컴파일 시간을 연장하는 경우) 전혀 막을 수 없습니다.
정의되지 않은 동작은 코드가 수행해야 하는 작업에 대한 특정 가정을 없애기 때문에 컴파일러에 최적화할 수 있는 더 많은 공간을 제공합니다.이렇게 하면 정의되지 않은 동작과 관련된 가정에 의존하는 프로그램이 예상대로 동작하는 것을 보장할 수 없습니다.따라서 C++ 표준에 따라 정의되지 않은 것으로 간주되는 특정 동작에 의존해서는 안 됩니다.
정의되지 않은 행동이 @TartanLama의 표현대로 '시간 여행'할 수 있는 이유를 이해하기 위해 'as-if' 규칙을 살펴보겠습니다.
1.9 프로그램 실행
1 이 국제 표준의 의미 설명은 매개 변수화된 비결정론적 추상 기계를 정의한다.본 국제표준은 적합 구현 구조에 대해 어떠한 요구사항도 두지 않는다.특히 추상 기계의 구조를 복사하거나 에뮬레이트할 필요가 없습니다.대신, 다음과 같이 추상 머신의 관찰 가능한 동작을 에뮬레이트(만)하기 위해 적합한 구현이 필요합니다.
이를 통해 프로그램을 입력과 출력이 있는 '블랙박스'로 볼 수 있습니다.입력에는 사용자 입력, 파일 및 기타 많은 것이 포함될 수 있습니다.출력은 표준에 언급된 '관측 가능한 동작'입니다.
표준에서는 입력과 출력 사이의 매핑만 정의하고 있습니다.예를 들어 '블랙박스 예'를 설명함으로써 이 작업을 수행하지만, 동일한 매핑을 가진 다른 블랙박스는 동등하게 유효하다고 명시적으로 말합니다.이것은 블랙박스의 내용이 무관하다는 것을 의미한다.
이것을 염두에 두고, 정의되지 않은 행동이 특정 순간에 일어난다고 말하는 것은 말이 되지 않을 것이다.블랙박스의 구현 예에서는 언제 어디서 발생하는지 알 수 있지만 실제 블랙박스는 전혀 다른 것이 될 수 있기 때문에 더 이상 어디서 언제 발생하는지 알 수 없습니다.이론적으로 컴파일러는 예를 들어 가능한 모든 입력을 열거하고 결과 출력을 미리 계산할 수 있습니다.그러면 컴파일 중에 정의되지 않은 동작이 발생하게 됩니다.
정의되지 않은 동작은 입력과 출력 사이의 매핑이 존재하지 않는 것입니다.프로그램은 일부 입력에 대해 정의되지 않은 동작을 가질 수 있지만 다른 입력에 대해서는 정의된 동작을 가질 수 있습니다.입력과 출력의 매핑은 완전하지 않습니다.출력과의 매핑이 존재하지 않는 입력이 있습니다.
문제의 프로그램이 입력에 대한 동작을 정의하지 않았기 때문에 매핑이 비어 있습니다.
가정하다int
는 32비트입니다.정의되지 않은 동작은 세 번째 반복에서 발생합니다.따라서 예를 들어 루프가 조건부로만 도달 가능하거나 세 번째 반복 전에 조건부로 종료될 수 있는 경우 세 번째 반복에 실제로 도달하지 않는 한 정의되지 않은 동작은 없습니다.단, 정의되지 않은 동작의 경우 정의되지 않은 동작의 호출과 관련된 "과거" 출력을 포함하여 프로그램의 모든 출력이 정의되지 않습니다.예를 들어, 이 경우 출력에 3개의 "Hello" 메시지가 표시된다는 보장은 없습니다.
TartanLama의 답은 정확하다.정의되지 않은 동작은 컴파일 중에도 언제든지 발생할 수 있습니다.이것은 터무니없을지도 모르지만 컴파일러가 필요한 작업을 수행할 수 있도록 하는 것이 중요한 기능입니다.컴파일러가 되는 것이 항상 쉬운 것은 아닙니다.매번 명세서에 적힌 대로 해야 해하지만, 때때로 특정한 행동이 일어나고 있다는 것을 증명하는 것은 엄청나게 어려울 수 있다.정지하고 있는 문제를 기억하고 있는 경우는, 특정의 입력이 입력되었을 때에 무한 루프에 들어가는지를 증명할 수 없는 소프트웨어를 개발하는 것은, 비교적 간단한 일입니다.
컴파일러를 비관적으로 만들고 다음 명령이 문제처럼 중단되는 문제 중 하나가 될 수 있다는 두려움에 끊임없이 컴파일할 수 있지만 이는 합리적이지 않습니다.대신 컴파일러에 패스합니다.이러한 「정의되지 않은 동작」토픽에 대해서는, 어떠한 책임도 지지 않습니다.정의되지 않은 행동은 모두 매우 미묘하게 악랄한 행동들로 이루어져 있습니다. 그래서 우리는 그것들을 정말로 악랄한 정지 문제들과 구분하는 데 어려움을 겪습니다.
출처를 잃은 것은 인정하지만, 투고하는 것을 좋아하는 예가 있습니다.그래서 다시 설명해야 합니다.MySQL의 특정 버전에서 가져온 것입니다.MySQL에서는 사용자가 제공한 데이터로 채워진 순환 버퍼가 있었습니다.물론 데이터가 버퍼에 오버플로하지 않도록 하기 위해 다음 사항을 점검했습니다.
if (currentPtr + numberOfNewChars > endOfBufferPtr) { doOverflowLogic(); }
멀쩡해 보여요.단, number Of New Chars가 너무 커서 오버플로우가 발생하면 어떻게 해야 할까요?그 후, 랩을 감아, 보다 작은 포인터가 됩니다.endOfBufferPtr
오버플로 로직은 호출되지 않습니다.그래서 그들은 그 전에 두 번째 점검을 추가했다.
if (currentPtr + numberOfNewChars < currentPtr) { detectWrapAround(); }
버퍼 오버플로 에러는 해결한 것 같죠?그러나 이 버퍼가 특정 버전의 Debian에서 오버플로우되었음을 나타내는 버그가 제출되었습니다.세심한 조사 결과 이 버전의 Debian이 특히 최신 버전의 gcc를 사용한 첫 번째 버전인 것으로 나타났습니다.이 버전의 gcc에서는 포인터의 오버플로는 정의되지 않은 동작이기 때문에 currentPtr + numberOfNewChars는 currentPtr보다 작은 포인터가 될 수 없다는 것을 컴파일러가 인식했습니다.이는 gcc가 전체 체크를 최적화하기에 충분했고, 체크하기 위해 코드를 작성했는데도 갑자기 버퍼 오버플로로부터 보호되지 않았습니다.
이것은 스펙의 동작입니다.모든 것이 합법적이었습니다(단, 다음 버전에서 gcc가 이 변경을 롤백했다고 들었습니다).직관적인 동작이라고는 생각되지 않지만 상상력을 조금만 확장해 보면 컴파일러에게 이 상황의 약간의 변형이 정지 문제가 될 수 있다는 것을 쉽게 알 수 있습니다.이 때문에 스펙 라이터들은 그것을 "정의되지 않은 행동"으로 만들고 컴파일러가 원하는 것은 무엇이든 할 수 있다고 말했다.
이론적인 답변 외에도 컴파일러가 오랜 시간 동안 루프에 다양한 변환을 적용하여 작업량을 줄였다는 것이 실질적인 관측입니다.예를 들어 다음과 같습니다.
for (int i=0; i<n; i++)
foo[i] = i*scale;
컴파일러는 이를 다음과 같이 변환할 수 있습니다.
int temp = 0;
for (int i=0; i<n; i++)
{
foo[i] = temp;
temp+=scale;
}
따라서 루프가 반복될 때마다 곱셈이 저장됩니다.컴파일러가 다양한 공격성으로 적응한 추가 최적화 형태는 다음과 같습니다.
if (n > 0)
{
int temp1 = n*scale;
int *temp2 = foo;
do
{
temp1 -= scale;
*temp2++ = temp1;
} while(temp1);
}
오버플로우 시 사일런트랩어라운드가 있는 머신에서도 n보다 작은 숫자가 있으면 오동작할 수 있습니다.이 숫자는 스케일 곱하면 0이 됩니다.메모리에서 스케일이 여러 번 읽혀지고 뭔가 예기치 않게 값이 변경된 경우(UB를 호출하지 않고 "스케일"이 중간 루프에서 변경될 수 있는 경우 컴파일러는 최적화를 수행할 수 없습니다).
이러한 최적화의 대부분은 INT_MAX+1과 UINT_MAX 사이의 값을 얻기 위해 2개의 짧은 부호 없는 타입을 곱하는 경우에는 문제가 없지만 gcc는 루프 내에서 이러한 곱셈으로 인해 루프가 조기 종료되는 경우가 있습니다.생성된 코드의 비교 명령에서 발생하는 이러한 동작은 알아차리지 못했지만, 컴파일러가 오버플로우를 사용하여 루프가 최대 4회 이상 실행될 수 있다고 추론하는 경우에서 관찰할 수 있습니다.기본적으로 어떤 입력은 UB의 원인이 되고 다른 입력은 그렇지 않은 경우에도 경고를 생성하지 않습니다.무시되는 루프의 상한.
정의되지 않은 동작은 정의상 회색 영역입니다.그것이 무엇을 할지 무엇을 하지 않을지 단순히 예측할 수 없습니다.그게 바로 '정의되지 않은 행동'의 의미입니다.
태고적부터 프로그래머들은 항상 정의되지 않은 상황에서 정의의 잔재를 구하려고 노력해 왔다.그들은 정말로 사용하고 싶은 코드가 몇 개 있지만, 그것은 정의되지 않은 것으로 판명되었습니다.그래서 그들은 이렇게 주장하려고 합니다. "정의되지 않은 것은 알지만, 최악의 경우, 분명 이렇게 하거나 이렇게 할 것입니다. 절대 그렇게 하지 않을 것입니다."그리고 때로는 이런 주장들이 어느 정도 옳을 때도 있지만, 종종 틀립니다.그리고 컴파일러가 점점 더 똑똑해지고(혹은 어떤 사람들은 점점 더 교묘해지고 있다고 말할 수 있다) 질문의 경계는 계속해서 변화하고 있습니다.
즉, 확실하게 동작할 수 있는 코드를 작성하고 싶다면, 그리고 장시간 동작할 수 있는 것은 단 한 가지 선택입니다.무슨 일이 있어도 정의되지 않은 동작을 피하는 것입니다.확실히, 만지면, 그것은 다시 당신을 괴롭힐 것입니다.
이 예에서 고려하지 않은 한 가지는 최적화입니다. a
루프는 설정되지만 사용되지 않습니다. 옵티마이저가 해결할 수 있습니다.따라서 낙관자가 폐기하는 것은 정당합니다.a
그 경우엔 정의되지 않은 모든 행동들이 부줌의 피해자처럼 사라지죠
그러나 최적화가 정의되어 있지 않기 때문에 이 자체는 정의되어 있지 않습니다.:)
이 질문은 C와 C++의 듀얼 태그가 붙어 있기 때문에, 양쪽 모두에 대응해 보겠습니다.여기서 C와 C++는 다른 접근방식을 취합니다.
C에서 구현은 프로그램 전체를 정의되지 않은 동작으로 취급하기 위해 정의되지 않은 동작이 호출된다는 것을 증명할 수 있어야 한다.OP의 예에서는 컴파일러가 그것을 증명하는 것은 사소한 것으로 보이기 때문에 프로그램 전체가 정의되어 있지 않은 경우 그대로입니다.
이는 결함 보고서 109에서 확인할 수 있으며, 이 보고서에서는 다음과 같이 질문합니다.
그러나 C 표준이 "정의되지 않은 값"(단순히 "정의되지 않은 행동"을 수반하지 않음)의 별개의 존재를 인정하는 경우, 컴파일러 테스트를 수행하는 사람은 다음과 같은 테스트 케이스를 작성할 수 있으며, 적합 구현이 최소한 컴파일해야 한다고 예상(또는 가능한 요구)할 수도 있다.이 코드(및 실행 가능)를 「실패」하지 않게 합니다.
int array1[5]; int array2[5]; int *p1 = &array1[0]; int *p2 = &array2[0]; int foo() { int i; i = (p1 > p2); /* Must this be "successfully translated"? */ 1/0; /* Must this be "successfully translated"? */ return 0; }
결론은 다음과 같습니다.위의 코드를 "정상적으로 번역"해야 합니까? (그것이 무엇을 의미하든)(하위 절 5.1.1.3에 첨부된 각주를 참조한다.)
답변은 다음과 같습니다.
C 기준서는 "정의되지 않은 가치"가 아니라 "결정적으로 평가되지 않은 가치"라는 용어를 사용한다.값이 미확정인 개체를 사용하면 정의되지 않은 동작이 발생합니다.하위 절 5.1.1.3의 각주는 유효한 프로그램이 여전히 올바르게 번역되어 있는 한 구현은 임의의 수의 진단을 생성할 수 있다고 지적한다.에바에 의해 정의되지 않은 동작이 발생하는 식이 상수 표현이 필요한 컨텍스트에 나타나는 경우 포함 프로그램은 엄밀하게 준거하지 않습니다.게다가 특정 프로그램의 가능한 모든 실행이 정의되지 않은 동작을 초래하는 경우, 해당 프로그램은 엄밀하게 준거하고 있지 않습니다.순응 실장은 엄밀하게 순응하는 프로그램을 번역하는 데 실패해서는 안 됩니다.단순히 그 프로그램을 실행할 수 있는 몇 가지 이유가 정의되지 않은 동작을 초래하기 때문입니다. foo는 호출되지 않을 수 있으므로 제시된 예는 적합한 구현에 의해 정상적으로 변환되어야 합니다.
C++에서는 접근법이 좀 더 완화되어 보이고 구현이 정적으로 증명할 수 있는지 여부에 관계없이 프로그램이 정의되지 않은 동작을 제안할 수 있습니다.
[intro.abstrac]p5에는 다음과 같은 내용이 있습니다.
적절한 형식의 프로그램을 실행하는 적합 실장은 동일한 프로그램 및 동일한 입력을 가진 추상기계의 해당 인스턴스의 가능한 실행 중 하나와 동일한 관찰 가능한 동작을 생성해야 한다.단, 이러한 실행에 정의되지 않은 조작이 포함되어 있는 경우, 이 문서에서는 (첫 번째 정의되지 않은 조작 이전의 조작에 대해서도) 그 입력을 사용하여 프로그램을 실행하는 구현에 대한 요건을 부여하지 않습니다.
가장 큰 답은 잘못된(그러나 일반적인) 오해입니다.
정의되지 않은 동작은 런타임 속성*입니다."시간 여행"은 할 수 없다!
일부 작업은 (표준에 따라) 부작용이 발생하도록 정의되며 최적화될 수 없습니다.I/O 또는 액세스를 수행하는 작업volatile
변수는 이 범주에 속합니다.
단, 주의사항이 있습니다.UB는 이전 작업을 취소하는 동작을 포함하여 모든 동작이 될 수 있습니다.이것은, 경우에 따라서는, 이전의 코드를 최적화하는 것과 같은 결과를 가져올 수 있습니다.
실제로 이는 상위 답변의 인용문(강조된 내용)과 일치합니다.
적절한 형식의 프로그램을 실행하는 적합 실장은 동일한 프로그램 및 동일한 입력을 가진 추상기계의 해당 인스턴스의 가능한 실행 중 하나와 동일한 관찰 가능한 동작을 생성해야 한다.
단, 이러한 실행이 정의되지 않은 동작을 포함하는 경우, 본 국제표준은 해당 입력으로 프로그램을 실행하는 구현에 대한 요구사항을 부여하지 않는다(첫 번째 정의되지 않은 동작 이전의 동작에 대해서도 마찬가지).
네, 이 인용문에는 "첫 번째 정의되지 않은 작업 이전의 작업에 관해서도"라고 기재되어 있지만, 이는 단순히 컴파일된 코드뿐만 아니라 실행 중인 코드에 관한 것입니다.
결국 실제로 도달하지 않은 정의되지 않은 동작은 아무 것도 하지 않으며, 실제로 UB를 포함하는 라인에 도달하려면 UB 앞에 있는 코드가 먼저 실행되어야 합니다!
따라서 UB가 실행되면 이전 작업의 영향은 정의되지 않습니다.그러나 그 전까지는 프로그램의 실행이 명확하게 정의되어 있습니다.
단, 이 문제가 발생하는 프로그램의 모든 실행은 이전 작업을 수행했지만 영향을 미치지 않는 프로그램을 포함하여 동등한 프로그램으로 최적화될 수 있습니다.그 결과, 선행 코드는, 그 효과가 취소되는 것과 같게 될 때마다 최적화되어 버릴 수 있습니다.그렇지 않으면, 최적화 할 수 없습니다.예에 대해서는, 이하를 참조해 주세요.
*주의: 이는 컴파일 시 발생하는 UB와 모순되지 않습니다.컴파일러가 UB 코드가 항상 모든 입력에 대해 실행된다는 것을 증명할 수 있다면 UB는 컴파일 시간까지 연장할 수 있습니다.단, 이를 위해서는 모든 이전 코드가 최종적으로 반환된다는 것을 알아야 합니다.이것은 강력한 요건입니다.예/설명에 대해서는, 이하를 참조해 주세요.
이를 구체화하려면 다음 코드를 인쇄해야 합니다.foo
다음에 이어지는 정의되지 않은 동작에 관계없이 입력을 기다립니다.
printf("foo");
getchar();
*(char*)1 = 1;
단, 다음과 같은 보증은 없습니다.foo
UB가 발생한 후에도 화면에 남아 있거나 입력한 문자가 더 이상 입력 버퍼에 포함되지 않습니다. 이 두 작업 모두 UB "시간 이동"과 유사한 효과가 있는 "unone"일 수 있습니다.
이 경우,getchar()
라인은 존재하지 않으며, 그것이 출력과 구별할 수 없는 경우에만 라인이 최적화되는 것이 합법적입니다.foo
그리고 나서 "하지 않는" 거죠.
이 둘을 구별할 수 없는지는 전적으로 구현(즉, 컴파일러와 표준 라이브러리)에 따라 달라집니다.예를 들어, 다음과 같은 경우printf
다른 프로그램이 출력을 읽기를 기다리는 동안 여기서 스레드를 차단하시겠습니까?아니면 바로 돌아올까요?
여기서 차단할 수 있으면 다른 프로그램이 전체 출력 읽기를 거부하여 다시 돌아오지 않을 수 있으며 결과적으로 UB가 실제로 발생하지 않을 수도 있습니다.
만약 그것이 바로 여기로 돌아올 수 있다면, 우리는 그것이 반드시 돌아와야 한다는 것을 알고 있습니다. 따라서 그것을 최적화하는 것은 그것을 실행하고 그 영향을 하지 않는 것과 완전히 구별할 수 없습니다.
물론, 컴파일러는 어떤 동작이 그것의 특정 버전에 허용되는지 알고 있기 때문에printf
, 그에 따라 최적화할 수 있으며, 그 결과적으로는printf
may get optimized out in some cases and not others. But, again, the justification is that this would be indistinguishable from the UB un-doing previous operations, not that the previous code is "poisoned" because of UB.
ReferenceURL : https://stackoverflow.com/questions/39914788/at-what-point-in-the-loop-does-integer-overflow-become-undefined-behavior
'programing' 카테고리의 다른 글
Vue.js - 상태에 따라 v-model을 라우팅 매개 변수에 동적으로 바인딩하는 방법 (0) | 2022.07.03 |
---|---|
Vue.js 3에서 TypeScript를 사용한 소품 입력 (0) | 2022.07.03 |
약속을 사용하여 Vue 앱을 렌더링하고 사용자 입력을 기다립니다. (0) | 2022.07.03 |
다중 응용 프로그램의 공유 저장소 Nuxt (0) | 2022.07.03 |
^ 연산자는 Java에서 무엇을 합니까? (0) | 2022.07.03 |