2013-03-25 5 views
15

그것은 나에게 같습니다 그러나null 포인터가 정의되지 않은 동작에 대해 산술 연산을 수행하고 있습니까? 다음 프로그램이 잘못된 포인터를 계산처럼 <code>NULL</code> 평등에 대한 할당 및 비교하지만 아무것도 좋은 없기 때문에

#include <stdlib.h> 
#include <stdio.h> 

int main() { 

    char *c = NULL; 
    c--; 

    printf("c: %p\n", c); 

    return 0; 
} 

, 그것은 GCC의 경고 또는 측정 장치의 아무도 나처럼 보인다 정의되지 않은 행동을 목표로 한 Clang은 이것이 실제로 UB라고 말합니다. 그 산술은 실제로 유효하고 나는 너무 pedantic인지, 또는 이것은 내가보고해야하는 그들의 점검 메커니즘의 결핍인가? 테스트

는 :

$ clang-3.3 -Weverything -g -O0 -fsanitize=undefined -fsanitize=null -fsanitize=address offsetnull.c -o offsetnull 
$ ./offsetnull 
c: 0xffffffffffffffff 

$ gcc-4.8 -g -O0 -fsanitize=address offsetnull.c -o offsetnull 
$ ./offsetnull 
c: 0xffffffffffffffff 

이 연타로 사용하고 충분히 공정 그래서 GCC가 나쁜 포인터 역 참조에 더 초점을 맞추고으로 꽤 잘 AddressSanitizer 것을 문서화 것으로 보인다. 그러나 다른 검사는 그것을 잡을하지 않는 중 : -/

편집 : 나는이 질문이 -fsanitize 플래그 생성 된 코드에서 잘 definedness의 동적 검사를 가능하게한다는 것이다 요청 이유의 일부. 그들이 잡았어야 할 것인가?

+5

배열의 일부가 아닌 포인터에 대해 산술 연산을 수행하는 것은 UB입니다 (배열이 아닌 포인터의 경우 one-past-the-end에 대해 +1은 예외입니다). – chris

+0

컴파일러는 한 번에 한 줄만 봅니다. 따라서 c가 NULL이라는 단서가 없습니다. LINT와 같은 것이 이것을 잡을 것입니다. 이 프로그램의 경우에는 C 변수를 역 참조하지 않으므로 무효화되지 않습니다. 이렇게하는 것이 좋습니다. 이점은 모든 f로 인해 64 비트 시스템에서 실행되고 있음을 볼 수 있다는 것입니다. (아마도 프로그램의 요점입니까?) –

+4

@ c.fogelklou : 당신은 완전히 그 지점을 놓쳤습니다. 다른 사람이 올린 내용을 아주 조심스럽게 읽어야합니다. 그들은 그 포인터를 형성하는 것이 정의 된 행동이 아니라는 것을 확인합니다 컴파일러는 실제로 않습니다. – Novelocrat

답변

19

배열을 가리 키지 않는 포인터에 대한 포인터 연산은 정의되지 않은 동작입니다.
또한 NULL 포인터를 Dereferencing하는 것은 정의되지 않은 동작입니다. c 배열 가리 키지 않기 때문

char *c = NULL; 
c--; 

정의 동작이 정의되지 않는다. 5.7.5 표준

C++ 11 :

일체형을 갖는식이에 첨가하거나 포인터로부터 감산하면, 결과는 포인터 피연산자의 형태를 갖는다. 포인터 피연산자가 배열 객체의 요소를 가리키고 배열이 충분히 큰 경우 결과는 원본 요소의 오프셋 요소를 가리키고 그 결과 원래 배열 요소의 첨자의 차이가 정수 표현식과 동일하게됩니다. 즉 식 P가 배열 객체의 i 번째 요소를 가리키는 경우 식 (P) + N (즉 N + (P)) 및 (P) -N (N은 값 n) 배열 객체의 i + n 번째 및 i - n 번째 요소에 각각 해당하는 요소가있는 경우 또한 표현식 P가 배열 객체의 마지막 요소를 가리키는 경우 표현식 (P) +1은 배열 객체의 마지막 요소보다 한 점 앞을 가리키며 표현식 Q가 배열 객체의 마지막 요소보다 한 점 앞을 가리키는 경우, 식 (Q) -1은 배열 객체의 마지막 요소를 가리 킵니다. 포인터 피연산자와 결과가 동일한 배열 객체의 요소를 가리 키거나 배열 객체의 마지막 요소 인 을 가리키는 경우 평가에서 오버플로가 발생하지 않아야합니다. 그렇지 않으면 동작이 정의되지 않습니다.

+2

분명히, NULL 포인터를 역 참조하는 것은 UB이며, C++ 11에서 설명한 것처럼 NULL 포인터를 '간접적으로 통과'하는 것과 같습니다. – Novelocrat

+1

배열의 일부가 아닌 포인터에 대한 산술 연산이 유효하지 않을 수 있습니다. – Novelocrat

+0

