두 개의 NULL 포인터를 빼는 동작이 정의되어 있습니까?
무효가 아닌 포인터 변수 두 개가 모두 NULL
가치 가있는 경우 정의 된 (C99 및 / 또는 C ++ 98에 따라) 차이가 있습니까?
예를 들어 다음과 같은 버퍼 구조가 있다고 가정합니다.
struct buf {
char *buf;
char *pwrite;
char *pread;
} ex;
말, ex.buf
배열 또는 일부 malloc으로 할당 한 메모리를 가리키는. 내 코드는 항상 보장하는 경우 pwrite
와 pread
그 배열 내의 점을하거나 과거 한 다음, 그 상당히 확신 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;
}
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
.
C ++ 표준 (5.7 [expr.add] / 7)에서 발견했습니다.
두 포인터 [...]가 모두 널이고 두 포인터를 빼면 결과는 std :: ptrdiff_t 유형으로 변환 된 값 0과 동일합니다.
다른 사람들이 말했듯이 C99는 동일한 배열 객체의 두 포인터 사이에 더하기 / 빼기가 필요합니다. NULL은 유효한 객체를 가리 키지 않기 때문에 빼기에 사용할 수 없습니다.
편집 :이 대답은 C에만 유효하며 대답했을 때 C ++ 태그를 보지 못했습니다.
아니요, 포인터 산술은 동일한 개체 내를 가리키는 포인터에만 허용됩니다. C 표준 널 포인터의 정의에 따라 어떤 객체도 가리 키지 않기 때문에 이것은 정의되지 않은 동작입니다.
(하지만 합리적인 컴파일러가 바로 반환 할 것이라고 생각 0
하지만 누가 알겠습니까?)
C 표준은이 경우 동작에 대한 요구 사항을 부과하지 않지만 많은 구현에서 표준에서 요구하는 최소값을 넘어서 많은 경우 포인터 산술 동작을 지정합니다.
어떤 부합 C 구현 및에 거의 모든 (모든 경우) C는 같은 방언, 다음과 같은 보장은 어떤 포인터 개최의 구현 p
같은 그 중 하나 *p
또는 *(p-1)
식별하는 오브젝트 :
- 임의의 정수 값을
z
제로, 포인터 값을 동일(p+z)
과(p-z)
에 모든면에서 동등한 것p
둘 경우 그들은 단지 상수가 될 것이다 제외p
하고는z
일정하다. - 어떤 들어
q
있는 동일합니다p
표현,p-q
그리고q-p
것 모두 수율 제로.
null을 포함한 모든 포인터 값에 대해 이러한 보장을 유지하면 사용자 코드에서 일부 null 검사가 필요하지 않을 수 있습니다. 또한 대부분의 플랫폼에서 모든 포인터 값이 null인지 여부에 관계없이 모든 포인터 값에 대해 이러한 보증을 유지하는 코드를 생성하는 것은 null을 특별히 처리하는 것보다 더 간단하고 저렴합니다. 그러나 일부 플랫폼은 0을 더하거나 뺄 때에도 널 포인터로 포인터 산술을 수행하려는 시도에 트랩 될 수 있습니다. 이러한 플랫폼에서 보증을 유지하기 위해 포인터 작업에 추가해야하는 컴파일러 생성 널 검사의 수는 결과로 생략 될 수있는 사용자 생성 널 검사의 수를 훨씬 초과하는 경우가 많습니다.
If there were an implementation where the cost of upholding the guarantees would be great, but few if any programs would receive any benefit from them, it would make sense to allow it to trap "null+zero" computations, and require that user code for such an implementation include the manual null checks that the guarantees could have made unnecessary. Such an allowance was not expected to affect the other 99.44% of implementations, where the value of upholding the guarantees would exceed the cost. Such implementations should uphold such guarantees, but their authors shouldn't need the authors of the Standard to tell them that.
The authors of C++ have decided that conforming implementations must uphold the above guarantees at any cost, even on platforms where they could substantially degrade the performance of pointer arithmetic. They judged that the value of the guarantees even on platforms where they would be expensive to uphold would exceed the cost. Such an attitude may have been affected by a desire to treat C++ as a higher-level language than C. A C programmer could be expected to know when a particular target platform would handle cases like (null+zero) in unusual fashion, but C++ programmers weren't expected to concern themselves with such things. Guaranteeing a consistent behavioral model was thus judged to be worth the cost.
Of course, nowadays questions about what is "defined" seldom have anything to do with what behaviors a platform can support. Instead, it is now fashionable for compilers to--in the name of "optimization"--require that programmers manually write code to handle corner cases which platforms would previously have handled correctly. For example, if code which is supposed to output n
characters starting at address p
is written as:
void out_characters(unsigned char *p, int n)
{
unsigned char *end = p+n;
while(p < end)
out_byte(*p++);
}
older compilers would generate code that would reliably output nothing, with no side-effect, if p==NULL and n==0, with no need to special-case n==0. On newer compilers, however, one would have to add extra code:
void out_characters(unsigned char *p, int n)
{
if (n)
{
unsigned char *end = p+n;
while(p < end)
out_byte(*p++);
}
}
which an optimizer may or may not be able to get rid of. Failing to include the extra code may cause some compilers to figure that since p "can't possibly be null", any subsequent null pointer checks may be omitted, thus causing the code to break in a spot unrelated to the actual "problem".
참고URL : https://stackoverflow.com/questions/8128168/is-the-behavior-of-subtracting-two-null-pointers-defined
'Development Tip' 카테고리의 다른 글
FROM의 하위 쿼리에는 별칭이 있어야합니다. (0) | 2020.10.19 |
---|---|
const 생성자는 실제로 어떻게 작동합니까? (0) | 2020.10.19 |
JavaFX의 Platform.runLater 및 Task (0) | 2020.10.18 |
Xcode 8에서 'Vary for Traits'는 무엇입니까? (0) | 2020.10.18 |
Java2D : 선 너비 늘리기 (0) | 2020.10.18 |