산술 오버플로가 왜 무시됩니까?


76

좋아하는 프로그래밍 언어로 1에서 2,000,000까지의 모든 숫자를 합한 적이 있습니까? 결과는 수동으로 쉽게 계산할 수 있습니다 (2,000,001,000,000). 부호없는 32 비트 정수의 최대 값보다 약 900 배 더 큽니다.

C #이 출력됩니다 -1453759936-음수! 그리고 Java도 마찬가지입니다.

이는 기본적으로 산술 오버플로를 무시하는 일반적인 프로그래밍 언어가 있음을 의미합니다 (C #에는이를 변경할 수있는 숨겨진 옵션이 있음). 그것은 나에게 매우 위험한 행동이며 그러한 오버플로로 인한 Ariane 5의 충돌이 아니 었습니까?

그렇다면 위험한 행동의 배후에있는 디자인 결정은 무엇입니까?

편집하다:

이 질문에 대한 첫 번째 답변은 과도한 검사 비용을 나타냅니다. 이 가정을 테스트하기 위해 짧은 C # 프로그램을 실행 해 보겠습니다.

Stopwatch watch = Stopwatch.StartNew();
checked
{
    for (int i = 0; i < 200000; i++)
    {
        int sum = 0;
        for (int j = 1; j < 50000; j++)
        {
            sum += j;
        }
    }
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);

내 컴퓨터에서 확인 된 버전은 11015ms, 확인되지 않은 버전은 4125ms가 걸립니다. 즉, 확인 단계는 숫자를 추가하는 데 거의 두 배가 걸립니다 (총 시간의 원래 시간의 3 배). 그러나 10,000,000,000 회의 반복으로 점검에 걸리는 시간은 여전히 ​​1 나노초 미만입니다. 중요한 상황이있을 수 있지만 대부분의 응용 프로그램에서는 중요하지 않습니다.

편집 2 :

서버 응용 프로그램 (여러 센서에서받은 데이터를 분석하는 Windows 서비스, 상당히 많은 수의 크 런칭)을 /p:CheckForOverflowUnderflow="false"매개 변수 (일반적으로 오버플로 검사를 켭니다)로 다시 컴파일 하여 장치에 배포했습니다. Nagios 모니터링에 따르면 평균 CPU로드는 17 %로 유지되었습니다.

이것은 위의 예제에서 찾은 성능 적중이 우리의 응용 프로그램과 전혀 관련이 없음을 의미합니다.


19
참고로 C #의 경우 checked { }섹션을 사용 하여 산술 오버플로 검사를 수행해야하는 코드 부분을 표시 할 수 있습니다 . 이것은 성능 때문입니다
Paweł Łukasik

14
"좋아하는 프로그래밍 언어로 1에서 2,000,000까지의 모든 숫자를 요약하려고 했습니까?" – 예 : (1..2_000_000).sum #=> 2000001000000. 내가 가장 좋아하는 언어 중 하나 : sum [1 .. 2000000] --=> 2000001000000. 내가 좋아하는 것이 아닙니다 : Array.from({length: 2000001}, (v, k) => k).reduce((acc, el) => acc + el) //=> 2000001000000. (공평하게, 마지막 것은 부정 행위입니다.)
Jörg W Mittag

27
IntegerHaskell의 @BernhardHiller 는 임의 정밀도이며 할당 가능한 RAM이 부족하지 않는 한 숫자를 보유합니다.
Polygnome

50
Ariane 5 충돌은 중요하지 않은 오버플로를 확인하여 발생했습니다. 로켓은 비행의 일부로 계산 결과가 더 이상 필요하지 않았습니다. 대신 오버플로가 감지되어 비행이 중단되었습니다.
사이먼 B

9
But with the 10,000,000,000 repetitions, the time taken by a check is still less than 1 nanosecond.그것은 루프가 최적화되었음을 나타냅니다. 또한 그 문장은 나에게 매우 유효한 이전 숫자와 모순됩니다.
usr

답변:


86

여기에는 3 가지 이유가 있습니다.

  1. 런타임시 오버플로 (모든 단일 산술 연산에 대한) 검사 비용이 너무 높습니다.

  2. 컴파일 타임에 오버플로 검사가 생략 될 수 있음을 증명하는 복잡성이 지나치게 높습니다.

  3. 경우에 따라 (예 : CRC 계산, 큰 라이브러리 등) "오버랩 오버랩"이 프로그래머에게 더 편리합니다.


10
unsigned int오버플로 검사 기능이있는 언어는 기본적으로 모든 정수 유형을 검사해야하므로 @DmitryGrigoryev 는 신경 쓰지 않아야합니다 . 당신은 작성해야합니다 wrapping unsigned int.
user253751

32
나는 비용 논쟁을 사지 않는다. CPU는 모든 단일 정수 계산에서 오버 플로우를 점검하고 ALU에서 캐리 플래그를 설정합니다. 누락 된 프로그래밍 언어 지원입니다. 캐리 플래그에 액세스 할 수 있는 간단한 didOverflow()인라인 함수 또는 전역 변수 __carry는 사용하지 않으면 CPU 시간이 0이됩니다.
slebetman

37
@ slebetman : x86입니다. ARM은 그렇지 않습니다. 예를 들어 ADD캐리를 설정하지 않습니다 (필요합니다 ADDS). Itanium은도하지 않습니다 캐리 플래그. x86에서도 AVX에는 캐리 플래그가 없습니다.
MSalters

30
@slebetman 캐리 플래그를 설정합니다 (예 : x86에서). 그러나 캐리 플래그를 읽고 결과를 결정해야합니다. 그것은 비싼 부분입니다. 산술 연산은 종종 루프 (그리고 꽉 조여진 루프)에서 사용되기 때문에 하나의 추가 명령어 만 필요한 경우에도 성능에 큰 영향을 줄 수있는 많은 안전한 컴파일러 최적화를 쉽게 방지 할 수 있습니다. ). 이것이 기본값이어야 함을 의미합니까? 어쩌면 말하기 unchecked가 쉬운 C #과 같은 언어에서는 특히 그렇습니다 . 그러나 오버플로가 얼마나 자주 발생하는지 과대 평가하고있을 수 있습니다.
루안

12
ARM의 adds가격 add은 캐리 플래그의 업데이트 여부를 선택하는 명령 1 비트 플래그 일뿐입니다. add오버플로에서 MIPS의 명령어 트랩- 대신 오버플로를 트랩하지 않도록 요청 해야 addu합니다!
user253751

65

누가 나쁜 상충이라고 말합니까?!

