( 이 질문과 답변을 참조하십시오 .)
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+1
A는 포인터 과거 – 더 – 끝 그것이되지 않도록, 객체에 대한 포인터 와 나는 dereferencable하지 믿습니다.
표준 권리의 수정에 대한 해석입니까, 아니면 인용 된 문장의 삭제를 보상하는 다른 규칙이 있습니까?
답변
이 표준 권리의 수정에 대한 해석입니까, 아니면이 인용문의 삭제를 보상하는 다른 규칙이 있습니까?
예,이 해석은 맞습니다. 끝을 지나는 포인터는 단순히 해당 주소를 가리키는 다른 포인터 값으로 변환 할 수 없습니다.
새로운 [basic.compound] / 3 는 다음과 같이 말합니다.
포인터 유형의 모든 값은 다음 중 하나입니다.
(3.1) 개체 또는 함수에 대한 포인터 (포인터는 개체 또는 함수를 가리킨다 고 함) 또는
(3.2) 개체 끝을 지나는 포인터 ([expr .add]) 또는
그것들은 상호 배타적입니다. p1+1
객체에 대한 포인터가 아니라 끝을 지나는 포인터입니다. p1+1
가 아닌 x[1]
에서 크기 1 배열 의 가설 을 가리 킵니다 . 이 두 개체는 포인터 상호 변환이 불가능합니다.p1
p2
비표준 메모도 있습니다.
[참고 : 개체의 끝 ([expr.add])을 지나는 포인터는 해당 주소에있을 수있는 개체 유형의 관련없는 개체를 가리키는 것으로 간주되지 않습니다. […]
의도를 명확히합니다.
TC는 다수의 의견 (에서 지적 하듯이 , 특히이 중 하나 )이 실제로 구현하는 시도와 함께 제공되는 문제의 특수한 경우 std::vector
이다 – [v.data(), v.data() + v.size())
아직 요구가 유효한 범위로하고 vector
소위, 배열 객체를 생성하지 않습니다 정의 된 포인터 산술 만 벡터의 주어진 객체에서 가상의 단일 크기 배열의 끝을 지나서 이동합니다. 더 많은 리소스를 보려면 CWG 2182 , 이 표준 토론 및 주제에 대한 두 가지 개정판 인 P0593R0 및 P0593R1 (특히 섹션 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)-유효하지 않은 포인터 값.객체의 끝을 가리키는 포인터이거나 객체의 끝을 지나는 포인터 유형의 값은 객체가 차지하는 메모리 (4.4) 의 첫 번째 바이트 또는 객체가
차지하는 스토리지의 끝 이후 메모리의 첫 번째 바이트의 주소를 나타냅니다. , 각각. [참고 : 개체의 끝 (8.7)을 지나는 포인터는 관련없는 항목 을 가리키는 것으로 간주되지 않습니다.해당 주소에있을 수있는 개체 유형의 개체입니다. 포인터 값은 표시하는 스토리지가 스토리지 기간의 끝에 도달하면 무효화됩니다. 6.7 참조. —end note] 포인터 산술 (8.7) 및 비교 (8.9, 8.10)를 위해 n 개 요소로 구성된 배열 x의 마지막 요소 끝을 지나는 포인터는 가상 요소 x [에 대한 포인터와 동일한 것으로 간주됩니다. 엔]. 포인터 유형의 값 표현은 구현에 따라 정의됩니다. 레이아웃 호환 유형에 대한 포인터는 동일한 값 표현 및 정렬 요구 사항 (6.11)을 가져야합니다.
노트의 끝 과거 포인터는 … 객체가 가리키는 때문에 여기에 적용되지 않습니다 p1
및 p2
하지 관련이없는 : 포인터를 arithmetics 스토리지를 제공하는 객체 내부 의미하기 때문에,하지만 같은 완전한 개체로 중첩 된 p2 - p1
정의를하고있다 (&buffer[sizeof(int)] - buffer]) / sizeof(int)
그것은 1입니다.
그래서 p1 + 1
에 대한 포인터 *p2
이며 *(p1 + 1) = 10;
동작을 정의하고의 값을 설정했습니다 *p2
.
또한 C ++ 14와 현재 (C ++ 17) 표준 간의 호환성에 대한 C4 부록을 읽었습니다. 단일 문자 배열에서 동적으로 생성 된 객체간에 포인터 산술을 사용할 가능성을 제거하는 것은 IMHO가 일반적으로 사용되는 기능이기 때문에 여기에서 인용해야하는 중요한 변경 사항입니다. 호환성 페이지에 아무것도 없기 때문에 금지하는 것이 표준의 의도가 아님을 확인한 것 같습니다.
특히, 기본 생성자가없는 클래스에서 객체 배열의 일반적인 동적 생성을 무효화합니다.
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
그런 다음 배열의 첫 번째 요소에 대한 포인터로 사용할 수 있습니다.
답변
여기에 제공된 답변을 확장하기 위해 수정 된 문구에서 제외되는 내용의 예가 있습니다.
경고 : 정의되지 않은 동작
#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;
}
완전히 구현에 의존하는 (그리고 깨지기 쉬운) 이유 때문에이 프로그램의 가능한 결과는 다음과 같습니다.
0x7fff1e4f2a64 0x7fff1e4f2a60 4
same
10
이 출력은 두 배열 (이 경우)이 메모리에 저장되어의 ‘끝을 지나서 하나’ A
가의 첫 번째 요소의 주소 값을 보유하는 것을 보여줍니다 B
.
개정 된 사양은 관계 A+1
가 B
. ‘값을 얻는 방법에 관계없이’라는 구구에서는 ‘A + 1’이 ‘B [0]’을 가리키면 ‘B [0]’에 대한 유효한 포인터라고 말합니다. 그것은 좋을 수 없으며 결코 의도가 아닙니다.