C / C ++에서 서명 된 오버플로 감지


82

언뜻보기 에이 질문은 정수 오버플로를 감지하는 방법 의 중복처럼 보일 수 있습니다 . 그러나 실제로는 상당히 다릅니다.

부호없는 정수 오버플로를 감지하는 것은 매우 사소한 일이지만 C / C ++에서 서명 된 오버플로를 감지하는 것은 실제로 대부분의 사람들이 생각하는 것보다 더 어렵다는 것을 발견했습니다.

가장 분명하지만 순진한 방법은 다음과 같습니다.

int add(int lhs, int rhs)
{
 int sum = lhs + rhs;
 if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) {
  /* an overflow has occurred */
  abort();
 }
 return sum; 
}

문제는 C 표준에 따르면 부호있는 정수 오버플로가 정의되지 않은 동작이라는 것입니다. 즉, 표준에 따르면 서명 된 오버플로가 발생하는 즉시 널 포인터를 역 참조한 것처럼 프로그램이 유효하지 않습니다. 따라서 정의되지 않은 동작을 유발할 수 없으며 위의 사후 조건 검사 예제에서와 같이 사실 후에 오버플로를 감지하려고 시도합니다.

위의 검사가 많은 컴파일러에서 작동 할 가능성이 있지만 믿을 수는 없습니다. 실제로 C 표준은 부호있는 정수 오버플로가 정의되지 않는다고 말하고 있기 때문에 일부 컴파일러 (예 : GCC)는 최적화 플래그가 설정 될 때 위의 검사 를 최적화합니다. 컴파일러는 부호있는 오버플로가 불가능하다고 가정하기 때문입니다. 이것은 오버플로를 확인하려는 시도를 완전히 중단합니다.

따라서 오버플로를 확인하는 또 다른 가능한 방법은 다음과 같습니다.

int add(int lhs, int rhs)
{
 if (lhs >= 0 && rhs >= 0) {
  if (INT_MAX - lhs <= rhs) {
   /* overflow has occurred */
   abort();
  }
 }
 else if (lhs < 0 && rhs < 0) {
  if (lhs <= INT_MIN - rhs) {
   /* overflow has occurred */
   abort();
  }
 }

 return lhs + rhs;
}

이러한 추가를 수행해도 오버플로가 발생하지 않는지 미리 확인할 때까지 실제로 두 정수를 함께 추가하지 않기 때문에 이것은 더 유망 해 보입니다. 따라서 정의되지 않은 동작은 발생하지 않습니다.

그러나이 솔루션은 더하기 연산이 작동하는지 테스트하기 위해 빼기 연산을 수행해야하므로 초기 솔루션보다 훨씬 덜 효율적입니다. 그리고이 (작은) 성능 저하에 대해 신경 쓰지 않더라도이 솔루션이 적절하다고 확신하지는 않습니다. 이 표현식 lhs <= INT_MIN - rhs은 컴파일러가 최적화 할 수있는 표현식과 똑같이 보입니다. 서명 된 오버플로가 불가능하다고 생각합니다.

여기에 더 나은 해결책이 있습니까? 1) 정의되지 않은 동작을 일으키지 않고 2) 컴파일러에게 오버플로 검사를 최적화 할 기회를 제공하지 않는 것이 보장되는 것입니까? 두 피연산자를 unsigned로 캐스팅하고 자신의 2의 보수 산술을 굴려서 검사를 수행하는 방법이있을 수 있다고 생각했지만 어떻게 해야할지 모르겠습니다.


1
탐지를 시도하는 것보다 오버플로 가능성이없는 코드를 작성하는 것이 더 낫지 않습니까?
Arun

9
@ArunSaha : 계산을하고 넘치지 않는지 확인하는 것은 정말 어렵고 일반적인 경우에 증명하는 것도 불가능합니다. 일반적인 관행은 가능한 한 넓은 정수 유형을 사용하고 희망하는 것입니다.
David Thornley