오버플로 검사가 활성화 된 모든 프로덕션 앱을 모두 실행합니다. 이것은 C # 컴파일러 옵션입니다. 나는 실제로 이것을 벤치마킹했고 그 차이를 결정할 수 없었습니다. 장난감이 아닌 HTML을 생성하기 위해 데이터베이스에 액세스하는 비용은 오버플로 검사 비용을 어둡게합니다.

프로덕션에서 오버플로가 발생하지 않는다는 것을 알고 있습니다. 오버플로가 발생하면 거의 모든 코드가 잘못 작동합니다. 버그는 양 성적이지 않습니다. 데이터가 손상 될 수 있으며 보안 문제가 발생할 수 있습니다.

때로는 성능과 같은 성능이 필요한 경우 unchecked {}세부적으로 사용하여 오버플로 검사를 비활성화 합니다. 오버플로되지 않는 작업에 의존한다고 부르고 싶을 때 checked {}그 사실을 문서화하기 위해 코드에 중복 적으로 추가 할 수 있습니다 . 나는 오버플로를 염두에 두지 만 검사에 감사 할 필요는 없습니다.

C # 팀은 기본적으로 오버플로를 확인 하지 않기로 선택했을 때 잘못된 선택을 했지만 호환성 문제로 인해 선택이 봉쇄되었습니다. 이 선택은 2000 년 무렵에 이루어 졌다는 점에 유의하십시오. 하드웨어는 성능이 떨어졌으며 .NET에는 아직 많은 관심이 없었습니다. .NET은 이런 식으로 Java 및 C / C ++ 프로그래머에게 호소하기를 원했습니다. .NET은 또한 금속에 가까워 야합니다. 그렇기 때문에 Java에없는 모든 코드, 구조체 및 기본 호출 기능이 안전하지 않은 이유가 있습니다.

하드웨어가 빨라지고 똑똑한 컴파일러는 기본적으로 오버플로 검사가 더 매력적입니다.

또한 오버플로 검사는 종종 무한 크기의 숫자보다 낫습니다. 무한한 규모의 숫자는 성능 비용이 훨씬 높고 최적화하기가 어렵고 (믿습니다) 무한한 자원 소비 가능성을 열어줍니다.

오버플로를 처리하는 JavaScript의 방식은 훨씬 더 나쁩니다. JavaScript 숫자는 부동 소수점 배가입니다. "오버 플로우"는 완전히 정확한 정수 세트를 남기는 것으로 나타납니다. 약간 잘못된 결과가 발생합니다 (예 : 하나만 끄는 경우-유한 루프를 무한 루프로 바꿀 수 있음).

C / C ++ 오버플로 검사와 같은 일부 언어의 경우 기본적으로 이러한 언어로 작성된 응용 프로그램 종류가 베어 메탈 성능을 필요로하기 때문에 부적절합니다. 그러나 더 안전한 모드 를 사용 하도록 허용하여 C / C ++를 더 안전한 언어로 만들려는 노력이 있습니다. 이는 코드의 90-99 %가 차가워지기 때문에 권장됩니다. 예를 들어 fwrapv2의 보수 래핑을 강제 하는 컴파일러 옵션이 있습니다. 이는 언어가 아니라 컴파일러의 "구현 품질"기능입니다.

Haskell에는 논리적 호출 스택과 지정된 평가 순서가 없습니다. 이로 인해 예측할 수없는 지점에서 예외가 발생합니다. 에서 a + b여부를 지정되지 않은 a또는 b먼저 평가하고 그 표현은 모두 여부에 종료 여부를 지정합니다. 따라서 Haskell은 대부분 무제한 정수를 사용하는 것이 좋습니다. 대부분의 Haskell 코드에서 예외는 실제로 부적절하기 때문에이 선택은 순수한 기능 언어에 적합합니다. 그리고 0으로 나누는 것은 실제로 Haskells 언어 디자인에서 문제가되는 부분입니다. 무제한 정수 대신 고정 너비 래핑 정수를 사용할 수 있었지만 언어 기능의 "정확성에 중점"테마와 맞지 않습니다.

오버플로 예외에 대한 대안은 정의되지 않은 작업에 의해 생성되고 작업을 통해 전달되는 포이즌 값 (예 : 부동 NaN값)입니다. 그것은 오버플로 검사보다 훨씬 비싸고 실패 할 수있는 것만이 아니라 모든 작업을 느리게 만듭니다 ( 아이테니엄은 NaT가 "Ting a Thing"이 아니더라도 일반적으로 플로트가 있고 int에는없는 하드웨어 가속을 제외하고 ). 또한 나쁜 데이터와 함께 프로그램이 계속 삐걱 거리는 점을 잘 알지 못합니다. 같습니다 ON ERROR RESUME NEXT. 오류를 숨기지 만 올바른 결과를 얻는 데 도움이되지 않습니다. supercat은 때때로 이것을 수행하는 것이 성능 최적화라고 지적합니다.


2
훌륭한 답변입니다. 그들이 왜 그렇게하기로 결정했는지에 대한 당신의 이론은 무엇입니까? C를 복사하고 궁극적으로 어셈블리와 바이너리를 복사 한 다른 모든 사람을 복사하는 것입니까?
jpmc26

19
사용자 기반의 99 %가 행동을 기대하면 행동을 취하는 경향이 있습니다. "C 복사"는 실제로 C의 사본이 아니라 그 확장입니다. C는 unsigned정수에 대해서만 예외없는 동작을 보장 합니다. 부호있는 정수 오버플로의 동작은 실제로 C 및 C ++에서 정의되지 않은 동작입니다. 예, 정의되지 않은 동작 입니다. 거의 모든 사람들이 2의 보수 오버플로로 구현합니다. C #은 C / C ++처럼 UB를 떠나지 않고 실제로 공식적으로 만듭니다.
Cort Ammon

10
@CortAmmon : Dennis Ritchie가 디자인 한 언어는 부호있는 정수에 대해 랩 어라운드 동작을 정의했지만 2가 아닌 보완 플랫폼에서 사용하기에는 적합하지 않았습니다. 정확한 2의 보수 랩 어라운드에서 특정 편차를 허용하면 일부 최적화를 크게 지원할 수 있지만 (예 : 컴파일러가 x * y / y를 x로 대체하면 곱셈과 나눗셈을 저장할 수 있음) 컴파일러 작성자는 정의되지 않은 동작을 수행 할 수있는 기회로 해석하지 않았습니다. 주어진 대상 플랫폼 및 응용 분야에 의미가있는 것이 아니라 오히려 유리한 기회를 제공합니다.
supercat

