두 개의 NULL 포인터를 빼는 동작이 정의되어 있습니까?


78

무효가 아닌 포인터 변수 두 개가 모두 NULL가치 가있는 경우 정의 된 (C99 및 / 또는 C ++ 98에 따라) 차이가 있습니까?

예를 들어 다음과 같은 버퍼 구조가 있다고 가정합니다.

struct buf {
  char *buf;
  char *pwrite;
  char *pread;
} ex;

말, ex.buf배열 또는 일부 malloc으로 할당 한 메모리를 가리키는. 내 코드는 항상 보장하는 경우 pwritepread그 배열 내의 점을하거나 과거 한 다음, 그 상당히 확신 ex.pwrite - ex.pread항상 정의됩니다. 그러나 어떤 경우 pwrite와 것은 pread모두 NULL입니다. 둘을 빼는 것이 정의되어 (ptrdiff_t)0있거나 엄격하게 준수하는 코드가 NULL에 대한 포인터를 테스트해야합니까? 내가 관심있는 유일한 경우는 포인터가 모두 NULL (초기화되지 않은 버퍼를 나타냄) 인 경우입니다. 그 이유는 위의 가정이 충족되면 완전히 호환되는 "사용 가능한"기능과 관련이 있습니다.

size_t buf_avail(const struct s_buf *b)
{     
    return b->pwrite - b->pread;
}

1
수술을 두 번 이상 시도 했습니까?
Hunter McMillen 2011

10
무슨 말이야? 이 작업의 결과가 95 % (5 %가 AS / 400이라고 가정 해 보겠습니다)의 구현 결과가 0이고 나쁜 일이 발생하지 않는다는 사실을 알고 있습니다. 구현 세부 사항에 관심이 없습니다. 내 질문은 특정 표준 정의와 관련이 있습니다.
John Luebs 2011

8
Hunter McMillen : 그것은 나쁜 접근 방식입니다. "내가 포인터를 int에 저장했는데 아무 일도 일어나지 않았습니다. 다른 컴퓨터와 컴파일러를 확인했는데 아무 일도 일어나지 않았습니다. 그런 다음 64 비트 컴퓨터가 나왔습니다." 지금은 작동하지만 정의되지 않은 동작에 의존하는 경우 향후 작동하지 않을 수 있습니다.
Maciej Piechotka 2011

3
난 당신의 코드가되어 보장 당신을 칭찬 보장 관련 표준이 아닌 당신이 테스트 플랫폼에서 작업에 일어났다 단지로 통지에 의해 작업에.
David Schwartz 2011

1
@TobySpeight : 8086 컴파일러는 near한정된 null 포인터와 한정된 null 포인터에 대해 다른 표현을 far가지지 만 null far포인터에 대해 여러 표현을 사용 합니까? 널의 경우 near포인터가로 변환됩니다 far널에 비해 차례에 포인터 far무슨 예를 들면 DS가 0x1234 같을 때 포인터, (1) 0000는 0000에 conveted 도착 : 0000; (2) 0x0000은 0x1234 : 0x0000으로 변환되지만 비교 연산자는 양쪽 세그먼트가 0 인 경우를 확인하거나 (3) 0x0000은 0x0000 : 0x0000과 같지 않은 것을 비교하는 0x1234 : 0x0000으로 변환됩니다.
supercat

답변:


100

C99에서는 기술적으로 정의되지 않은 동작입니다. C99 §6.5.6에 따르면 :

7) 이러한 연산자의 목적을 위해, 배열의 요소가 아닌 개체에 대한 포인터는 개체 유형이 요소 유형 인 길이 1 인 배열의 첫 번째 요소에 대한 포인터와 동일하게 동작합니다.

[...]

9) 두 포인터를 뺄 때, 둘 다 동일한 배열 객체의 요소를 가리켜 야합니다. 또는 배열 객체의 마지막 요소를 지나는 하나를 가리켜 야합니다. 결과는 두 배열 요소의 첨자의 차이입니다. [...]

그리고 §6.3.2.3 / 3은 다음과 같이 말합니다.

값이 0 인 정수 상수 표현식 또는 유형으로 캐스트 된 표현식을 void *널 포인터 상수라고합니다. 55) 널 포인터 상수가 포인터 유형으로 변환되면 널 포인터 라고하는 결과 포인터 는 객체 또는 함수에 대한 포인터와 같지 않은 비교를 보장합니다.

따라서 널 포인터는 객체와 같지 않기 때문에 6.5.6 / 9의 전제 조건을 위반하므로 정의되지 않은 동작입니다. 그러나 실제로는 거의 모든 컴파일러가 부작용없이 0의 결과를 반환 할 것이라고 확신합니다.

C89에서는 표준 문구가 약간 다르지만 정의되지 않은 동작입니다.