6
@Amardeep : 널 포인터 역 참조는 서명 된 오버 플로우와 동일하게 정의되지 않습니다. 정의되지 않은 동작은 표준이 진행되는 한 모든 일이 발생할 수 있음을 의미합니다. 서명 된 오버플로 후에 시스템이 유효하지 않고 불안정한 상태에 있지 않을 것이라고 가정 할 수 없습니다. OP는 이것의 한 가지 결과를 지적했습니다. 최적화 프로그램이 서명 된 오버플로가 발생하면 감지하는 코드를 제거하는 것은 완벽하게 합법적입니다.
David Thornley

16
@Amardeep : 그런 구현을 언급했습니다. GCC는 최적화 플래그가 설정되면 오버플로 검사 코드를 제거 합니다. 따라서 기본적으로 프로그램이 중단됩니다. 이것은 미묘한 보안 결함을 초래할 수 있기 때문에 null 포인터 역 참조보다 더 나쁩니다 . 반면에 null을 역 참조하면 segfault로 프로그램을 무뚝뚝하게 방해 할 수 있습니다.
Channel72

2
@Amardeep : 컴파일러 설정에 따라 오버플로로 인해 트랩이 발생하는 구현이 확실히 보입니다. 언어가 특정 unsigned 변수 나 수량이 (1) 깔끔하게 래핑할지, (2) 잘못할지, (3) 편리한 작업을해야하는지 지정할 수 있다면 좋을 것입니다. 변수가 기계의 레지스터 크기보다 작은 경우 서명되지 않은 수량이 깔끔하게 포장되도록 요구하면 최적의 코드가 생성되지 않을 수 있습니다.
supercat

답변:


26

빼기에 대한 접근 방식은 정확하고 잘 정의되어 있습니다. 컴파일러는이를 최적화 할 수 없습니다.

더 큰 정수 유형을 사용할 수있는 경우 또 다른 올바른 접근 방식은 더 큰 유형에서 산술을 수행 한 다음 다시 변환 할 때 결과가 더 작은 유형에 맞는지 확인하는 것입니다.

int sum(int a, int b)
{
    long long c;
    assert(LLONG_MAX>INT_MAX);
    c = (long long)a + b;
    if (c < INT_MIN || c > INT_MAX) abort();
    return c;
}

좋은 컴파일러는 전체 추가 및 if명령문을 int크기 추가 및 단일 조건부 오버 플로우 로 변환해야하며 실제로 더 큰 추가를 수행하지 않아야합니다.

편집 : Stephen이 지적했듯이 정상적인 asm을 생성하기 위해 (그다지 좋지 않은) 컴파일러 gcc를 얻는 데 문제가 있습니다. 생성되는 코드는 그렇게 느리지는 않지만 확실히 차선책입니다. gcc가 옳은 일을하도록하는이 코드의 변형을 아는 사람이 있다면보고 싶습니다.


1
이것을 사용하고 싶은 사람은 내가 편집 한 버전을보고 있는지 확인하십시오. 원본에서는 long long추가하기 전에 캐스트를 어리석게 생략했습니다 .
R .. GitHub STOP HELPING ICE

3
호기심으로이 최적화를 수행하는 컴파일러를 얻는 데 성공 했습니까? 몇몇 컴파일러에 대한 빠른 테스트는 그것을 할 수있는 어떤 것도 발견하지 못했습니다.
Stephen Canon

2
x86_64에서는 32 비트 정수를 사용하는 데 비효율적 인 것이 없습니다. 성능은 64 비트와 동일합니다. 네이티브 단어 크기보다 작은 유형을 사용하는 한 가지 동기는 오버플로 / 캐리가 직접 액세스 가능한 위치에서 발생하기 때문에 오버플로 조건을 처리하거나 (임의의 정밀도 산술을 위해) 수행하는 것이 매우 효율적이라는 것입니다.
R .. GitHub STOP HELPING ICE

2
@R., @Steven : OP가 제공 한 빼기 코드가 올바르지 않습니다. 내 대답을 참조하십시오. 나는 또한 최대 두 번의 비교만으로 그것을 수행하는 코드를 제공합니다. 아마도 컴파일러가 더 잘할 것입니다.
Jens Gustedt

3
이 접근 방식은 sizeof(long long) == sizeof(int). C는 sizeof(long long) >= sizeof(int).
chux-Monica 복원

