2017-12-18 39 views
1

Clang 5.0 및 Undefined Behavior Sanitizer (UBsan)를 사용할 때 최근에 제거한 문제를 이해하려고합니다. 버퍼를 정방향 또는 역방향으로 처리하는 코드가 있습니다. 대소 문자는 similar to the code shown below입니다.Clang 5.0 및 UBsan에서 포인터가 추가되고 정수가 오버플로됩니까?

0-len은 조금 이상하게 보일 수 있지만 초기 Microsoft .Net 컴파일러에 필요합니다. 연타 5.0 UBsan produced integer overflow findings :

adv-simd.h:1138:26: runtime error: addition of unsigned offset to 0x000003f78cf0 overflowed to 0x000003f78ce0 
adv-simd.h:1140:26: runtime error: addition of unsigned offset to 0x000003f78ce0 overflowed to 0x000003f78cd0 
adv-simd.h:1142:26: runtime error: addition of unsigned offset to 0x000003f78cd0 overflowed to 0x000003f78cc0 
... 

라인 1138, 1140, 1142 (친구가) 수도 걸음 뒤로 인해 0-len의 증가이다.

ptr += inc; 

(또한 C++에 대해 설명하는) Pointer comparisons in C. Are they signed or unsigned?에 따르면, 포인터는 어느 쪽도 서명되지도 서명한다. 우리의 오프셋은 부호가 없으며 역방향 보폭을 얻기 위해 부호없는 정수 랩에 의존했습니다.

코드는 GCC UBsan 및 Clang 4 및 이전 UBsan에서 유효합니다. 우리는 결국 Clang 5.0을 help with the LLVM devs으로 삭제했습니다. size_t 대신 ptrdiff_t을 사용해야했습니다.

제 질문은, 공사에서 정수 오버플로/정의되지 않은 동작은 어디에 있습니까? ptr + <unsigned>은 부호있는 정수가 오버플로되어 정의되지 않은 동작을 일으켰습니까?


여기 실제 코드를 반영하는 MSVC가 있습니다.

expr.add/4

I의 서식을 유지하기 위해 여기에 이미지를 사용

(이됩니다

#include <cstddef> 
#include <cstdint> 
using namespace std; 

uint8_t buffer[64]; 

int main(int argc, char* argv[]) 
{ 
    uint8_t * ptr = buffer; 
    size_t len = sizeof(buffer); 
    size_t inc = 16; 

    // This sets up processing the buffer in reverse. 
    // A flag controls it in the real code. 
    if (argc%2 == 1) 
    { 
     ptr += len - inc; 
     inc = 0-inc; 
    } 

    while (len > 16) 
    { 
     // process blocks 
     ptr += inc; 
     len -= 16; 
    } 

    return 0; 
} 
+2

포인터 자체는 부호가 없거나 unsing되지 않습니다. 그러나 포인터가 배열 내부의 어딘가를 가리키고있을 때 포인터가 증가하거나 감소 할 수 있기 때문에 포인터 추가가 서명됩니다. [mcve]가 제공되지 않는 한 정의되지 않은 행동이 있는지 여부를 판단하기에는 부족하기 때문에이 질문의 정보를 바탕으로 더 이상 말할 수 없습니다. –

+0

감사합니다. @ 샘. * "...하지만 포인터 추가가 서명되었습니다 ..."* - 그게 내가 놓치고있는 부분입니다. 부정적인 포인터는 나에게 의미가 없습니다. 나는 그들을 무신론자로 취급했지만 서명되지 않은 유형처럼 행동했습니다. 부정적인 포인터는 C++위원회에서 의미가 있다고 생각합니다. 서명 된 오버 플로우로 인해 어떻게 음의 포인터가 살아남습니까? – jww

+1

배열의 세 번째 요소에 대한 포인터가있는 경우 -1을 추가하고 배열의 두 번째 요소에 대한 포인터로 끝낼 수 있습니다. 포인터 추가가 서명됩니다. –

답변

2

포인터의 정수를 추가의 정의는 (N4659은/4 expr.add)).

이것은 이전 표준의 명확하지 않은 설명을 대체하는 새로운 표현입니다. 당신의 코드에 적용되는 표준 견적에있는 변수의

uint8_t buffer[64]; 
uint8_t *ptr = buffer + 48; 
ptr = ptr + (SIZE_MAX - 15); 

i48하고 j(SIZE_MAX - 15)입니다 : (argc가 홀수 인 경우) 코드에서

우리는 해당하는 코드와 끝까지 n64입니다.

이제 질문은 그것이 0 ≤ i + j ≤ n 인 것이 맞는지 여부입니다. "i + j"가 이고 결과가 i + j 인 것으로 해석하면 32n보다 작습니다. 그러나 수학적 결과를 의미한다면 n보다 훨씬 큽니다.

표준은 여기에 수학 방정식에 글꼴을 사용하며 글꼴을 소스 코드로 사용하지 않습니다. 은 유효한 연산자가 아닙니다. 그래서 나는이 방정식이 수학적 가치를 설명하기 위해 의도한다고 생각합니다. 즉 이것은 정의되지 않은 행동입니다.

+0

이론적 근거 : 16 비트'size_t'하지만 32 비트 포인터를 사용하는 시스템에서 . 컴파일러가'ptr + = 65540'을'ptr - = 16'으로 바꾸는 것은 불가능합니다. 첫번째 크기는 최대 크기의 객체 내에서 유효한 추가가 될 수 있습니다. –

+0

'size_t'와'ptrdiff_t'가있는 시스템 다른 크기, 그건 사실 일 겁니다. 분할 모드 8086은 객체가 32K를 초과 할 수 있지만 포인터 빼기가 일반적으로 16 비트 부호있는 값을 생성 했으므로 흥미 롭습니다. 두 포인터가 60000 바이트 떨어져있는 개체의 부분을 식별하면 차이는 -5536이되지만 한 포인터에 -5536을 추가하면 다른 포인터가 생깁니다. – supercat

1

C 표준은 포인터 차이 연산자에 의해 산출 된 유형 인 ptrdiff_t을 정의합니다. 시스템이 32 비트 size_t과 64 비트 ptrdiff_t을 가질 수 있습니다. 이러한 정의는 64 비트 선형 또는 유사 선형 포인터를 사용하지만 개개의 객체가 각각 4GiB 미만이되도록 요구 한 시스템에 적합합니다.

개체가 각각 2GiB보다 작은 것으로 알려진 경우 size_t 대신 ptrdiff_t 유형의 값을 저장하면 불필요하게 비효율적 일 수 있습니다. 그러나 이러한 시나리오에서 코드는 음수 일 수있는 포인터 차이를 유지하는 데 을 사용하지 말고 int32_t [개체가 각각 2GiB보다 작은 경우 충분히 커야합니다]를 사용하십시오. ptrdiff_t이 64 비트 인 경우에도 유형이 int32_t 인 값은 포인터에서 더하거나 빼기 전에 올바르게 부호 확장됩니다.