3
@CortAmmon은 -에 의해 생성 된 코드 확인 gcc -O2을 위해를 x + 1 > x(여기서 x입니다 int). gcc.gnu.org/onlinedocs/gcc-6.3.0/gcc/… 도 참조하십시오 . C에서 부호있는 오버플로에 대한 2s- 보완 동작 은 실제 컴파일러에서도 선택 사항 이며 gcc기본 최적화 수준에서는 무시하도록 기본 설정됩니다.
Jonathan Cast

2
@supercat 예, 대부분의 C 컴파일러 작성자는 프로그래머에게 합리적인 의미를 제공하는 것보다 비현실적인 벤치 마크를 0.5 % 더 빠르게 실행하는 데 더 관심이 있습니다 (그렇습니다. 해결하기 쉬운 문제가 아닌 이유를 이해할 수있는 이유를 이해합니다) 결합했을 때 예기치 않은 결과, 야다, 야다 그러나 여전히 초점이 없으며 대화를 따르면 그것을 알 수 있습니다). 다행스럽게도 더 잘하려고하는 사람들이 있습니다 .
Voo

30

오버플로 발생 하는 드문 경우를 자동으로 잡기 위해 모든 계산을 훨씬 더 비싸게 만드는 것은 나쁜 절충안이기 때문 입니다. 모든 프로그래머가 사용하지 않는 기능에 대해 가격을 지불 하게하는 것보다 문제가되는 드문 경우를 인식하고 특별한 예방책을 추가하여 프로그래머에게 부담을주는 것이 훨씬 좋습니다 .


28
버퍼 오버 플로우에 대한 검사는 거의 발생하지 않기 때문에 생략해야한다고 말하는 것과 같습니다.
Bernhard Hiller

73
@BernhardHiller : 이것이 바로 C와 C ++가하는 일입니다.
Michael Borgwardt

12
@DavidBrown : 산술 오버플로처럼. 전자는 VM을 손상시키지 않습니다.
중복 제거기

35
@Dupuplicator는 훌륭한 지적입니다. CLR은 확인 가능한 프로그램이 나쁜 일이 발생하더라도 런타임의 변형을 위반하지 않도록 신중하게 설계되었습니다 . 안전한 프로그램은 물론 나쁜 일이 발생하면 자신의 불변을 위반할 수 있습니다 .
Eric Lippert

7
@svick 산술 연산은 아마도 배열 인덱싱 연산보다 훨씬 일반적입니다. 그리고 대부분의 정수 크기는 오버플로되는 산술을 수행하기 매우 드물게 충분히 큽니다. 따라서 비용-이익 비율은 매우 다릅니다.
Barmar

20

그러한 위험한 행동의 배후에있는 디자인 결정은 무엇입니까?

"필요하지 않은 기능에 대해 사용자에게 성능 저하를 강요하지 마십시오."

C 및 C ++ 디자인에서 가장 기본적인 신조 중 하나이며, 오늘날 사소한 것으로 간주되는 작업에 대한 적절한 성능을 얻기 위해 어리석은 왜곡을 거쳐야 할 때가 다릅니다.

최신 언어는 배열 경계 검사와 같은 다른 많은 기능에 대해 이러한 태도를 가지고 있습니다. 오버플로 검사를 위해 왜 그렇게하지 않았는지 잘 모르겠습니다. 단순히 감독이 될 수도 있습니다.


18
C # 디자인에 대한 감독은 아닙니다. C #의 디자이너는 의도적으로 두 가지 모드를 만들었습니다. checked그리고 unchecked, 로컬로 전환하기위한 구문과 명령 줄 스위치 (및 VS의 프로젝트 설정)를 전역 적으로 변경하는 구문을 추가했습니다. 당신은 unchecked불이행 을 하는 것에 동의하지 않을 수도 있지만 (내가하는 것),이 모든 것은 분명히 아주 신중합니다.
svick

8
@ slebetman-단지 기록 : 여기의 비용은 오버플로를 검사하는 비용 (사소한)이 아니라 오버플로가 발생했는지에 따라 다른 코드를 실행하는 비용입니다 (매우 비쌉니다). CPU는 조건부 분기 명령문을 좋아하지 않습니다.
Jonathan Cast

5
@jcast 최신 프로세서에 대한 분기 예측이 그 조건부 분기 문 페널티를 거의 제거하지 않습니까? 모든 정상적인 경우에는 오버플로가 없어야하므로 예측 가능한 분기 동작입니다.
CodeMonkey

4
@CodeMonkey에 동의하십시오. 컴파일러는 오버플로가 발생하면 일반적으로로드 / 콜드되지 않은 페이지로 조건부 점프를 수행합니다. 이에 대한 기본 예측은 "취득되지 않음"이며 아마도 변경되지 않을 것입니다. 총 오버 헤드는 파이프 라인에서 하나의 명령입니다. 그러나 이는 산술 명령어 당 하나의 명령어 오버 헤드입니다.
MSalters

2
@MSalters 예, 추가 명령어 오버 헤드가 있습니다. 독점적으로 CPU 바운드 문제가있는 경우 영향이 클 수 있습니다. IO와 CPU 헤비 코드가 혼합 된 대부분의 응용 프로그램에서는 영향이 최소라고 가정합니다. Rust 방식을 좋아합니다. 디버그 빌드에서만 오버 헤드를 추가하지만 릴리스 빌드에서는 오버 헤드를 제거합니다.
CodeMonkey

20

유산

이 문제는 레거시에서 비롯된 것 같습니다. C에서 :

  • 부호있는 오버플로는 정의되지 않은 동작입니다 (컴파일러는 랩을 만들기 위해 플래그를 지원합니다).
  • 서명되지 않은 오버플로는 동작으로 정의됩니다 (줄 바꿈).

이는 프로그래머가 수행하는 작업을 알고 있다는 원칙에 따라 최상의 성능을 얻기 위해 수행되었습니다 .

Statu-Quo로 연결

C (및 확장 C ++)에 오버플로 감지가 필요하지 않다는 사실은 오버플로 검사가 느리다는 것을 의미합니다.

하드웨어는 대부분 C / C ++에 적합합니다 ( 확실히 x86에는 strcmp명령어 (일명 SSE 4.2 기준 PCMPISTRI )!). C는 신경 쓰지 않기 때문에 일반적인 CPU는 오버플로를 효율적으로 감지하지 못합니다. x86에서는 잠재적으로 넘칠 수있는 각 작업 후에 코어 별 플래그를 확인해야합니다. 실제로 원하는 것은 결과에 "오염 된"플래그입니다 (NaN이 전파되는 것처럼). 그리고 벡터 연산은 훨씬 더 문제가 될 수 있습니다. 일부 새로운 플레이어 는 효율적인 오버플로 처리로 시장에 등장 할 수 있습니다. 그러나 지금은 x86과 ARM은 신경 쓰지 않습니다.