36

아니요, 두 번째 코드가 올바르지 않지만 가까이 있습니다.

int half = INT_MAX/2;
int half1 = half + 1;

추가 결과는 INT_MAX입니다. ( INT_MAX항상 홀수). 그래서 이것은 유효한 입력입니다. 그러나 당신의 일상에서 당신은 가질 INT_MAX - half == half1것이고 당신은 중단 할 것입니다. 거짓 양성.

이 오류는 두 검사를 모두 입력하는 <대신 입력하여 복구 할 수 있습니다 <=.

그러나 코드도 최적이 아닙니다. 다음을 수행합니다.

int add(int lhs, int rhs)
{
 if (lhs >= 0) {
  if (INT_MAX - lhs < rhs) {
   /* would overflow */
   abort();
  }
 }
 else {
  if (rhs < INT_MIN - lhs) {
   /* would overflow */
   abort();
  }
 }
 return lhs + rhs;
}

이것이 유효한지 확인하려면 lhs부등식의 양쪽에 상징적으로 추가 해야합니다. 그러면 결과가 범위를 벗어난 산술적 조건을 정확히 알 수 있습니다.


+1은 베스트 답변입니다. 경미 : /* overflow will occurred */코드가 lhs + rhs실제로 합을 수행 하지 않은 경우 오버플로가 발생했음을 감지하는 것이 요점임을 강조하는 것이 좋습니다 .
chux-Monica 복원

16

IMHO, 오버플로 센시티브 C ++ 코드를 처리하는 가장 쉬운 방법은 SafeInt<T>. 이것은 여기에서 원하는 안전 보장을 제공하는 코드 플렉스에서 호스팅되는 크로스 플랫폼 C ++ 템플릿입니다.

일반적인 수치 연산과 동일한 사용 패턴을 많이 제공하고 예외를 통해 흐름의 위와 아래를 표현하므로 사용하는 것이 매우 직관적입니다.


14

gcc 케이스의 경우 gcc 5.0 릴리스 노트에서 이제 __builtin_add_overflow오버플로 확인을위한 추가 기능을 제공하는 것을 볼 수 있습니다 .

오버플로 검사를 사용하는 산술을위한 새로운 내장 함수 세트가 추가되었습니다 : __builtin_add_overflow, __builtin_sub_overflow 및 __builtin_mul_overflow 및 clang과의 호환성을 위해 다른 변형도 추가되었습니다. 이러한 내장 기능에는 두 개의 정수 인수 (동일한 유형일 필요 없음)가 있으며, 인수는 무한 정밀도 부호있는 유형으로 확장되고, +,-또는 *가 수행되며 결과는 가리키는 정수 변수에 저장됩니다. 마지막 주장으로. 저장된 값이 무한 정밀도 결과와 같으면 내장 함수는 false를 리턴하고 그렇지 않으면 true를 리턴합니다. 결과를 보유 할 정수 변수의 유형은 처음 두 인수의 유형과 다를 수 있습니다.

예를 들면 :

__builtin_add_overflow( rhs, lhs, &result )

gcc 문서 에서 Overflow Checking 을 사용 하여 산술을 수행하는 내장 함수를 볼 수 있습니다 .

[...] 이러한 내장 함수에는 모든 인수 값에 대해 완전히 정의 된 동작이 있습니다.

clang은 또한 확인 된 산술 내장 집합을 제공합니다 .

Clang은 C에서 빠르고 쉽게 표현할 수있는 방식으로 보안에 중요한 애플리케이션에 대해 검사 된 산술을 구현하는 내장 세트를 제공합니다.

이 경우 내장은 다음과 같습니다.

__builtin_sadd_overflow( rhs, lhs, &result )

이 함수 는 한 가지를 제외하고는 매우 유용합니다 . 오버플 int result; __builtin_add_overflow(INT_MAX, 1, &result);result에 저장된 내용을 명시 적으로 말하지 않고 불행히도 정의되지 않은 동작 이 발생 하지 않도록 지정하면 조용 합니다. 확실히 그것이 의도였습니다. UB는 없습니다. 그것을 지정하면 더 좋습니다.
chux-Monica 복원

