development

올바른 주소와 유형을 가진 포인터가 C ++ 17 이후로 여전히 유효한 포인터입니까?

big-blog 2020. 10. 8. 08:16
반응형

올바른 주소와 유형을 가진 포인터가 C ++ 17 이후로 여전히 유효한 포인터입니까?


( 이 질문과 답변을 참조하십시오 .)

C ++ 17 표준 이전에는 [basic.compound] / 3 에 다음 문장이 포함되었습니다 .

유형 T의 개체가 주소 A에있는 경우 값이 주소 A 인 cv T * 유형의 포인터는 값을 얻은 방법에 관계없이 해당 개체를 가리 킵니다.

그러나 C ++ 17 이후로이 문장은 제거되었습니다 .

예를 들어이 문장이이 예제 코드를 정의했으며 C ++ 17 이후로 정의되지 않은 동작이라고 생각합니다.

 alignas(int) unsigned char buffer[2*sizeof(int)];
 auto p1=new(buffer) int{};
 auto p2=new(p1+1) int{};
 *(p1+1)=10;

C ++ 17 전에 p1+1에 주소를 가지고 *p2있으므로, 오른쪽 유형이 *(p1+1)에 대한 포인터입니다 *p2. C ++에서 17 p1+1A는 포인터 과거 - 더 - 끝 그것이되지 않도록, 객체에 대한 포인터 와 나는 dereferencable하지 믿습니다.

표준 권리의 수정에 대한 해석입니까, 아니면 인용 된 문장의 삭제를 보상하는 다른 규칙이 있습니까?


표준 권리의 수정에 대한 해석입니까, 아니면이 인용 된 문장의 삭제를 보상하는 다른 규칙이 있습니까?

예,이 해석은 맞습니다. 끝을 지나는 포인터는 단순히 해당 주소를 가리키는 다른 포인터 값으로 변환 할 수 없습니다.

새로운 [basic.compound] / 3 는 다음과 같이 말합니다.

포인터 유형의 모든 값은 다음 중 하나입니다.
(3.1) 개체 또는 함수에 대한 포인터 (포인터는 개체 또는 함수를 가리킨다 고 함) 또는
(3.2) 개체 끝을 지나는 포인터 ([expr .add]) 또는

그것들은 상호 배타적입니다. p1+1객체에 대한 포인터가 아니라 끝을 지나는 포인터입니다. p1+1아닌 x[1]에서 크기 1 배열 의 가설 가리 킵니다 . 이 두 개체는 포인터 상호 변환이 불가능합니다.p1p2

비 규범 적 메모도 있습니다.