memmory-block을 사용하면 배열의 이름을 지정하는 것입니까? 또는 할당 된 memmory 또한 UB에서 pointer-arithmetic입니까? – dhein

15

예, 이것은 정의되지 않은 동작이며 -fsanitize=undefined이 잡아야 할 것입니다. 이것에 대한 점검을 추가하기 위해 이미 TODO 목록에 있습니다.

FWIW의 경우 C 및 C++ 규칙이 약간 다릅니다. 널 포인터에 0을 추가하고 다른 포인터에서 하나의 널 포인터를 뺀 것은 C에서 정의되지 않은 동작을 갖지만 C++에서는 그렇지 않습니다. 널 포인터에 대한 다른 모든 산술은 두 언어 모두에서 정의되지 않은 동작을합니다.

+3

[expr.add] p7의 의도는 null 포인터에 0을 추가하거나 두 개의 null 포인터를 빼는 것입니다. C++에서는 잘 정의되어 있지만 p5와 p6에서는 그 동작이 정의되지 않았다고 명시 적으로 말합니다. 일반적으로 표준의 한 부분이 프로그램의 동작을 정의하는 것처럼 보이고 다른 부분이 동작이 정의되지 않는다고 말하면, 그 행동은 정의되지 않았다. – hvd

+3

나는 p5와 p6의 표현을 향상 시키려고 노력했으나 (그들에게 잘못된 몇 가지 다른 것들이있다.) 그러나 지금까지 어떤 성공도 이루지 못했다. 또한 p6의 각주는 다른, 미적 호환되지 않는 포인터 연산 모델을 설명합니다. –

+0

@RichardSmith 쓰기/읽기 주소 0이 유효한 플랫폼에서 코드를 처리하는 것이 간단할까요 아니면 블랙리스트를 사용하기 만하면됩니까? –

6

null 포인터에 대한 산술 연산이 금지되어있을뿐만 아니라 dereference를 시도하여 null 포인터에 대한 산술 연산도 실패하면 null 포인터 트랩의 이점이 크게 저하됩니다.

표준에 정의 된 상황이 없기 때문에 널 포인터에 아무 것도 추가하지 않으면 합법적 인 포인터 값을 얻을 수 없습니다. 더욱이 구현이 그러한 동작에 대한 유용한 동작을 정의 할 수있는 상황은 드물고 일반적으로 컴파일러 내장 함수 (*)를 통해보다 잘 처리 될 수 있습니다. 그러나 많은 구현에서 널 포인터 연산이 트랩되지 않으면 널 포인터에 오프셋을 추가하면 유효하지 않지만 더 이상 인식 가능이 널 포인터가되는 포인터를 생성 할 수 있습니다. 그러한 포인터를 참조 해제하려는 시도는 포착되지 않지만 임의의 효과를 유발할 수 있습니다.

(null + offset) 및 (null-offset) 형식의 트래핑 포인터 계산은 이러한 위험을 제거합니다. 첫 번째 두 표현식에 의해 반환 된 값은 아무런 유용성을 갖지 않을 것이지만 보호는 반드시 트래핑 (포인터 - null), (null 포인터) 또는 (null - null)을 요구하지는 않을 것임을 유의하십시오 [구현이 null-null가 제로가되는 경우, 특정의 구현을 타겟으로 한 코드가, 경우에 따라서는 특수한 경우의 코드보다 효율적 일 가능성이 있습니다. null] 무효 인 포인터를 생성하지 않습니다. 또한, (null + 0) 및 (null-0) 중 하나를 포획하는 것보다는 null 포인터를 사용하는 것이 안전을 위협하지 않으며 사용자 코드 특수한 경우 null 포인터를 가질 필요가 없지만 컴파일러 이후 이점은 덜 매력적입니다 그렇게하려면 추가 코드를 추가해야합니다.

(*) 예를 들어, 8086 컴파일러의 이러한 내장 함수는 부호없는 16 비트 정수 "seg"및 "ofs"를 허용하고 주소가 우연히 제로가되었습니다. 8086의 주소 (0x0000 : 0x0000)는 일부 프로그램에서 액세스해야 할 수도있는 인터럽트 벡터이며 주소 (0xFFFF : 0x0010)는 주소 라인이 20 개 밖에없는 구형 프로세서에서 (0x0000 : 0x0000)와 동일한 실제 위치에 액세스하는 동안 24 개 이상의 주소 행을 가진 프로세서에서 실제 위치 0x100000에 액세스). 경우에 따라 포인터 (예 :)가 C 표준에서 인식하지 못하는 항목 (예 : 인터럽트 벡터와 같은 항목)을 가리키는 특별한 지정을 사용하고 해당 항목을 널 트랩하지 않거나 지정하지 않을 수도 있습니다 그 volatile 포인터는 그러한 방식으로 취급됩니다. 적어도 하나의 컴파일러에서 첫 번째 동작을 보았지만 두 번째 컴파일러는 본 적이 없다고 생각합니다.