1
@chux 좋은 점, 여기 에 결과가 항상 정의되어 있으며 내 대답을 업데이트했습니다. 그렇지 않다면 오히려 아이러니 할 것입니다.
Shafik Yaghmour 2015-08-31

관심있는 새 참조에는 (unsigned) long long *resultfor 가 없습니다 __builtin_(s/u)addll_overflow. 확실히 이것들은 오류입니다. 다른 측면의 진실성에 대해 궁금해합니다. IAC, 반갑습니다 __builtin_add/sub/mull_overflow(). 그들이 언젠가 C 스펙에 도달하기를 바랍니다.
chux - 분석 재개 모니카

1
+1 이것은 표준 C에서 얻을 수있는 것보다 훨씬 더 나은 어셈블리를 생성 합니다. 이러한 내장 기능을 사용할 수있는 경우를 감지하고 컴파일러가 제공하지 않는 경우에만 표준 솔루션을 사용해야합니다.
Alex Reinking

11

인라인 어셈블러를 사용하는 경우 오버플로 플래그를 확인할 수 있습니다 . 또 다른 가능성은 safeint 데이터 유형을 사용할 수 있다는 것입니다 . Integer Security 에 대한이 문서를 읽는 것이 좋습니다 .


6
+1 이것은 "C가 정의하지 않으면 플랫폼 별 동작으로 강제됩니다."라고 말하는 또 다른 방법입니다. 어셈블리에서 쉽게 처리되는 많은 것들이 C에서 정의되지 않아 휴대 성이라는 이름으로 두더지 언덕에서 산을 만듭니다.
Mike DeSimone

5
C 질문에 대한 asm 답변에 대해 반대표를주었습니다. 내가 말했듯이 C로 수표를 작성하는 정확하고 이식 가능한 방법이 있습니다. 이것은 손으로 쓰는 것과 똑같은 asm을 생성합니다. 당연히 이것을 사용하면 성능에 미치는 영향은 동일 할 것이며 또한 권장 한 C ++ safeint 항목보다 훨씬 덜 영향을 미칠 것입니다.
R .. GitHub STOP HELPING ICE

1
@Matthieu : 하나의 구현에서만 사용되는 코드를 작성하고 그 구현이 무언가가 작동하도록 보장하고 좋은 정수 성능이 필요하다면 구현 별 트릭을 사용할 수 있습니다. 그러나 그것은 OP가 요구 한 것이 아닙니다.
David Thornley

3
C는 좋은 이유로 구현 정의 동작과 정의되지 않은 동작을 구별합니다. UB가있는 것이 현재 버전 의 구현 에서 "작동" 한다고해서 향후 버전에서 계속 작동한다는 의미는 아닙니다. gcc 및 서명 된 오버플로 동작 고려 ...
R .. GitHub STOP HELPING ICE

2
동일한 asm을 생성하기 위해 C 코드를 얻을 수 있다는 주장에 -1을 기반으로했기 때문에 모든 주요 컴파일러가이 점에서 쓰레기로 판명 될 때이를 철회하는 것이 공정하다고 생각합니다 ..
R .. GitHub STOP HELPING ICE

6

가장 빠른 방법은 내장 GCC를 사용하는 것입니다.

int add(int lhs, int rhs) {
    int sum;
    if (__builtin_add_overflow(lhs, rhs, &sum))
        abort();
    return sum;
}

x86에서 GCC는 이것을 다음과 같이 컴파일합니다.

    mov %edi, %eax
    add %esi, %eax
    jo call_abort 
    ret
call_abort:
    call abort

프로세서의 내장 오버플로 감지를 사용합니다.

GCC 내장을 사용하는 것이 괜찮지 않다면 다음으로 빠른 방법은 부호 비트에서 비트 연산을 사용하는 것입니다. 추가로 서명 된 오버플로는 다음과 같은 경우에 발생합니다.

  • 두 피연산자의 부호가 같고
  • 결과는 피연산자와 다른 부호를 갖습니다.

