가장 엄밀한 의미에서 이것은 정의되지 않은 동작이 아니라 구현 정의입니다. 따라서 비주류 아키텍처를 지원할 계획이라면 바람직하지 않지만 그렇게 할 수 있습니다 .
interjay가 제시 한 표준 인용문은 UB를 나타내는 좋은 인용문이지만 포인터 포인터 산술을 다루기 때문에 내 의견으로는 두 번째로 가장 좋은 것입니다 (재미있게, 하나는 명시 적으로 UB이지만 다른 하나는 그렇지 않습니다). 질문의 작업을 직접 다루는 단락이 있습니다.
[expr.post.incr] / [expr.pre.incr]
피연산자는 [...]이거나 완전히 정의 된 객체 유형에 대한 포인터입니다.
오, 잠깐만 요, 완전히 정의 된 객체 타입? 그게 다야? 정말, 타이핑 ? 그래서 당신은 전혀 물체가 필요하지 않습니까?
실제로 뭔가가 잘 정의되어 있지 않다는 힌트를 찾으려면 약간의 읽기가 필요합니다. 지금까지는 완벽하게 허용되는 것처럼 읽히지 만 제한은 없습니다.
[basic.compound] 3
하나가 가질 수있는 포인터 유형에 대해 설명하고 다른 세 가지 중 하나가 아니라면 작업 결과는 3.4 : invalid pointer에 분명히 해당 합니다.
그러나 포인터가 유효하지 않다고 말하지는 않습니다. 반대로, 포인터가 정기적으로 유효하지 않은 매우 일반적인 정상 조건 (예 : 저장 기간 종료)이 나열됩니다. 그것은 분명히 허용 가능한 일입니다. 그리고 실제로 :
[basic.stc] 4
유효하지 않은 포인터 값을 통한 간접 처리 및 유효하지 않은 포인터 값을 할당 해제 함수에 전달하면 동작이 정의되지 않습니다. 유효하지 않은 포인터 값을 사용하면 구현 정의 동작이 있습니다.
우리는 거기에 "다른 것"을하고 있으므로 정의되지 않은 행동이 아니라 구현에 따라 정의되므로 일반적으로 허용됩니다 (구현이 명시 적으로 다른 것을 말하지 않는 한).
불행히도, 그것은 이야기의 끝이 아닙니다. 최종 결과는 여기서부터 더 이상 변경되지 않지만 더 혼란스러워지고 "포인터"를 더 오래 검색합니다.
[basic.compound]
객체 포인터 유형의 유효한 값은 메모리의 바이트 주소 또는 널 포인터를 나타냅니다. T 유형의 객체가 주소 A에 있으면 값을 얻는 방법에 관계없이 해당 객체를 가리키고 있습니다.
[참고 : 예를 들어, 배열 끝을 지나는 주소는 해당 주소에있을 수있는 배열 요소 유형의 관련되지 않은 객체를 가리키는 것으로 간주됩니다. [...]].
다음과 같이 읽으십시오. 포인터 가 메모리의 어딘가를 가리키는 한 괜찮습니다.
[basic.stc.dynamic.safety] 포인터 값은 안전하게 파생 된 포인터입니다. [blah blah]
다음과 같이 읽으십시오 : 좋아, 안전하게 파생 된 것, 무엇이든. 이것이 무엇인지 설명하지 않으며 실제로 필요하다고 말하지도 않습니다. 안전하게 파생 된 목. 분명히 안전하지 않은 포인터를 계속 사용할 수 있습니다. 나는 그것들을 역 참조하는 것이 그렇게 좋은 생각은 아니지만 아마도 그것들을 갖는 것은 완벽하게 허용 될 것이라고 추측하고있다. 달리 말하지 않습니다.
구현은 편안한 포인터 안전성을 가질 수 있으며,이 경우 포인터 값의 유효성은 그것이 안전하게 파생 된 포인터 값인지에 의존하지 않습니다.
아, 그래서 내가 생각한 것만 중요하지 않을 수 있습니다. 그러나 기다려라. 즉, 그럴 수도 있습니다 . 내가 어떻게 알아?
대안 적으로, 구현은 엄격한 포인터 안전성을 가질 수 있는데,이 경우, 안전하게 파생 된 포인터 값이 아닌 포인터 값은 참조 된 완전한 객체가 동적 저장 기간이고 이전에 도달 가능하다고 선언되지 않은 한 유효하지 않은 포인터 값이다
잠깐, declare_reachable()
모든 포인터 를 불러야 할 수도 있습니까? 내가 어떻게 알아?
이제 intptr_t
잘 정의 된 로 변환하여 안전하게 파생 된 포인터를 정수로 표현할 수 있습니다. 물론 정수 인 경우 원하는대로 증가시키는 것이 합법적이고 잘 정의되어 있습니다.
그리고 예, intptr_t
다시 포인터 로 변환 할 수 있습니다 . 이는 또한 잘 정의되어 있습니다. 원래의 값이 아니기 만하면 더 이상 안전하게 파생 된 포인터를 가지고 있다고 보장 할 수 없습니다. 그럼에도 불구하고 구현 표준을 정의하면서 표준의 서한에 이르기까지 이것은 100 % 합법적 인 일입니다.
[expr.reinterpret.cast] 5
정수형 또는 열거 형의 값을 포인터로 명시 적으로 변환 할 수 있습니다. 포인터가 충분한 크기 [...]의 정수로 변환 된 후 같은 포인터 유형 [...] 원래 값으로 다시 변환되었습니다. 포인터와 정수 사이의 매핑은 그렇지 않으면 구현 정의됩니다.
캐치
포인터는 단지 일반적인 정수이며 포인터로만 사용됩니다. 오, 그게 사실이라면!
불행히도, 전혀 사실 이 아닌 아키텍처가 존재 하며, 단지 유효하지 않은 포인터를 생성하는 것 (포인터 레지스터에 포인팅하지 않고 포인터를 참조하는 것)만으로도 트랩이 발생합니다.
이것이 "구현 정의"의 기초입니다. 그것은 당신이 원할 때마다 포인터를 증가시키는 것은 물론 표준이 다루고 싶지 않은 오버플로를 일으킬 수 있다는 사실입니다 . 응용 프로그램 주소 공간의 끝이 오버플로의 위치와 일치하지 않을 수 있으며 특정 아키텍처의 포인터에 대한 오버플로와 같은 것이 있는지조차 알지 못합니다. 대체로 가능한 이점과 관련이없는 악몽입니다.
다른 한편으로는 과거의 과거 객체 조건을 다루는 것이 쉽습니다. 구현은 주소 공간의 마지막 바이트가 차지되도록 객체가 할당되지 않았는지 확인해야합니다. 따라서 보장하는 것이 유용하고 사소한 것으로 잘 정의되어 있습니다.