컴파일러 최적화 프로그램은 오버플로 검사를 최적화하거나 오버플로가있을 때 최적화하는 데 능숙하지 않습니다. John Regher와 같은 일부 학자들은이 통계에 대해 불평 하지만 사실 오버플로 "실패"라는 간단한 사실로 인해 어셈블리가 시작되기 전에도 CPU 최적화가 방해 받게 된다는 사실이 있습니다. 특히 자동 벡터화를 방지 할 때 ...

계단식 효과

따라서 효율적인 최적화 전략과 효율적인 CPU 지원이 없으면 오버플로 검사 비용이 많이 듭니다. 포장보다 훨씬 비쌉니다.

그렇지 않은 x + y - 1경우 오버플 로와 같은 성가신 동작을 추가 하면 x - 1 + y합법적으로 사용자를 성가 시게 할 수 있으며 오버플로 검사는 일반적으로 래핑을 위해 버려집니다 (이 예제와 다른 많은 것들을 정상적으로 처리합니다).

그래도 모든 희망이 사라지는 것은 아닙니다

clang 및 gcc 컴파일러에서 "sanitizers"를 구현하려는 노력이있었습니다. 이진을 계측하여 정의되지 않은 동작의 사례를 감지하는 방법입니다. 를 사용 -fsanitize=undefined하면 부호있는 오버플로가 감지되어 프로그램이 중단됩니다. 테스트하는 동안 매우 유용합니다.

프로그래밍 언어를 사용할 수 오버플로 검사가 기본적으로 디버그 모드를 (가 성능상의 이유로 릴리스 모드에서 연산 포장 사용합니다).

따라서 오버플로 검사 및 가짜 결과의 위험이 감지되지 않을 것이라는 우려가 커지고 있으며, 이것이 연구 커뮤니티, 컴파일러 커뮤니티 및 하드웨어 커뮤니티에 대한 관심을 불러 일으킬 것입니다.


6
그 스웰에 예를 들면 오버 플로우를 확인하기위한 효과적인 방법의 반대이다 @DmitryGrigoryev 단지 1 체크 첨가 사이클 당 4 정상 가산에서 처리량을 감소시키고, 즉, 분기 예측 오류의 영향을 고려하기 전에있어 jo'들과 오염의 글로벌 효과가 분기 예측기 상태와 코드 크기 증가에 추가됩니다. 이 플래그가 끈적 거리면 실제 잠재력을 제공 할 것입니다. 그리고 여전히 벡터화 된 코드에서는 제대로 할 수 없습니다.

3
당신이에 연결하고 있기 때문에 블로그 포스트 존 리 제르에 의해 작성, 나는 또한 링크하는 것이 적절할 것이라고 생각 그의 기사의 또 다른 몇 개월이 연결된 하나 이전에 작성된. 이 기사는 다른 철학에 대해 이야기합니다. 이전 기사에서 정수는 고정 크기입니다. 정수 산술이 확인됩니다 (즉, 코드가 계속 실행될 수 없음). 예외 나 함정이 있습니다. 최신 기사에서는 고정 크기 정수를 모두 버리고 오버플로를 제거하는 방법에 대해 설명합니다.
rwong

2
@rwong 무한 크기의 정수에도 문제가 있습니다. 오버플로가 버그 (자주 발생하는 버그)의 결과 인 경우 모든 문제가 심각하게 실패 할 때까지 모든 서버 리소스를 사용하는 장기간의 고뇌로 빠른 충돌이 발생할 수 있습니다. 나는 대부분 "초기 실패"접근법의 팬입니다. 전체 환경에 중독 될 가능성이 적습니다. 1..100대신 Pascal-ish 유형을 선호합니다. 2 ^ 31 등으로 "강제"되는 대신 예상 범위를 명시하십시오. 일부 언어는 물론이를 제공하며 기본적으로 오버플로 검사를 수행하는 경향이 있습니다 (때로는 컴파일 타임, 짝수).
Luaan

1
@Luaan : 흥미로운 점은 종종 중간 계산이 일시적으로 오버플로 될 수 있지만 결과는 그렇지 않다는 것입니다. 예를 들어 1..100 범위에서는 결과가 맞더라도 51이 x * 2 - 2되면 오버플로가 발생 x하여 계산을 다시 정렬해야합니다 (때로는 부 자연스러운 방법으로). 내 경험상, 나는 일반적으로 계산을 더 큰 유형으로 실행하고 결과가 맞는지 확인하는 것을 선호한다는 것을 알았습니다.
Matthieu M.

1
@MatthieuM. 네, "충분히 똑똑한 컴파일러"영역으로 들어가는 곳입니다. 이상적으로, 값 1.103은 참 1..100이 예상되는 상황에서 사용되지 않는 한 1..100 유형에 유효해야합니다 (예 : 할당 결과가 유효한 1 인 x = x * 2 - 2경우 모두 작동해야 함) x. .100 번호). 즉, 숫자 유형에 대한 연산은 대입이 적합한 한 유형 자체보다 정밀도가 높을 수 있습니다. 이것은 (a + b) / 2(부호없는) 오버플로를 무시 하는 것이 올바른 옵션 수있는 경우에 매우 유용합니다 .
루안

10

오버플로를 감지하려고 시도하는 언어는 역사적으로 관련 의미를 정의하여 유용한 최적화가 무엇인지 심각하게 제한하는 방식으로 정의했습니다. 무엇보다도 코드에 지정된 것과 다른 순서로 계산을 수행하는 것이 유용한 경우가 많지만 오버플로를 잡는 대부분의 언어는 다음과 같은 주어진 코드를 보장합니다.

for (int i=0; i<100; i++)
{
  Operation1();
  x+=i;
  Operation2();
}

x의 시작 값으로 인해 루프를 통과 한 47 번째 패스에서 오버플로가 발생하는 경우 Operation1은 47 회 실행되고 Operation2는 46으로 실행됩니다. 이러한 보증이 없으면 루프 내에서 x를 사용하지 않는 경우 아무것도 없습니다. Operation1 또는 Operation2에서 예외가 발생하면 x 값을 사용하며 코드는 다음과 같이 대체 될 수 있습니다.