의 부호 비트는 ~(lhs ^ rhs)피연산자에 동일한 부호가 있으면 on이고 부호 비트 lhs ^ sum는 결과에 피연산자와 다른 부호가 있으면 온입니다. 따라서 정의되지 않은 동작을 피하기 위해 서명되지 않은 형식으로 추가를 수행 한 다음 기호 비트를 사용할 수 있습니다.~(lhs ^ rhs) & (lhs ^ sum) .

int add(int lhs, int rhs) {
    unsigned sum = (unsigned) lhs + (unsigned) rhs;
    if ((~(lhs ^ rhs) & (lhs ^ sum)) & 0x80000000)
        abort();
    return (int) sum;
}

이것은 다음과 같이 컴파일됩니다.

    lea (%rsi,%rdi), %eax
    xor %edi, %esi
    not %esi
    xor %eax, %edi
    test %edi, %esi
    js call_abort
    ret
call_abort:
    call abort

32 비트 머신 (gcc 사용)에서 64 비트 유형으로 캐스팅하는 것보다 훨씬 빠릅니다.

    push %ebx
    mov 12(%esp), %ecx
    mov 8(%esp), %eax
    mov %ecx, %ebx
    sar $31, %ebx
    clt
    add %ecx, %eax
    adc %ebx, %edx
    mov %eax, %ecx
    add $-2147483648, %ecx
    mov %edx, %ebx
    adc $0, %ebx
    cmp $0, %ebx
    ja call_abort
    pop %ebx
    ret
call_abort:
    call abort

1

64 비트 정수로 변환하고 이와 유사한 조건을 테스트하는 것이 더 좋을 수 있습니다. 예를 들면 :

#include <stdint.h>

...

int64_t sum = (int64_t)lhs + (int64_t)rhs;
if (sum < INT_MIN || sum > INT_MAX) {
    // Overflow occurred!
}
else {
    return sum;
}

여기서 기호 확장이 어떻게 작동하는지 자세히 살펴보고 싶을 수도 있지만 올바른 것 같습니다.


return 문에서 비트 및 캐스트를 제거합니다. 쓰여진대로 잘못되었습니다. 더 큰 부호있는 정수 유형에서 더 작은 유형으로의 변환은 값이 더 작은 유형에 맞는 한 완벽하게 잘 정의되고 명시 적 캐스트가 필요하지 않습니다. 경고를 제공하고 값이 오버플로되지 않는지 방금 확인했을 때 캐스트를 추가하도록 제안하는 컴파일러는 고장난 컴파일러입니다.
R .. GitHub STOP HELPING ICE

@R 당신이 맞습니다, 나는 내 캐스트에 대해 명시 적으로 말하는 것을 좋아합니다. 그래도 정확성을 위해 변경하겠습니다. 미래의 독자를 위해 리턴 라인은 return (int32_t)(sum & 0xffffffff);.
Jonathan

2
당신이 쓰는 경우주의 sum & 0xffffffff, sum암시 적 형식으로 변환됩니다 unsigned int(32 비트를 가정 int하기 때문에) 0xffffffff유형이 있습니다 unsigned int. 그런 다음 비트 및의 결과 는이고 음수 unsigned int이면 sum에서 지원하는 값 범위를 벗어납니다 int32_t. int32_t그런 다음으로 의 변환 에는 구현 정의 동작이 있습니다.
R .. GitHub STOP HELPING ICE

s가 64 비트 인 ILP64 환경에서는 작동하지 않습니다 int.
rtx13

1

어때 :

int sum(int n1, int n2)
{
  int result;
  if (n1 >= 0)
  {
    result = (n1 - INT_MAX)+n2; /* Can't overflow */
    if (result > 0) return INT_MAX; else return (result + INT_MAX);
  }
  else
  {
    result = (n1 - INT_MIN)+n2; /* Can't overflow */
    if (0 > result) return INT_MIN; else return (result + INT_MIN);
  }
}

나는 그것이 모든 합법적 INT_MIN이고 INT_MAX(대칭 적 이든 아니든) 작동해야한다고 생각합니다 . 표시된 클립과 같은 기능이지만 다른 동작을 얻는 방법이 분명해야합니다).


