2개의 NULL 포인터를 감산하는 동작이 정의되어 있습니까?
(C99 및/또는 C++98에 따라) 정의된 2개의 비포이드 포인터 변수의 차이(둘 다NULL
가치있는가?
예를 들어 다음과 같은 버퍼 구조를 가지고 있다고 합시다.
struct buf {
char *buf;
char *pwrite;
char *pread;
} ex;
말합니다,ex.buf
어레이 또는 malloc's 메모리를 가리킵니다.내 코드가 항상 그걸 보장해준비는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) 2개의 포인터를 감산했을 경우, 양쪽 모두 같은 배열 객체의 요소 또는 배열 객체의 마지막 요소 뒤의 요소를 가리켜야 합니다.그 결과, 2개의 배열 요소의 첨자의 차이가 발생합니다.[...]
그리고 § 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):
두 개의 포인터 [...]가 모두 null이고 두 개의 포인터가 감산된 경우 결과는 std::ptrdiff_t 형식으로 변환된 값 0과 동일합니다.
다른 사람들이 말했듯이, C99는 두 포인터 사이의 추가/감산을 동일한 배열 객체의 것으로 해야 합니다.NULL은 유효한 개체를 가리키지 않으므로 감산 시 사용할 수 없습니다.
편집: 이 답변은 C에만 유효하며, 답변할 때 C++ 태그를 보지 못했습니다.
아니요, 포인터 산술은 동일한 개체 내에서 포인터를 가리키는 포인터에만 허용됩니다.C 표준 늘 포인터의 정의에서는 오브젝트를 가리키지 않기 때문에 이것은 정의되지 않은 동작입니다.
(단, 합리적인 컴파일러라면 어떤 컴파일러라도 반환될 것입니다.0
하지만 누가 알겠어요.)
C 기준서는 이 경우에 행동요구에 대해 어떠한 요구사항도 부과하지 않지만, 많은 실무적용에서는 이 기준서에서 요구하는 최소치를 초과하는 경우에 포인터 산술의 행동을 명시하고 있다.
모든 C 실장 및 거의 모든 (전부는 아닐지라도) C 유사 사투리 실장에서는 다음 보증이 모든 포인터에 적용됩니다.p
어느 쪽이든*p
또는*(p-1)
에 몇 가지 오브젝트를 나타냅니다.
- 임의의 정수값
z
포인터 값은 0이 됩니다.(p+z)
그리고.(p-z)
모든 면에서 와 동등할 것이다p
단, 양쪽이 모두 일정하다는 점만 제외하고p
그리고.z
일정합니다. - 모든 사용자용
q
와 동등하다.p
, 표현.p-q
그리고.q-p
둘 다 0이 됩니다.
이러한 보증을 null을 포함한 모든 포인터 값에 대해 유지하면 사용자 코드에서 null 체크가 필요하지 않을 수 있습니다.게다가 대부분의 플랫폼에서는 Null인지 아닌지에 관계없이 모든 포인터 값에 대해 그러한 보증을 유지하는 코드를 생성하는 것이 Null을 특별히 처리하는 것보다 간단하고 저렴합니다.그러나 일부 플랫폼에서는 제로 더하기 또는 빼기 시에도 늘 포인터를 사용하여 포인터 계산을 수행하려는 시도가 트랩될 수 있습니다.이러한 플랫폼에서는 보증을 유지하기 위해 포인터 조작에 추가해야 하는 컴파일러에 의한 늘체크 수가 결과적으로 생략될 수 있는 사용자 생성 늘체크 수를 크게 초과할 수 있습니다.
보증을 유지하는 데 비용이 많이 드는 구현이 존재하지만, 이들로부터 혜택을 받는 프로그램이 거의 없는 경우, "null+zero" 연산을 트랩하는 것이 타당하며, 이러한 구현의 사용자 코드에는 보증이 불필요할 수 있는 수동 null 체크가 포함되어 있어야 합니다.이러한 허용치는 보증 유지 가치가 비용을 초과하는 나머지 99.44%의 구현에 영향을 미치지 않을 것으로 예상되었다.그러한 이행은 그러한 보증을 지지해야 하지만, 작성자가 기준서의 작성자에게 그러한 사실을 알릴 필요는 없다.
C++의 저자들은 포인터 산술의 성능을 상당히 저하시킬 수 있는 플랫폼에서도 어떤 대가를 치르더라도 위의 보증을 유지해야 한다고 결정했다.이들은 유지 비용이 많이 드는 플랫폼에서도 보증의 가치가 비용을 초과할 것으로 판단했습니다.이러한 태도는 C++를 C보다 더 높은 수준의 언어로 취급하려는 욕구에 의해 영향을 받았을 수 있다.C 프로그래머는 특정 타깃 플랫폼이 (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==CJB 및 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가 "null일 수 없음"이므로 이후의 null 포인터 체크가 생략되어 실제 "문제"와 무관한 장소에서 코드가 끊어질 수 있다고 생각할 수 있습니다.
언급URL : https://stackoverflow.com/questions/8128168/is-the-behavior-of-subtracting-two-null-pointers-defined
'source' 카테고리의 다른 글
AWS RDS 연결 중단됨 Haproxy (0) | 2022.09.25 |
---|---|
MariaDb SQL 주입 (1) | 2022.09.25 |
OkHttp의 response.body.toString()이 문자열을 반환하도록 가져올 수 없습니다. (1) | 2022.09.25 |
Java ThreadLocal 변수가 정적이어야 하는 이유 (1) | 2022.09.25 |
Maria에서 다중 마스터 복제 오류를 수정하는 방법DB (0) | 2022.09.18 |