x+=4950;
for (int i=0; i<100; i++)
{
  Operation1();
  Operation2();
}

불행히도 루프 내에서 오버플로가 발생했을 경우 올바른 의미를 보장하면서 이러한 최적화를 수행하는 것은 본질적으로 다음과 같은 것이 필요합니다.

if (x < INT_MAX-4950)
{
  x+=4950;
  for (int i=0; i<100; i++)
  {
    Operation1();
    Operation2();
  }
}
else
{
  for (int i=0; i<100; i++)
  {
    Operation1();
    x+=i;
    Operation2();
  }
}

많은 실제 코드가 더 많은 루프를 사용한다고 생각하면 오버플로 의미를 유지하면서 코드를 최적화하는 것이 어렵다는 것이 분명합니다. 또한 캐싱 문제로 인해 일반적으로 실행되는 경로에 대한 작업이 적더라도 코드 크기가 증가하면 전체 프로그램이 더 느리게 실행될 수 있습니다.

오버플로 감지를 저렴하게 만드는 데 필요한 것은 느슨한 오버플로 감지 의미론의 정의 된 세트로, 결과에 영향을 줄 수있는 오버플로없이 계산을 수행했는지 (*) 부담없이 부담없이 코드를 쉽게보고 할 수 있도록 정의 된 느슨한 오버플로 감지 의미 집합입니다. 그 이상의 세부 사항을 가진 컴파일러. 언어 사양이 오버플로 감지 비용을 위의 최소값으로 줄이는 데 중점을 둔 경우 기존 언어보다 훨씬 저렴하게 만들 수 있습니다. 그러나 효율적인 오버플로 감지를 용이하게하기위한 어떠한 노력도 모릅니다.

(*) 언어에서 모든 오버플로가보고 될 것이라고 약속하면 오버플 x*y/yx하지 x*y않도록 보장 할 수없는 경우 와 같은 표현을 단순화 할 수 없습니다 . 마찬가지로 계산 결과를 무시하더라도 모든 오버플로를보고 할 언어는 오버플로 검사를 수행 할 수 있도록이를 수행해야합니다. 이러한 경우 오버플로는 산술적으로 부정확 한 동작을 초래할 수 없으므로 오버플로로 인해 잠재적으로 부정확 한 결과가 발생하지 않도록 프로그램에서 이러한 검사를 수행하지 않아도됩니다.

또한 C의 오버플로는 특히 나쁩니다. C99를 지원하는 거의 모든 하드웨어 플랫폼이 2의 보완 자동 랩 어라운드 시맨틱을 사용하지만, 최신 컴파일러는 오버 플로우시 임의의 부작용을 일으킬 수있는 코드를 생성하는 것이 유행입니다. 예를 들어, 다음과 같은 것이 주어집니다 :

#include <stdint.h>
uint32_t test(uint16_t x, uint16_t y) { return x*y & 65535u; }
uint32_t test2(uint16_t q, int *p)
{
  uint32_t total=0;
  q|=32768;
  for (int i = 32768; i<=q; i++)
  {
    total+=test(i,65535);
    *p+=1;
  }
  return total;
}

GCC는 test2에 대한 코드를 생성하여 무조건 증분 (* p) 한 번 증가하고 q에 전달 된 값에 관계없이 32768을 반환합니다. 추론에 따르면 (32769 * 65535) 및 65535u의 계산으로 오버플로가 발생하므로 컴파일러에서 (q | 32768)이 32768보다 큰 값을 생성하는 경우를 고려할 필요가 없습니다. (32769 * 65535) 및 65535u의 계산이 결과의 상위 비트를 처리해야하는 이유 때문에 gcc는 루프를 무시하기 위해 부호있는 오버플로를 정당화로 사용합니다.


2
"이것은 현대의 컴파일러에게는 유행이다 ..."-마찬가지로, 일부 유명한 커널 개발자들은 그들이 사용한 최적화 플래그에 관한 문서를 읽지 않고 인터넷을 통해 화를내는 것을 선택하는 것이 아주 유행했다 그들이 원하는 행동을 얻기 위해 더 많은 컴파일러 플래그를 추가해야하기 때문에 ;-). 이 경우 -fwrapv질문자가 원하는 동작은 아니지만 정의 된 동작 이 발생 합니다. 물론 gcc 최적화는 모든 종류의 C 개발을 표준 및 컴파일러 동작에 대한 철저한 시험으로 바꿉니다.
Steve Jessop

1
@SteveJessop : 컴파일러 정의 작성자가 "정의되지 않은 동작"이 "기본 플랫폼에서 의미가있는 것은 무엇이든"을 의미하는 저수준 방언을 인식 한 다음 프로그래머가 불필요한 보증을 암시하는 방법을 추가하면 C는 훨씬 더 건강한 언어가됩니다. 표준에서 "휴대 불가능 또는 오류"라는 문구가 단순히 "오류"를 의미한다고 가정하기보다는. 많은 경우에, 행동 보장이 약한 언어로 얻을 수있는 최적의 코드는 더 강한 보증 또는 보증이없는 것보다 훨씬 좋습니다. 예를 들어 ...
supercat

1
... 프로그래머가 x+y > zyield 0 또는 yield 1 이외의 다른 작업을 수행하지 않는 방식 으로 평가해야하는 경우 오버플로의 경우 결과가 동일하게 수용 될 수 있습니다. x+y > z어떤 컴파일러보다 식이 방어 적으로 작성된 버전의 식에 대해 생성 할 수 있습니다. 사실, 나누기 / 나머지 이외의 정수 계산이 부작용없이 실행될 것이라는 보장 으로 유용한 오버플로 관련 최적화의 일부를 배제 할 수 있습니까?
supercat

나는 세부 사항을 완전히 이해하지는 못했지만 고백은 일반적으로 "컴파일러 작가"와 관련이 있으며 특히 "내 -fwhatever-makes-sense패치를 수락하지 않는 gcc의 누군가"가 아니라는 사실은 더 많은 것이 있음을 강력히 제안합니다. 그들의 부분에 기발한 것보다. 내가 들었던 일반적인 주장은 코드 인라이닝 (및 매크로 확장)이 코드 구문의 특정 사용에 대해 가능한 한 많이 추론함으로써 이익을 얻는다는 것입니다. 일반적으로 필요하지 않은 경우를 처리하는 삽입 코드가 생성되기 때문입니다. 주변 코드가 불가능하다는 것을 증명합니다.
Steve Jessop