+1은 아마도 더 직관적 인 좋은 대체 접근 방식입니다.
R .. GitHub STOP HELPING ICE

1
result = (n1 - INT_MAX)+n2;n1이 작고 (0) n2가 음수이면 이것이 오버플로 될 수 있다고 생각합니다 .
davmac 2013-08-05

@davmac : 흠 ... 세 가지 경우를 구분해야 할 수도 있습니다. for (n1 ^ n2) < 0, 2의 보수 시스템에서 값이 반대 부호를 가지고 있으며 직접 추가 될 수 있음을 의미하는 1로 시작 합니다. 값의 부호가 같으면 위에 제공된 접근 방식이 안전합니다. 다른 한편으로, 나는 표준의 저자가 즉각적인 비정상적인 프로그램 종료를 강요하지 않는 방식으로 오버플로의 경우 레일을 뛰어 넘을 것이라고 예상했는지 궁금합니다. 다른 계산의 예측할 수없는 중단.
supercat

0

확실한 해결책은 unsigned로 변환하여 잘 정의 된 unsigned 오버플로 동작을 얻는 것입니다.

int add(int lhs, int rhs) 
{ 
   int sum = (unsigned)lhs + (unsigned)rhs; 
   if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) { 
      /* an overflow has occurred */ 
      abort(); 
   } 
   return sum;  
} 

이것은 정의되지 않은 서명 된 오버플로 동작을 서명 된 것과 서명되지 않은 사이의 범위를 벗어난 값의 구현 정의 변환으로 대체하므로 컴파일러의 문서를 확인하여 정확히 어떤 일이 발생할지 알 필요가 있지만 적어도 잘 정의되어야합니다. 변환시 신호를 발생시키지 않는 2- 보완 기계에서 올바른 일을해야합니다. 이는 거의 모든 기계와 지난 20 년 동안 빌드 된 C 컴파일러입니다.


당신은 여전히 결과를 저장하고 sum을 인 int. 그 결과 값 (unsigned)lhs + (unsigned)rhs이보다 크면 구현 정의 결과 또는 구현 정의 신호가 발생합니다 INT_MAX.
R .. GitHub STOP HELPING ICE

2
@R : 그게 요점입니다. 동작은 정의되지 않은 것이 아니라 정의 된 구현이므로 구현시 수행하는 작업을 문서화하고 일관되게 수행해야합니다. 구현에서 문서화하는 경우에만 신호가 발생할 수 있으며,이 경우 항상 발생해야하며 해당 동작을 사용할 수 있습니다.
Chris Dodd

0

long값 을 추가하는 경우 이식 가능한 코드는 long값을 낮은 int부분 과 높은 부분 으로 나눌 수 있습니다 (또는 크기가 같은 short경우 부분 long으로 int).

static_assert(sizeof(long) == 2*sizeof(int), "");
long a, b;
int ai[2] = {int(a), int(a >> (8*sizeof(int)))};
int bi[2] = {int(b), int(b >> (8*sizeof(int))});
... use the 'long' type to add the elements of 'ai' and 'bi'

특정 CPU를 대상으로하는 경우 인라인 어셈블리를 사용하는 것이 가장 빠른 방법입니다.

long a, b;
bool overflow;
#ifdef __amd64__
    asm (
        "addq %2, %0; seto %1"
        : "+r" (a), "=ro" (overflow)
        : "ro" (b)
    );
#else
    #error "unsupported CPU"
#endif
if(overflow) ...
// The result is stored in variable 'a'

-1

나는 이것이 효과가 있다고 생각합니다.

int add(int lhs, int rhs) {
   volatile int sum = lhs + rhs;
   if (lhs != (sum - rhs) ) {
       /* overflow */
       //errno = ERANGE;
       abort();
   }
   return sum;
}

volatile을 사용하면 컴파일러가 테스트를 최적화하지 못하도록합니다. sum 가 더하기와 빼기 사이에서 변경되었을 수 못합니다.

x86_64 용 gcc 4.4.3을 사용하면이 코드에 대한 어셈블리는 스택과 불필요한 스택 작업에 ​​대한 모든 것을 저장하지만 더하기, 빼기 및 테스트를 수행합니다. 나는 심지어 시도했다register volatile int sum = 했지만 어셈블리는 동일했습니다.