반면에 C ++ 03은이 인스턴스에서 동작을 정의했습니다. 표준은 두 개의 널 포인터를 뺄 때 특별한 예외를 만듭니다. C ++ 03 §5.7 / 7 내용 :

포인터 값에서 값 0을 더하거나 빼면 결과가 원래 포인터 값과 비교됩니다. 두 포인터가 동일한 객체를 가리 키거나 둘 다 동일한 배열의 끝을 지나서 하나를 가리 키거나 둘 다 null이고 두 포인터를 빼면 결과는 유형으로 변환 된 값 0과 동일합니다 ptrdiff_t.

C ++ 11 (C ++ 14의 최신 초안, n3690)은 C ++ 03과 동일한 표현을 사용 std::ptrdiff_t하며 ptrdiff_t.


14
완전성으로 인해 현재 가장 좋은 답변입니다.
John Dibling

이것은 "9) 두 포인터를 빼면 결과가 0이되고 그렇지 않으면 둘 다 같은 배열 객체의 요소를 가리켜 야합니다 ..."
R .. GitHub STOP HELPING ICE

@R .., 명확하게 "비교 동일"이라고 말해야합니까? 두 개의 널 포인터는 동일한 값을 포함하지 않을 수 있으므로 같지 않습니다.
Jens Gustedt

또한 다가오는 C1X 표준최신 초안 에도 동일한 언어가있는 것 같습니다. 나는 이것이 실제로 감독이고 언어위원회가 그것을 고치기를 바랍니다.
Adam Rosenfield 2011

두 개의 널 포인터는 동등 비교로 인해 동일한 입니다. 물론 그들은 같은 표현을 가지고 있지 않을 수도 있습니다 .
R .. GitHub STOP HELPING ICE

36

C ++ 표준 (5.7 [expr.add] / 7)에서 발견했습니다.

두 포인터 [...]가 모두 널이고 두 포인터를 빼면 결과는 std :: ptrdiff_t 유형으로 변환 된 값 0과 동일합니다.

다른 사람들이 말했듯이 C99는 동일한 배열 객체의 두 포인터 사이에 더하기 / 빼기가 필요합니다. NULL은 유효한 객체를 가리 키지 않기 때문에 빼기에 사용할 수 없습니다.


4
+1 : 흥미 롭기 때문에 C ++는이 동작을 명시 적으로 정의하지만 C는 그렇지 않습니다.
Oliver Charlesworth

23

편집 :이 대답은 C에만 유효하며 대답했을 때 C ++ 태그를 보지 못했습니다.

아니요, 포인터 산술은 동일한 개체 내를 가리키는 포인터에만 허용됩니다. C 표준 널 포인터의 정의에 따라 어떤 객체도 가리 키지 않기 때문에 이것은 정의되지 않은 동작입니다.

(하지만 합리적인 컴파일러가 바로 반환 할 것이라고 생각 0하지만 누가 알겠습니까?)


5
이것은 올바르지 않습니다. 5.7 [expr.add] / 7 : "두 포인터가 동일한 객체를 가리 키거나 동일한 배열의 끝을 지나서 하나를 지나는 두 지점 또는 둘 다 null 이고 두 포인터를 빼면 결과는 값 0과 동일하게 비교됩니다. 유형으로 변환 std::ptrdiff_t. "
CB Bailey

1
이것은 C ++의 특정 컨텍스트에서 올바르지 않습니다. 태그 된 다른 언어는 어떻습니까?
John Dibling 2011

8
와우,이 절름발이 질문이 C / C ++ 사양 차이에 영향을 줄 것이라고는 생각하지 못했습니다.
John Luebs 2011

3
@Jens : FAQ 및 사이트 관리자는 답변을 개선 할 수있는 경우 수정하도록 권장합니다. 당신의 대답은 이전보다 지금 더 낫습니다. 나는 당신이 라인 밖으로 생각 사이트의 정책을 주어, 정직하게, 기분을 상하게 할 의도하지 않고 할 수 불쾌. 참조 : stackoverflow.com/privileges/edit
John Dibling 2011

2
Jens, 보통 나는 당신과 동의합니다. 나는 누군가의 대답을 편집하지 않고 의견에 대한 나의 의견 차이를 지적하려고 노력합니다. 깃털이 덜 뭉쳐 져서 무언가를 배울 수도 있습니다. 아니면 내가 틀렸고 내 편집이 비생산적 일 수도 있습니다. 하지만이 경우 John의 편집이 정당하다고 생각합니다. 귀하의 답변이 최고 등급이지만 100 % 정확하지는 않았기 때문입니다. 사람들이 대안을 고려하지 않고 올바른 답을 찾기 위해 "붙이지 않게"하는 것이 필요했습니다.
Mark Ransom 2011