따라서 간단한 예를 들어, 작성 foo(i + INT_MAX + 1)하면 컴파일러 작성자는 foo()음수가 아닌 인수에 대한 정확성에 의존 하는 인라인 코드에 최적화를 적용하는 데 관심이 있습니다 (아마도 divmod 트릭). 추가 제한 사항에 따라 네거티브 입력에 대한 동작이 플랫폼에 적합한 최적화 만 적용 할 수 있습니다. 물론 개인적으로 나는 -f그것이 스위치 -fwrapv등을 켜는 옵션 이되어 기뻐하며 플래그가없는 일부 최적화를 비활성화해야합니다. 그러나 내가 직접 일하는 모든 일을 귀찮게하는 것은 아닙니다.
Steve Jessop

9

모든 프로그래밍 언어가 정수 오버플로를 무시하는 것은 아닙니다. 일부 언어는 라이브러리를 통해 모든 숫자 (대부분의 Lisp 언어, Ruby, Smalltalk 등) 및 다른 언어에 대해 안전한 정수 연산을 제공합니다. 예를 들어 C ++에 대한 다양한 BigInt 클래스가 있습니다.

언어가 기본적으로 오버플로로부터 정수를 안전하게 만드는지의 여부는 그 목적에 달려 있습니다. C 및 C ++와 같은 시스템 언어는 비용없이 추상화를 제공해야하며 "큰 정수"는 아닙니다. Ruby와 같은 생산성 언어는 즉시 큰 정수를 제공 할 수 있습니다. IMHO와 Java 사이의 어딘가에있는 언어는 IMHO가 안전 정수를 사용하지 않아야합니다.


오버플로 감지 (그리고 신호, 패닉, 예외 등)와 큰 숫자로 전환하는 것에는 차이가 있습니다. 전자는 후자보다 훨씬 저렴해야합니다.
Matthieu M.

@MatthieuM. 물론-나는 그 대답에 대해 명확하지 않다는 것을 알고 있습니다.
Nemanja Trifunovic 12

7

보시다시피, 기본적으로 오버플로 검사가 활성화되어 있으면 C #이 3 배 느려집니다 (예를 들어 해당 언어의 일반적인 응용 프로그램이라고 가정). 성능이 항상 가장 중요한 기능은 아니지만 언어 / 컴파일러는 일반적으로 일반적인 작업에서의 성능과 비교됩니다. 이는 언어 기능의 품질이 다소 주관적이며 성능 테스트는 객관적이라는 사실에 기인합니다.

대부분의 측면에서 C #과 유사하지만 3 배 더 느린 새로운 언어를 도입하려는 경우, 최종 사용자의 대부분이 오버플로 검사의 혜택을 누리더라도 시장 점유율을 얻는 것은 쉽지 않습니다. 고성능에서.


10
특히 C #의 경우, 초기에는 측정이 어려운 개발자 생산성 메트릭이 아니거나 보안 위반이 아닌 거래에서 현금으로 절약 된 메트릭스가 아닌 Java 및 C ++과 비교했을 때, 측정하기 어렵지만 사소한 성능 벤치 마크입니다.
Eric Lippert

1
아마도 CPU의 성능은 간단한 숫자 크 런칭으로 확인됩니다. 따라서 오버플로 탐지를 최적화하면 해당 테스트에서 "나쁜"결과가 발생할 수 있습니다. 캐치 22.
베른하르트 힐러

5

성능에 따른 오버플로 검사 부족을 정당화하는 많은 답변 외에도 고려해야 할 두 가지 종류의 산술이 있습니다.

  1. 인덱싱 계산 (배열 인덱싱 및 / 또는 포인터 산술)

  2. 다른 산술

언어가 포인터 크기와 동일한 정수 크기를 사용하는 경우 인덱싱 계산으로 인해 오버플로가 발생하기 전에 메모리가 부족해야하기 때문에 잘 구성된 프로그램이 인덱싱 계산을 오버플로하지 않습니다.

따라서 할당 된 데이터 구조를 포함하는 포인터 산술 및 인덱싱 표현식으로 작업 할 때는 메모리 할당을 확인하는 것으로 충분합니다. 예를 들어, 32 비트 주소 공간이 있고 32 비트 정수를 사용하고 최대 2GB의 힙이 할당 될 경우 (주소 공간의 약 절반) 인덱싱 / 포인터 계산 (기본적으로)이 오버플로되지 않습니다.

또한 배열 인덱싱 또는 포인터 계산에 얼마나 많은 덧셈 / 뺄셈 / 곱셈이 포함되어 있는지에 대해 놀랄 수도 있습니다. 따라서 첫 번째 범주에 해당합니다. 객체 포인터, 필드 액세스 및 배열 조작은 인덱싱 작업이며 많은 프로그램이 이보다 더 많은 산술 계산을 수행하지 않습니다! 기본적으로 이것은 정수 오버플로 검사없이 프로그램이 작동하는 주된 이유입니다.

모든 비 인덱싱 및 비 포인터 계산은 오버플로를 원하거나 예상하는 것 (예 : 해싱 계산)과 그렇지 않은 것 (예 : 요약 예)으로 분류해야합니다.

후자의 경우 프로그래머는 대체 데이터 유형 (예 : double또는 일부)을 사용 BigInt합니다. 많은 계산에는 재무 계산과 같은 decimal데이터 형식이 필요합니다 double. 정수 유형을 고수하지 않으면 정수 오버플로를 검사해야합니다. 그렇지 않으면 프로그램이 감지 할 때 감지되지 않은 오류 조건에 도달 할 수 있습니다.

프로그래머로서, 우리는 숫자 데이터 유형의 선택과 그 정확성에 대한 언급이 아닌 오버플로 가능성 측면에서 그 결과에 민감해야합니다. 일반적으로 (특히 빠른 정수 유형을 사용하려는 C 언어의 언어로 작업 할 때) 인덱싱 계산과 다른 것의 차이점을 인식하고 인식해야합니다.


3

Rust 언어 는 디버깅 빌드에 대한 검사를 추가하고 최적화 된 릴리스 버전에서 제거하여 오버플로 검사와 흥미로운 검사 사이에 흥미로운 절충안을 제공합니다. 이를 통해 테스트하는 동안 버그를 찾을 수 있으며 최종 버전에서는 여전히 완전한 성능을 얻을 수 있습니다.

오버 플로우 랩 어라운드가 때때로 원하는 동작이기 때문에, 오버 플로우를 점검하지 않는 버전의 연산자있습니다 .

변경 에 대한 RFC 의 선택에 대한 추론에 대해 자세히 읽을 수 있습니다 . 이 블로그 게시물 에는 이 기능이 파악하는 데 도움이 되는 버그 목록을 비롯하여 흥미로운 정보가 많이 있습니다 .