int sum =휘발성 또는 레지스터가없는 버전의 경우 함수는 테스트를 수행하지 않았으며 하나의 lea명령 만 사용하여 추가를 수행했습니다 (lea 유효 주소로드이며 플래그 레지스터를 건드리지 않고 추가하는 데 자주 사용됨).

버전이 더 큰 코드와 점프 더 많은,하지만 나는 할 것이다 모른다 더 나은 .


4
volatile정의되지 않은 동작 을 가리기 위해 오용하는 경우 -1 . 그것이 "작동"한다면, 당신은 여전히 ​​"운이 좋은"것입니다.
R .. GitHub STOP HELPING ICE

@R : 작동하지 않으면 컴파일러가 volatile올바르게 구현되지 않은 것입니다. 내가 시도한 것은 이미 답변 된 질문에 대한 매우 일반적인 문제에 대한 더 간단한 해결책이었습니다.
nategoose

그러나 실패 할 수있는 곳은 정수에 대한 오버플로시 숫자 표현이 더 낮은 값으로 래핑되는 시스템입니다.
nategoose

마지막 주석에는 "하지 않음"또는 "하지 않음"이 있어야합니다.
nategoose

@nategoose, "작동하지 않으면 컴파일러가 휘발성을 올바르게 구현하지 않는다"는 주장은 잘못되었습니다. 우선, 2의 보수 산술에서 오버플로가 발생하더라도 lhs = sum-rhs는 항상 사실입니다. 그렇지 않은 경우에도이 특정 예제는 약간 고안된 것이지만 컴파일러는 예를 들어 추가를 수행하는 코드를 생성하고 결과 값을 저장하고 값을 다시 다른 레지스터로 읽고 저장된 값을 읽은 값과 비교할 수 있습니다. 값이 동일하므로 오버플로가 발생하지 않았다고 가정합니다.
davmac 2013-08-05

-1

내가보기에 가장 간단한 검사는 피연산자와 결과의 부호를 확인하는 것입니다.

합계를 살펴 보겠습니다. 오버플로는 두 피연산자가 동일한 부호를 가질 때만 + 또는-양방향으로 발생할 수 있습니다. 그리고 분명히 결과의 부호가 피연산자의 부호와 같지 않을 때 오버플로가 발생합니다.

따라서 다음과 같은 검사로 충분합니다.

int a, b, sum;
sum = a + b;
if  (((a ^ ~b) & (a ^ sum)) & 0x80000000)
    detect_oveflow();

편집 : Nils가 제안했듯이 올바른 if조건입니다.

((((unsigned int)a ^ ~(unsigned int)b) & ((unsigned int)a ^ (unsigned int)sum)) & 0x80000000)

그리고 언제부터 지시

add eax, ebx 

정의되지 않은 동작으로 이어질까요? Intel x86 명령 집합 참조에는 그런 것이 없습니다 ..


2
여기서 요점을 놓치고 있습니다. 두 번째 코드 줄은 sum = a + b정의되지 않은 동작 을 생성 할 수 있습니다.
Channel72

당신이 당신의 테스트 또한 중 부호로 합계, a와 b를 주조 경우 코드는 .. BTW 작동합니다
닐스 Pipenbrinck에게

프로그램이 충돌하거나 다르게 작동하기 때문에 정의되지 않았습니다. 프로세서가 OF 플래그를 계산하기 위해 수행하는 정확한 작업입니다. 표준은 비표준 사례로부터 자신을 보호하려고 노력하고 있지만 이것이 허용되지 않는다는 의미는 아닙니다.
ruslik

@Nils 그래, 나는 그것을하고 싶었지만 4 (usngined int)s가 훨씬 더 읽을 수 없게 만들 것이라고 생각했습니다 . (당신은 먼저 그것을 읽고 당신이 그것을 좋아할 때만 시도해보십시오).
ruslik

1
정의되지 않은 동작하지 어셈블리 컴파일 한 후, C 인
phuclv
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.