0

C 표준은이 경우 동작에 대한 요구 사항을 부과하지 않지만 많은 구현에서 표준에서 요구하는 최소값을 넘어서 많은 경우 포인터 산술 동작을 지정합니다.

어떤 부합 C 구현 및에 거의 모든 (모든 경우) C는 같은 방언, 다음과 같은 보장은 어떤 포인터 개최의 구현 p같은 그 중 하나 *p또는 *(p-1)식별하는 오브젝트 :

  • 임의의 정수 값을 z제로, 포인터 값을 동일 (p+z)(p-z)에 모든면에서 동등한 것 p둘 경우 그들은 단지 상수가 될 것이다 제외 p하고는 z일정하다.
  • q해당하는 항목 p에 대해 p-q및 표현식 q-p은 모두 0을 생성합니다.

null을 포함한 모든 포인터 값에 대해 이러한 보장을 유지하면 사용자 코드에서 일부 null 검사가 필요하지 않을 수 있습니다. 또한 대부분의 플랫폼에서 null인지 여부에 관계없이 모든 포인터 값에 대해 이러한 보장을 유지하는 코드를 생성하는 것은 null을 특별히 처리하는 것보다 더 간단하고 저렴합니다. 그러나 일부 플랫폼은 0을 더하거나 뺄 때에도 널 포인터로 포인터 산술을 수행하려는 시도를 트랩 할 수 있습니다. 이러한 플랫폼에서 보증을 유지하기 위해 포인터 작업에 추가해야하는 컴파일러 생성 널 검사의 수는 많은 경우 결과로 생략 될 수있는 사용자 생성 널 검사의 수를 크게 초과합니다.

보증을 유지하는 데 드는 비용이 크지 만 프로그램이 혜택을받을 수있는 프로그램이 거의 없다면 "null + zero"계산을 트랩하고 해당 사용자 코드를 다음과 같이 요구하는 것이 합리적 일 것입니다. 이러한 구현에는 보증이 불필요하게 만들 수있는 수동 null 검사가 포함됩니다. 그러한 수당은 보장을 유지하는 가치가 비용을 초과하는 다른 99.44 %의 구현에 영향을 미칠 것으로 예상되지 않았습니다. 그러한 구현은 그러한 보장을 유지해야하지만, 작성자는 표준 작성자가이를 말할 필요가 없어야합니다.

C ++의 작성자는 포인터 산술의 성능을 크게 저하시킬 수있는 플랫폼에서도 어떤 비용 으로든 준수 구현이 위의 보증을 유지해야한다고 결정했습니다. 그들은 유지 비용이 많이 드는 플랫폼에서도 보증의 가치가 비용을 초과 할 것이라고 판단했습니다. 이러한 태도는 C ++를 C보다 높은 수준의 언어로 취급하려는 욕구의 영향을 받았을 수 있습니다. AC 프로그래머는 특정 대상 플랫폼이 비정상적인 방식으로 (null + zero)와 같은 경우를 처리 할 때를 알 것으로 예상 할 수 있지만 C ++ 프로그래머는 그런 것들에 대해 스스로를 걱정할 것으로 예상되지 않았습니다. 따라서 일관된 행동 모델을 보장하는 것은 비용의 가치가있는 것으로 판단되었습니다.

물론 오늘날 "정의 된"항목에 대한 질문은 플랫폼이 지원할 수있는 동작과 거의 관련이 없습니다. 대신, 이제는 컴파일러가 "최적화"라는 이름으로, 이전에 플랫폼이 올바르게 처리했을 코너 케이스를 처리하기 위해 프로그래머가 수동으로 코드를 작성하도록 요구하는 것이 유행입니다. 예를 들어 n주소에서 시작하는 문자 를 출력해야하는 코드 p가 다음과 같이 작성되면

void out_characters(unsigned char *p, int n)
{
  unsigned char *end = p+n;
  while(p < end)
    out_byte(*p++);
}

이전 컴파일러는 p == NULL 및 n == 0 인 경우 특별한 경우 n == 0이 필요없이 부작용없이 안정적으로 아무것도 출력하지 않는 코드를 생성합니다. 그러나 최신 컴파일러에서는 추가 코드를 추가해야합니다.

void out_characters(unsigned char *p, int n)
{
  if (n)
  {
    unsigned char *end = p+n;
    while(p < end)
      out_byte(*p++);
  }
}

옵티마이 저가 제거 할 수도 있고 제거 할 수도 없습니다. 추가 코드를 포함하지 않으면 일부 컴파일러는 p "널이 될 수 없음"이므로 후속 널 포인터 검사가 생략되어 실제 "문제"와 관련이없는 지점에서 코드가 중단 될 수 있다고 생각할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.