2
Rust는 checked_mul오버플로가 발생했는지 확인 None하고, Some그렇지 않으면 반환하는 메소드를 제공합니다 . 이는 프로덕션 및 디버그 모드에서 사용할 수 있습니다. doc.rust-lang.org/std/primitive.i32.html#examples-15
Akavall

3

Swift에서는 정수 오버플로가 기본적으로 감지되어 프로그램을 즉시 중지합니다. 랩 어라운드 동작이 필요한 경우이를 달성하는 다른 연산자 & +, &-및 & *가 있습니다. 그리고 작업을 수행하고 오버플로가 있는지 여부를 알려주는 기능이 있습니다.

초보자가 Collatz 시퀀스를 평가하고 코드가 충돌하는 것을 보는 것이 재미 있습니다. :-)

이제 Swift의 디자이너는 LLVM과 Clang의 디자이너이기도하므로 최적화에 대해 약간 또는 두 가지를 알고 있으며 불필요한 오버플로 검사를 피할 수 있습니다. 모든 최적화가 활성화 된 상태에서 오버플로 검사는 코드 크기와 실행 시간에 큰 도움이되지 않습니다. 또한 대부분의 오버플로로 인해 결과가 잘못되어 코드 크기와 실행 시간이 많이 소요됩니다.

추신. C, C ++에서 Objective-C 부호있는 정수 산술 오버 플로우는 정의되지 않은 동작입니다. 즉, 부호있는 정수 오버플로의 경우 컴파일러가 정의에 따라 올바른 것을 의미합니다. 부호있는 정수 오버 플로우에 대처하는 일반적인 방법은이를 무시하고 CPU가 제공하는 모든 결과를 취하여 컴파일러에 이러한 오버 플로우가 발생하지 않을 것이라는 가정을 세우는 것입니다 (예 : 오버 플로우는 Swift처럼 오버플로가 발생하면 확인하고 충돌하는 경우는 거의 없습니다.


1
나는 C에서 UB 주도의 광기를 추진하는 사람들이 다른 언어를 선호하여 비밀리에 그것을 훼손하려고 노력하고 있는지 궁금했습니다. 말이 되겠네요.
supercat

x+1>x무조건 true로 처리하면 컴파일러에서 임의의 큰 유형을 편리하게 사용하여 정수 표현식을 평가할 수있는 경우 (또는 마치 그렇게하는 것처럼) x에 대해 "가정"을 수행 할 필요가 없습니다. 오버플로 기반 "가정"의 초기 예는 uint32_t mul(uint16_t x, uint16_t y) { return x*y & 65535u; }컴파일러 가 32768보다 클 수없는 sum += mul(65535, x)결정을 내릴 수 있다고 결정했을 x것입니다 [C89 이론적 근거를 쓴 사람들에게 충격을 줄 수있는 행동, 이는 결정 요인 중 하나임을 시사합니다. ..
supercat

... unsigned short추진하는 signed int것은 2의 보완 자동 랩 어라운드 구현 (즉, 사용중인 대부분의 C 구현)이 또는로 unsigned short승격 이든 위와 같은 코드를 위와 같은 방식으로 처리한다는 사실이었습니다 . 표준은 위와 같은 코드를 무의미하게 처리하기 위해 자동 랩 어라운드 2의 보완 하드웨어에 대한 구현을 요구 하지 않았지만 표준 작성자는 어쨌든 그렇게 기대할 것으로 보입니다. intunsigned
supercat

2

실제로, 이것의 진짜 원인은 순수한 기술적 / 역사적입니다 : CPU 는 대부분 무시 부호입니다. 일반적으로 레지스터에 두 개의 정수를 추가하는 단일 명령 만 있으며 CPU는이 두 정수를 부호있는 것으로 해석하는지 여부에 상관하지 않습니다. 뺄셈과 곱셈에도 마찬가지입니다. 부호를 인식해야하는 유일한 산술 연산은 나눗셈입니다.

이것이 작동하는 이유는 사실상 모든 CPU에서 사용되는 부호있는 정수의 2의 보수 표현입니다. 예를 들어, 4 비트 2의 보수에서 5와 -3의 추가는 다음과 같습니다.

  0101   (5)
  1101   (-3)
(11010)  (carry)
  ----
  0010   (2)

캐리 비트를 버리는 랩 어라운드 동작이 올바른 부호있는 결과를 얻는 방법을 관찰하십시오. 마찬가지로 CPU는 일반적 x - y으로 x + ~y + 1다음 과 같이 빼기 를 구현합니다 .

  0101   (5)
  1100   (~3, binary negation!)
(11011)  (carry, we carry in a 1 bit!)
  ----
  0010   (2)

이것은 하드웨어의 추가로 감산을 구현하여 사소한 방식으로 산술 논리 유닛 (ALU)에 대한 입력 만 조정합니다. 더 간단 할 수있는 것은 무엇입니까?

곱셈은 ​​일련의 덧셈에 지나지 않으므로 유사하게 작동합니다. 2의 보수 표현을 사용하고 산술 연산 수행을 무시한 결과는 단순화 된 회로와 단순화 된 명령어 세트입니다.

분명히 C는 금속에 가깝게 작동하도록 설계되었으므로 부호없는 산술의 표준화 된 동작과 정확히 동일한 동작을 채택하여 부호있는 산술 만 정의되지 않은 동작을 생성 할 수 있습니다. 그리고 그 선택은 Java와 같은 다른 언어, 그리고 분명히 C #으로 이어졌습니다.


나는이 대답을하기 위해 여기에 왔습니다.
Mr Lister

불행히도, 어떤 사람들은 플랫폼에서 저수준 C 코드를 작성하는 사람들이 그러한 목적에 적합한 C 컴파일러가 오버플로가 발생했을 때 제한적인 방식으로 행동 할 것이라고 기대하는 대담성을 가져야한다는 개념을 지나치게 부당한 것으로 간주하는 것 같습니다. 개인적으로, 나는 계산이 때문에 32 비트 시스템에 컴파일러의 편의 (에서 임의의 확장 된 정밀도를 사용하여 수행하는 것처럼 경우 합리적인 컴파일러는 행동에 대한 생각 x==INT_MAX다음, x+1임의로 컴파일러에서 중 2147483648 또는 -2147483648으로 작동 할 수 있습니다 편의),하지만 ...
supercat

어떤 사람들은 경우에 있다고 생각하는 것 x하고 y있습니다 uint16_t및 32 비트 시스템에서 코드를 계산 x*y & 65535u하면 y때 65535, 그 코드를 가정해야 컴파일러에 도달되지 않습니다 x32768보다 큰
supercat

1

일부 답변에서 확인 비용에 대해 논의했으며 이의 제기가 합당한 이유라는 이의 제기에 대한 답변을 수정했습니다. 나는 그 점들을 다루려고 노력할 것이다.

C와 C ++ (예시)에서 언어 설계 원칙 중 하나는 요청하지 않은 기능을 제공하지 않는 것입니다. 이것은 일반적으로 "사용하지 않는 것에 대해 지불하지 마십시오"라는 문구로 요약됩니다. 프로그래머가 오버플로 검사를 원하면 요청할 수 있습니다 (그리고 페널티를 지불합니다). 이렇게하면 언어를 사용하는 것이 더 위험 해지지 만이를 알고있는 언어로 작업하도록 선택하면 위험을 감수하게됩니다. 그러한 위험을 원치 않거나 안전이 가장 중요한 성능의 코드를 작성하는 경우 성능 / 위험 균형이 다른보다 적절한 언어를 선택할 수 있습니다.

그러나 10,000,000,000 회의 반복으로 점검에 걸리는 시간은 여전히 ​​1 나노초 미만입니다.

이 추론에는 몇 가지 잘못된 점이 있습니다.

  1. 이것은 환경에 따라 다릅니다. 코드는 성능 측면에서 규모가 다른 모든 종류의 환경에 맞게 작성되므로 일반적으로 이와 같은 특정 수치를 인용하는 것은 거의 의미가 없습니다. 임베디드 컴퓨터를 코딩하는 사람에게는 놀랍도록 빠르며 슈퍼 컴퓨터 클러스터를 코딩하는 사람에게는 엄청나게 느릴 수 있습니다.

  2. 1 나노초는 드물게 실행되는 코드 세그먼트에는 아무 것도없는 것처럼 보일 수 있습니다. 반면에 코드의 주요 기능인 일부 계산의 내부 루프에 있으면 면도 할 수있는 시간의 모든 부분이 큰 차이를 만들 수 있습니다. 클러스터에서 시뮬레이션을 실행하는 경우 내부 루프에 저장된 나노초의 분수는 하드웨어 및 전기에 소비되는 비용으로 직접 변환 될 수 있습니다.

  3. 일부 알고리즘 및 컨텍스트의 경우 10,000,000,000 회 반복이 중요하지 않을 수 있습니다. 다시 말하지만 일반적으로 특정 상황에서만 적용되는 특정 시나리오에 대해 이야기하는 것은 이치에 맞지 않습니다.

중요한 상황이있을 수 있지만 대부분의 응용 프로그램에서는 중요하지 않습니다.

아마도 당신이 옳을 것입니다. 그러나 이것은 특정 언어의 목표가 무엇인지에 관한 문제입니다. 실제로 많은 언어는 "가장 많이"필요를 수용하거나 다른 문제보다 안전을 선호하도록 설계되었습니다. C 및 C ++와 같은 다른 것들은 효율성을 우선시합니다. 이러한 맥락에서, 대부분의 사람들이 방해받지 않기 때문에 모든 사람이 성능 저하를하게 만드는 것은 언어가 성취하려는 것에 반대합니다.


-1

이 좋은 답변입니다,하지만 난 놓친 점은 여기에 있다고 생각 : 정수 오버 플로우의 효과가 있습니다 반드시 나쁜 일이, 그리고 사후이 있는지 알고 어려운 i되는 것을 갔다 MAX_INT것에 MIN_INT인해 오버 플로우 문제였다 또는 의도적으로 -1을 곱하여 수행 한 경우

예를 들어, 0보다 큰 표현 가능한 정수를 모두 더하려면 for(i=0;i>=0;++i){...}추가 루프를 사용하고 오버플로가 발생하면 추가가 중지됩니다. 이는 목표 동작입니다 (오류가 발생하면 우회해야 함을 의미합니다) 표준 산술을 방해하기 때문에 임의의 보호). 원시 산술을 제한하는 것은 나쁜 습관입니다.

  • 그것들은 모든 것에 사용됩니다-원시 수학의 속도 저하는 모든 기능 프로그램에서 속도 저하입니다
  • 프로그래머가 필요하면 언제든지 추가 할 수 있습니다.
  • 그것들이 있고 프로그래머가 필요하지 않지만 (더 빠른 런타임이 필요하면) 최적화를 위해 쉽게 제거 할 수 없습니다
  • 만약 당신이 그것들을 가지고 있고 프로그래머가 그것들 이 존재 하지 않기를 원한다면 (위의 예에서와 같이), 프로그래머는 런타임 적중을 취하고 있고 (관련성이있을 수도 있고 그렇지 않을 수도 있음), 프로그래머는 여전히 제거하는데 시간을 투자해야합니다 또는 '보호'문제 해결.

3
언어가 제공하지 않으면 프로그래머가 효율적인 오버플로 검사를 추가하는 것은 실제로 불가능합니다. 함수가 무시되는 값을 계산하면 컴파일러가 계산을 최적화 할 수 있습니다. 함수가 오버 플로우 확인하지만 값을 계산하면 , 그렇지 않으면 무시를가 오버 플로우하는 경우, 컴파일러는 오버 플로우가 다른 프로그램의 출력에 영향을주지 않습니다 무시해도 될 수있는 경우에도, 계산 및 트랩을 수행해야합니다.
supercat

1
당신은 갈 수 INT_MAXINT_MIN-1을 곱하여.
David Conrad

해결책은 프로그래머가 주어진 코드 블록 또는 컴파일 단위에서 검사를 해제 할 수있는 방법을 제공하는 것입니다.
David Conrad

for(i=0;i>=0;++i){...}팀에서 낙심시키려는 코드 스타일입니다. 특수 효과 / 부작용에 의존하고 그것이 의도 한 바를 명확하게 표현하지 않습니다. 그러나 여전히 다른 프로그래밍 패러다임을 보여주는 귀하의 답변에 감사드립니다.
Bernhard Hiller

1
@Delioth : i64 비트 유형 인 경우 일관된 자동 랩 어라운드 2의 보완 동작을 구현하고 초당 10 억 반복을 실행하는 경우에도 이러한 루프는 int실행이 허용 된 경우 가장 큰 값 을 찾을 수 있습니다. 수백 년. 일관된 자동 랩 어라운드 동작을 약속하지 않는 시스템에서는 코드의 길이에 관계없이 이러한 동작이 보장되지 않습니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.