[참고 : 개체의 끝 ([expr.add])을 지나는 포인터는 해당 주소에있을 수있는 개체 유형의 관련없는 개체를 가리키는 것으로 간주되지 않습니다. [...]

의도를 명확히합니다.


TC는 다수의 의견 (에서 지적 하듯이 , 특히 이것 )이 실제로 구현하는 시도와 함께 제공되는 문제의 특수한 경우 std::vector이다 - [v.data(), v.data() + v.size())그러나 요구가 유효한 범위로하고 vector그렇게 배열 개체를 만들지 않습니다 정의 된 포인터 산술 만 벡터의 주어진 객체에서 가상의 단일 크기 배열의 끝을 지나서 이동합니다. 더 많은 리소스를 보려면 CWG 2182 , 이 표준 토론 및 주제에 대한 두 가지 개정판 인 P0593R0P0593R1 (구체적으로 섹션 1.3)을 참조하십시오.


귀하의 예에서 *(p1 + 1) = 10;UB 는 크기 1 의 배열 끝을 지나기 때문에 UB이어야합니다 . 그러나 배열이 더 큰 char 배열에서 동적으로 구성 되었기 때문에 여기서는 매우 특별한 경우입니다.

동적 개체 생성은 C ++ 표준의 n4659 초안 4.5 C ++ 개체 모델 [intro.object] §3에 설명되어 있습니다.

3 "array of N unsigned char"유형 또는 "array of N std :: byte"(21.2.1) 유형의 다른 객체 e와 연관된 스토리지에 완전한 객체가 생성 (8.3.4)되면 해당 어레이는 스토리지를 제공합니다. 생성 된 객체에 대해 다음과 같은 경우 :
(3.1) — e의 수명이 시작되고 끝나지 않았고,
(3.2) — 새 객체의 스토리지가 e 내에 완전히 들어 맞는 경우,
(3.3) — 이들을 충족하는 더 작은 배열 객체가 없습니다. 제약.

3.3은 다소 불분명 해 보이지만 아래 예는 의도를 더 명확하게합니다.

struct A { unsigned char a[32]; };
struct B { unsigned char b[16]; };
A a;
B *b = new (a.a + 8) B; // a.a provides storage for *b
int *p = new (b->b + 4) int; // b->b provides storage for *p
// a.a does not provide storage for *p (directly),
// but *p is nested within a (see below)

실시 예에 따라서, buffer어레이는 스토리지 제공 모두 *p1*p2.

다음 단락 모두에 대한 완전한 객체 것을 증명 *p1하고 *p2있습니다 buffer:

4 다음과 같은 경우 객체 a는 다른 객체 b 내에 중첩됩니다.
(4.1) — a는 b의 하위 객체이거나
(4.2) — b는 a 또는
(4.3)에 대한 스토리지를 제공합니다. — a 가 c 내에 중첩 된 객체 c가있는 경우 , c는 b 내에 중첩됩니다.

5 모든 객체 x에 대해 다음과 같이 결정되는 x의 완전한 객체라는 객체가 있습니다.
(5.1) — x가 완전한 객체이면 x의 완전한 객체는 그 자체입니다.
(5.2) — 그렇지 않으면 x의 완전한 객체는 x를 포함하는 (고유 한) 객체의 완전한 객체입니다.

이것이 설정되면 C ++ 17 용 n4659 초안의 다른 관련 부분은 [basic.coumpound] §3 (내 강조)입니다.

3 ... 포인터 유형의 모든 값은 다음 중 하나입니다.
(3.1) — 객체 또는 함수에 대한 포인터 (포인터는 객체 또는 함수를
가리킴) 또는 (3.2) — 끝을 지나는 포인터 객체 (8.7) 또는
(3.3)-해당 유형에 대한 널 포인터 값 (7.11) 또는
(3.4)-유효하지 않은 포인터 값.

A value of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory (4.4) occupied by the object or the first byte in memory after the end of the storage occupied by the object, respectively. [ Note: A pointer past the end of an object (8.7) is not considered to point to an unrelated object of the object’s type that might be located at that address. A pointer value becomes invalid when the storage it denotes reaches the end of its storage duration; see 6.7. —end note ] For purposes of pointer arithmetic (8.7) and comparison (8.9, 8.10), a pointer past the end of the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical element x[n]. The value representation of pointer types is implementation-defined. Pointers to layout-compatible types shall have the same value representation and alignment requirements (6.11)...

The note A pointer past the end... does not apply here because the objects pointed to by p1 and p2 and not unrelated, but are nested into the same complete object, so pointer arithmetics make sense inside the object that provide storage: p2 - p1 is defined and is (&buffer[sizeof(int)] - buffer]) / sizeof(int) that is 1.

So p1 + 1 is a pointer to *p2, and *(p1 + 1) = 10; has defined behaviour and sets the value of *p2.


I have also read the C4 annex on the compatibility between C++14 and current (C++17) standards. Removing the possibility to use pointer arithmetics between objects dynamically created in a single character array would be an important change that IMHO should be cited there, because it is a commonly used feature. As nothing about it exist in the compatibility pages, I think that it confirms that it was not the intent of the standard to forbid it.

In particular, it would defeat that common dynamic construction of an array of objects from a class with no default constructor:

class T {
    ...
    public T(U initialization) {
        ...
    }
};
...
unsigned char *mem = new unsigned char[N * sizeof(T)];
T * arr = reinterpret_cast<T*>(mem); // See the array as an array of N T
for (i=0; i<N; i++) {
    U u(...);
    new(arr + i) T(u);
}

arr can then be used as a pointer to the first element of an array...


To expand on the answers given here is an example of what I believe the revised wording is excluding:

Warning: Undefined Behaviour

#include <iostream>
int main() {
    int A[1]{7};
    int B[1]{10};
    bool same{(B)==(A+1)};

    std::cout<<B<< ' '<< A <<' '<<sizeof(*A)<<'\n';
    std::cout<<(same?"same":"not same")<<'\n';
    std::cout<<*(A+1)<<'\n';//!!!!!  
    return 0;
}

For entirely implementation dependent (and fragile) reasons possible output of this program is:

0x7fff1e4f2a64 0x7fff1e4f2a60 4
same
10

That output shows that the two arrays (in that case) happen to be stored in memory such that 'one past the end' of A happens to hold the value of the address of the first element of B.

The revised specification is ensuring that regardless A+1 is never a valid pointer to B. The old phrase 'regardless of how the value is obtained' says that if 'A+1' happens to point to 'B[0]' then it's a valid pointer to 'B[0]'. That can't be good and surely never the intention.

참고URL : https://stackoverflow.com/questions/48062346/is-a-pointer-with-the-right-address-and-type-still-always-a-valid-pointer-since

반응형