C++ 규격에 따라 합법인지 아닌지를 나타내는 첨자를 사용하여 단일 어레이 요소의 주소를 취득할 수 있습니까?
다음 코드는 C++ 규격에 의해 허용되지 않는다고 주장하는 것을 여러 번 보았습니다.
int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
이&array[5]
이 컨텍스트에서 합법적인 C++ 코드?
가능하면 스탠다드를 참조하여 답변해 주셨으면 합니다.
C기준에 부합하는지 알아보는 것도 재미있을 것 같습니다.그리고 표준 C++가 아니라면 왜 다른 방법으로 다루기로 결정되었는가?array + 5
또는&array[4] + 1
?
네, 합법이에요.C99 초안 기준:
§ 6.5.2.1 단락 2:
postfix 식 뒤에 대괄호로 둘러싸인 식
[]
는 배열 오브젝트 요소의 첨자 표기입니다.첨자 연산자의 정의[]
그것이다E1[E2]
와 동일하다(*((E1)+(E2)))
바이너리에 적용되는 변환 규칙 때문에+
연산자, if.E1
는 어레이 오브젝트(즉, 어레이 오브젝트의 초기 요소에 대한 포인터)이며,E2
정수입니다.E1[E2]
를 지정합니다.E2
의 -번째 요소E1
(제로에서 제외).
§ 6.5.3.2, 제3항(내건 포함):
단항
&
연산자는 오퍼랜드의 주소를 제공합니다.오퍼랜드의 타입이 「type」인 경우, 결과 타입은 「type」입니다.피연산자가 단항식의 결과인 경우*
연산자, 그 연산자도,&
연산자가 평가되고 결과는 둘 다 생략된 것처럼 나타납니다. 단, 연산자에 대한 제약조건은 여전히 적용되며 결과는 l값이 아닙니다.마찬가지로 피연산자가 연산자의 결과인 경우 & 연산자도 에 의해 암시된 단항도 평가되지 않으며 그 결과는 연산자가 제거되고 연산자가 연산자로 변경된 것과 같습니다.그렇지 않으면, 그 결과는 오퍼랜드에 의해 지정된 객체 또는 함수에 대한 포인터가 됩니다.
§ 6.5.6 제8항:
정수 유형을 가진 식을 포인터에 추가하거나 포인터에서 빼면 결과에는 포인터 피연산자 유형이 포함됩니다.포인터 오퍼랜드가 배열 객체의 요소를 가리키고 배열이 충분히 클 경우, 결과는 결과 및 원본 배열 요소의 첨자 차이가 정수식과 같도록 원래 요소로부터의 오프셋을 가리킵니다.바꿔 말하면, 이 표현은
P
를 가리키다i
배열 객체의 -번째 요소, 식(P)+N
(계속해서,N+(P)
)와(P)-N
(어디서)N
가치가 있다n
)는, 각각 다음의 항목을 가리킵니다.i+n
-th 및i−n
배열 객체의 -번째 요소(존재하는 경우).게다가 만약 그 표현이P
배열 객체의 마지막 요소인 식(expression)을 가리킵니다.(P)+1
배열 객체의 마지막 요소보다 한 칸 앞서서 식에 해당하는 경우Q
배열 객체의 마지막 요소(식)를 한 번 지나갑니다.(Q)-1
는 배열 객체의 마지막 요소를 가리킵니다.포인터 오퍼랜드와 결과 모두 동일한 배열 객체의 요소를 가리킬 경우 또는 배열 객체의 마지막 요소를 가리킬 경우 평가에서 오버플로가 발생하지 않습니다.그렇지 않을 경우 동작은 정의되지 않습니다.결과가 배열 객체의 마지막 요소를 1개 지나칠 경우, 해당 요소를 단일 객체의 피연산자로 사용할 수 없습니다.*
평가 대상 연산자.
표준에서는 포인터가 참조되지 않는 한 배열의 끝을 지나1개의 요소를 포인트 하는 것을 명시적으로 허가하고 있습니다.6.5.2.1 및 6.5.3.2에서는 다음 식과&array[5]
와 동등하다&*(array + 5)
에 상당합니다.(array+5)
이 값은 배열의 끝을 1개 지난 것을 가리킵니다.이것은 (6.5.3.2에 의한) 참조 해제가 되지 않기 때문에 합법입니다.
이 예는 합법적이지만 실제로는 경계 밖의 포인터를 사용하지 않기 때문입니다.
먼저 아웃바운더에 대해 설명하겠습니다(이 예에서 원패스 엔드 포인터가 사용되고 있는 것을 깨닫기 전에 원래 그렇게 질문을 해석했기 때문입니다).
일반적으로 Out-of-Bounds 포인터를 만들 수도 없습니다.포인터는 배열 내의 요소 또는 끝의 요소를 가리켜야 합니다.다른 곳은 없어요.
포인터는 존재조차 허용되지 않습니다. 즉, 포인터를 참조 해제하는 것도 허용되지 않습니다.
이 주제에 대한 기준의 내용은 다음과 같습니다.
5.7:5:
적분형식을 포인터에 추가하거나 포인터에서 빼면 포인터 피연산자 유형이 결과에 포함됩니다.포인터 오퍼랜드가 배열 객체의 요소를 가리키고 배열이 충분히 클 경우, 결과는 결과 및 원본 배열 요소의 첨자 차이가 적분식과 동일하도록 원래 요소로부터의 오프셋 요소를 가리킵니다.즉, 식 P가 배열 객체의 i번째 요소를 가리킬 경우, 식 P+N(등가 N+P) 및 (P)-N(여기서 N은 값 n을 가진다)이 각각 설치된 배열 객체의 i+n번째 및 i-n번째 요소를 가리킬 수 있다.또한 식 P가 배열 객체의 마지막 요소를 가리키면 식 (P)+1은 배열 객체의 마지막 요소를 가리키고 식 Q가 배열 객체의 마지막 요소를 가리키면 식 (Q)-1은 배열 객체의 마지막 요소를 가리키고 있다.포인터 오퍼랜드와 결과 모두 동일한 배열 객체의 요소를 가리킬 경우 또는 배열 객체의 마지막 요소를 가리킬 경우 평가는 오버와우를 생성하지 않아야 합니다.그렇지 않을 경우 동작은 정의되지 않습니다.
(내 것을 제외)
물론 이것은 operator+용입니다.만약을 위해 표준에서 어레이 서브스크립션에 대해 다음과 같이 기술되어 있습니다.
5.2.1:1:
표현
E1[E2]
(의식에 따라) 와 동일하다.*((E1)+(E2))
물론 분명한 경고가 있습니다.이 예에서는 실제로 Out-of-Bounds 포인터가 표시되지 않습니다."끝이 하나 지난" 포인터를 사용합니다.이것은 다릅니다.포인터는 (위에서 말한 바와 같이) 존재는 허용되지만, 내가 볼 때 표준에서는 포인터를 참조하는 것에 대해 아무런 언급이 없습니다.가장 가까운 것은 3.9.2:3 입니다.
[주의: 예를 들어, 어레이의 끝(5.7) 뒤에 있는 주소는 해당 주소에 있을 수 있는 어레이 요소 유형의 관련되지 않은 개체를 가리키는 것으로 간주됩니다.: end note ]
그 말은 법적으로 참조를 취소할 수 있다는 뜻인 것 같은데 그 장소에 대한 읽기나 쓰기의 결과는 명확하지 않아요
마지막 부분을 수정해 주신 ilproxyil 덕분에 질문의 마지막 부분에 답변해 드립니다.
array + 5
실제로는 아무것도 참조 해제하지 않습니다.단순히 포인터를 생성해 버립니다.array
.&array[4] + 1
디레퍼런스array+4
(완벽히 안전합니다)는 그 lvalue의 주소를 가져와서 그 주소에 추가합니다.그 결과, 1개의 past-the-end 포인터가 됩니다(단, 그 포인터는 참조되지 않습니다).&array[5]
references array + 5 (제가 보기엔 합법적이며, 위에서 설명한 바와 같이 "어레이 요소 유형의 관련되지 않은 객체"가 됩니다)그리고 나서, 그 요소의 주소도 충분히 합법적이라고 생각됩니다.
따라서 이 경우 최종 결과는 동일하지만 완전히 동일한 작업을 수행하지는 않습니다.
그것은 합법이다.
C++의 gcc 문서에 따르면&array[5]
합법입니다.C++ 와 C 의 어느쪽이든, 어레이의 끝을 1 개 지나면, 요소의 주소를 안전하게 지정할 수 있습니다.유효한 포인터를 얻을 수 있습니다.그렇게&array[5]
표현이 합법적이기 때문에
그러나 포인터가 유효한 주소를 가리키고 있는 경우에도 할당되지 않은 메모리에 대한 포인터의 참조를 해제하려고 하는 것은 아직 정의되지 않은 동작입니다.따라서 해당 식에 의해 생성된 포인터를 참조 해제하려고 하는 것은 포인터 자체가 유효하더라도 정의되지 않은 동작(즉, 불법)입니다.
하지만 실제로, 나는 그것이 보통 사고를 일으키지 않을 것이라고 생각한다.
편집: 참고로 STL 컨테이너의 end() 반복기는 일반적으로 이렇게 구현됩니다(원패스 더 엔드에 대한 포인터로서). 따라서 이는 합법적이라는 것을 증명하는 매우 좋은 증거입니다.
편집: 해당 주소로 포인터를 유지하는 것이 합법적인지 묻는 것이 아니라 포인터를 얻는 정확한 방법이 합법적인지 묻는 것이군요.나는 그것에 대해 다른 회답자들의 의견에 따르겠다.
이것은 합법적이며, 'lvalue to rvalue' 전환에 따라 달라집니다.핵심 문제 232의 마지막 행은 다음과 같습니다.
우리는 표준의 접근방식이 정상으로 보인다는 데 동의했다. 즉, p = 0; *p;는 본질적으로 오류가 아니다.lvalue-to-rvalue 변환은 정의되지 않은 동작을 제공합니다.
이것은 약간 다른 예이지만 '*'는 lvalue에서 rvalue로 변환되지 않습니다.따라서 표현식이 lvalue를 기대하는 '&'의 직접 피연산자임을 고려하면 동작이 정의됩니다.
저는 그것이 불법이라고 생각하지 않지만 &array[5]의 행동은 정의되지 않았다고 생각합니다.
5.2.1 [ expr . sub ]E1 [ E2 ]는 (정의상) *((E1)+(E2)와 동일합니다)
5.3.1 [expr.unary.op] 단항 * 연산자...결과는 식이 가리키는 개체 또는 함수를 나타내는 l값입니다.
표현식((E1)+(E2)이 실제로 객체를 가리키고 있지 않기 때문에 이 시점에서는 동작이 정의되어 있지 않습니다.또, 오브젝트가 지시되지 않는 한, 표준에서는 결과가 어떻게 되는지를 나타내고 있습니다.
- 1.3.12 [defns.undefined] 정의되지 않은 행동은 이 국제표준이 행동의 명시적 정의에 대한 설명을 생략할 경우에도 예상할 수 있다.
다른 곳에서 언급했듯이array + 5
그리고.&array[0] + 5
는, 어레이의 끝을 넘어 포인터를 취득하기 위한 유효하고 정의되고 있는 방법입니다.
위의 답변과 더불어 operator &은 클래스에서 덮어쓸 수 있음을 지적합니다.따라서 POD에 유효하더라도 유효하지 않은 오브젝트에 대해 실행하는 것은 좋지 않을 수 있습니다(처음에는 연산자&()를 덮어쓰는 것과 같습니다.
이것은 합법입니다.
int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
섹션 5.2.1 첨자 E1[E2]의 표현은 *(E1)+(E2)와 동일하다.
이를 통해 array_end도 동등하다고 할 수 있습니다.
int *array_end = &(*((array) + 5)); // or &(*(array + 5))
제5.3.1.1절 단항 연산자 '*' : 단항 연산자는 간접을 실시한다.단항 연산자가 적용되는 표현식은 객체 유형에 대한 포인터 또는 함수 유형에 대한 포인터여야 하며, 그 결과는 해당 식이 가리키는 객체 또는 함수를 가리키는 l값이어야 한다.식 유형이 "Tinter to T"인 경우 결과 유형은 "T"입니다. [주의: 불완전한 유형(cvoid 이외)에 대한 포인터를 참조할 수 있습니다.이렇게 얻은 l값은 (예를 들어 참조를 초기화하기 위해) 제한된 방법으로 사용할 수 있습니다.이 l값을 r값으로 변환해서는 안 됩니다(4.1 참조). - end note ]
위의 중요한 부분:
'결과는 객체 또는 함수를 참조하는 값입니다.'
단항 연산자 '*'가 int를 참조하는 l값을 반환하고 있습니다(참조 해제 없음).그런 다음 단항 연산자 '&'는 l값의 주소를 가져옵니다.
아웃오브바운드의 디레퍼런스(reference)가 없는 한, 동작은 표준으로 완전하게 커버되어 모든 동작이 정의됩니다.그래서 내가 읽은 바로는 위 내용이 완전히 합법적이다.
많은 STL 알고리즘이 올바르게 정의되는 동작에 의존한다는 것은 표준위원회가 이미 이를 검토했다는 일종의 힌트이며, 나는 이를 명시적으로 커버하는 무언가가 있다고 확신한다.
다음 코멘트 섹션에서는 다음 두 가지 인수를 제시합니다.
(읽어주세요: 하지만 길고, 우리 둘 다 트롤처럼 되어버립니다.
인수 1
5.7항 5항 때문에 불법이다.
적분형식을 포인터에 추가하거나 포인터에서 빼면 포인터 피연산자 유형이 결과에 포함됩니다.포인터 오퍼랜드가 배열 객체의 요소를 가리키고 배열이 충분히 클 경우, 결과는 결과 및 원본 배열 요소의 첨자 차이가 적분식과 동일하도록 원래 요소로부터의 오프셋 요소를 가리킵니다.즉, 식 P가 배열 객체의 i번째 요소를 가리키면 식 P+N(동등 N+P) 및 (P)-N(여기서 N은 값 n을 가진다)이 각각 설치된 배열 객체의 i+n번째 및 i-n번째 요소를 가리키고 있다.또한 식 P가 배열 객체의 마지막 요소를 가리키면 식 (P)+1은 배열 객체의 마지막 요소를 가리키고 식 Q가 배열 객체의 마지막 요소를 가리키면 식 (Q)-1은 배열 객체의 마지막 요소를 가리키고 있다.포인터 오퍼랜드와 결과 모두 동일한 배열 객체의 요소를 가리킬 경우 또는 배열 객체의 마지막 요소를 가리킬 경우 평가에서 오버플로가 발생하지 않습니다.그렇지 않을 경우 동작은 정의되지 않습니다.
또한 섹션은 관련이 있지만 정의되지 않은 동작을 나타내지 않습니다.어레이 내의 모든 요소는 어레이 내에 있거나 (위 단락에서 잘 정의되어 있습니다) 끝부분을 지나야 합니다.
인수 2:
다음 두 번째 인수는 다음과 같습니다.*
는 참조 해제 연산자입니다.
이 용어는 '*' 연산자를 설명하는 데 일반적으로 사용되는 용어이지만, 표준에서는 '참조 해제'라는 용어가 언어와 그것이 기본 하드웨어에 미치는 의미에 대해 잘 정의되지 않기 때문에 의도적으로 피한다.
어레이의 끝을 넘어 메모리에 액세스 하는 것은 확실히 정의되지 않은 동작입니다.나는 이 일을 납득할 수 없다.unary * operator
는, 이 콘텍스트의 메모리(표준에 정의되어 있는 방법이 아님)에 액세스 합니다.이 맥락에서 (표준에 의해 정의된) (5.3.1.1 참조)unary * operator
a를 반환하다lvalue referring to the object
제가 이해하기로는 이것은 기본 메모리에 대한 접근이 아닙니다.이 표현의 결과는 즉시 에 의해 사용됩니다.unary & operator
에 의해 참조되는 객체의 주소를 반환하는 연산자lvalue referring to the object
.
위키피디아와 비표준 출처에 대한 많은 다른 언급들이 제시되어 있다.이 모든 것이 무관하다고 생각합니다.C++는 표준으로 정의됩니다.
결론:
나는 내가 고려하지 않았을 수도 있고 나의 위의 주장이 틀렸다는 것을 증명할 수도 있는 기준의 많은 부분이 있다는 것을 인정하기를 바란다.NON은 다음과 같습니다.이것이 UB임을 나타내는 표준 레퍼런스를 보여주면그럴게요.
- 정답을 남겨주세요.
- 모든 것을 대문자로 써라 이건 바보같은 짓이고 나는 모두가 읽기에 틀렸다.
이것은 인수가 아닙니다.
전 세계의 모든 것이 C++ 표준으로 정의되는 것은 아닙니다.당신의 생각을 여세요.
작업 초안(n2798):
unary & operator의 결과는 오퍼랜드에 대한 포인터입니다.피연산자는 l값 또는 한정 ID여야 한다.첫 번째 경우, 식의 유형이 "T"이면 결과의 유형은 "T에 포인터"가 된다(p.103).
array [ 5 ]는 내가 아는 한 qualified-id가 아닙니다(목록은 87페이지).가장 가까운 것은 식별자로 보이지만 array는 식별자 어레이[5]는 식별자가 아닙니다."lvalue는 객체 또는 함수를 의미하기 때문에 lvalue가 아닙니다.(p.76) 어레이[5]는 분명히 함수가 아니며 유효한 오브젝트를 참조하는 것은 보증되지 않습니다(어레이 + 5는 마지막으로 할당된 어레이 요소 뒤에 있기 때문입니다).
분명히, 경우에 따라서는 동작할 수 있지만, 유효한 C++나 안전성이 없습니다.
주의: 어레이를 통과하기 위해 추가하는 것은 합법입니다(1113페이지).
식 P [포인터]가 배열 객체의 마지막 요소를 가리키면 식 P+1은 배열 객체의 마지막 요소를 가리키고 식 Q가 배열 객체의 마지막 요소를 가리키면 식 Q-1은 배열 객체의 마지막 요소를 가리키게 된다.포인터 오퍼랜드와 결과 모두 동일한 배열 객체의 요소를 가리킬 경우 또는 배열 객체의 마지막 요소를 지나칠 경우 평가에서 오버와우를 생성해서는 안 됩니다.
하지만 &를 사용하여 그렇게 하는 것은 불법입니다.
그것이 합법적인데도 왜 관례에서 벗어나야 하는가?어레이 + 5가 더 짧기 때문에 제 생각에는 읽기 쉬울 것 같습니다.
편집: 대칭으로 하고 싶은 경우는, 기입할 수 있습니다.
int* array_begin = array;
int* array_end = array + 5;
다음과 같은 이유로 정의되지 않은 동작이어야 합니다.
Out-of-Bounds 요소에 액세스하려고 하면 정의되지 않은 동작이 발생합니다.따라서 이 표준은 구현이 그 경우에 예외를 두는 것을 금지하지 않는다(즉, 요소에 접근하기 전에 구현 확인 경계).한다면
& (array[size])
로 정의되었다.begin (array) + size
외부 액세스의 경우에 예외를 두는 실장은, 표준에 준거하지 않게 됩니다.이 수율을 올리는 것은 불가능하다.
end (array)
배열이 배열이 아니라 임의 수집 유형인 경우.
C++ 표준, 5.19, 단락 4:
주소 상수식은 lvalue...에 대한 포인터입니다.포인터는 unary & operator... 또는 array (4.2)...type의 식을 사용하여 명시적으로 작성해야 합니다.첨자 연산자 []...는 주소 상수 식을 생성하는 데 사용할 수 있지만 이러한 연산자를 사용하여 개체의 값에 액세스해서는 안 됩니다.첨자 연산자를 사용할 경우, 그 연산자의 피연산자 중 하나는 정수식이어야 한다.
제가 보기에 &array[5]는 주소 상수 표현인 합법적인 C++인 것 같습니다.
일반적인 예가 아닌 특정 예제의 경우 허용됩니다.AFAIK는 법적으로 할당된 메모리 블록보다 한 블록 이상 이동할 수 있습니다.어레이의 끝에서 1만큼 멀리 있는 요소에 액세스하려고 하는 일반적인 경우에는 작동하지 않습니다.
방금 검색한 C-Faq : 링크 텍스트
그것은 완전히 합법적이다.
stl로부터의 벡터<>템플릿 클래스는 myVec.end()를 호출하면 바로 이 작업을 수행합니다.여기서는 어레이의 끝부분을 지나는1개의 요소를 가리키는 포인터(여기서는 반복기)를 가져옵니다.
언급URL : https://stackoverflow.com/questions/988158/take-the-address-of-a-one-past-the-end-array-element-via-subscript-legal-by-the
'programing' 카테고리의 다른 글
gcc의 __attribute__(packed) / #pragma pack은 안전하지 않습니까? (0) | 2022.08.21 |
---|---|
소켓 접속과 읽기 타임아웃의 차이점은 무엇입니까? (0) | 2022.08.21 |
TypeScript를 사용할 때 단일 파일 구성 요소에서 Vue를 가져오는 방법 (0) | 2022.08.21 |
Vuex 모듈 게터 및 돌연변이에 액세스하는 방법 (0) | 2022.08.18 |
Java에서 String이 불변인 이유는 무엇입니까? (0) | 2022.08.18 |