development

아래 첨자를 통해 하나의 과거 배열 요소의 주소를 가져옵니다. C ++ 표준에 의해 합법적입니까?

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

아래 첨자를 통해 하나의 과거 배열 요소의 주소를 가져옵니다. 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?


귀하의 예제는 합법적이지만 실제로 범위를 벗어난 포인터를 사용하지 않기 때문입니다.

범위를 벗어난 포인터를 먼저 처리해 보겠습니다 (예제가 대신 한 끝 포인터를 사용한다는 사실을 알아 차리기 전에 제가 원래 질문을 해석 한 방식이기 때문입니다).

일반적으로 범위를 벗어난 포인터 만들 수도 없습니다 . 포인터는 배열 내의 요소 또는 끝을 지나는 요소를 가리켜 야합니다 . 다른 곳은 없습니다.

포인터는 존재하는 것도 허용되지 않습니다. 이는 분명히 역 참조도 허용되지 않음을 의미합니다.

표준이 주제에 대해 말하는 내용은 다음과 같습니다.

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))

물론, 명백한주의 사항이 있습니다. 귀하의 예제는 실제로 범위를 벗어난 포인터를 표시하지 않습니다. "끝을 지나는"포인터를 사용하는데 이는 다릅니다. 포인터는 존재할 수 있지만 (위에서 말했듯이) 표준은 내가 볼 수있는 한 역 참조에 대해 아무것도 말하지 않습니다. 내가 찾을 수있는 가장 가까운 것은 3.9.2 : 3입니다.

[참고 : 예를 들어, 배열의 끝 (5.7)을 지나는 주소는 해당 주소에있을 수있는 배열 요소 유형의 관련되지 않은 개체를 가리키는 것으로 간주됩니다. —end note]

예, 당신은 법적으로 그것을 역 참조 할 수 있지만 그 위치를 읽거나 쓰는 결과는 명시되지 않았습니다.

질문의 마지막 부분에 답하여 여기 마지막 부분을 수정 해 주신 ilproxyil에게 감사드립니다.

  • array + 5실제로는 어떤 것도 역 참조하지 않으며 단순히 array.
  • &array[4] + 1역 참조 array+4(완벽하게 안전함)는 해당 lvalue의 주소를 가져 와서 해당 주소에 하나를 추가합니다. 그 결과 마지막 포인터 하나가 생성됩니다 (하지만 해당 포인터는 역 참조되지 않습니다.
  • &array[5] 배열 +5를 역 참조 (내가 볼 수있는 한 합법적이며 위에서 말했듯이 "배열 요소 유형의 관련되지 않은 객체"가 됨)를 역 참조한 다음 해당 요소의 주소를 가져옵니다. 이는 또한 충분히 합법적 인 것처럼 보입니다.

따라서이 경우 최종 결과는 동일하지만 완전히 동일한 작업을 수행하지 않습니다.


예, 합법적입니다. 로부터 C99 표준 초안 :

§6.5.2.1, 단락 2 :

접미사 식 뒤에 대괄호로 묶인 식은 []배열 개체의 요소를 첨자로 지정하는 것입니다. 첨자 연산자의 정의 []E1[E2]동일하다 (*((E1)+(E2))). 이항 +연산자에 적용되는 변환 규칙으로 인해 if E1가 배열 객체 (동일하게 배열 객체의 초기 요소에 대한 포인터)이고 E2정수인 경우 (0부터 계산) E1[E2]의- E2번째 요소를 지정합니다 E1.

§6.5.3.2, 단락 3 (내 강조) :

단항 &연산자는 피연산자의 주소를 산출합니다. 피연산자가 '유형이있는 경우 유형 ' ', 결과는 유형' '포인터가 유형을 ' '. 피연산자가 단항 *연산자 의 결과 이면 해당 연산자도 연산자도 &평가 되지 않으며 결과는 연산자에 대한 제약 조건이 계속 적용되고 결과가 lvalue가 아니라는 점을 제외하고는 둘 다 생략 된 것과 같습니다. 마찬가지로, 피연산자가 결과 없다면 []연산자 둘 오퍼레이터 및도 단항 *의해 암시 []평가되고있는 것처럼 결과가 &운전자가 제거하고 []작업자가 변경되었다 +연산자. 그렇지 않으면 결과는 피연산자로 지정된 개체 또는 함수에 대한 포인터입니다.

§6.5.6, 단락 8 :

포인터에서 정수 유형이있는 표현식을 더하거나 빼면 결과는 포인터 피연산자의 유형을 갖습니다. 포인터 피연산자가 배열 개체의 요소를 가리키고 배열이 충분히 큰 경우 결과는 결과와 원래 배열 요소의 첨자의 차이가 정수 식과 같도록 원래 요소에서 오프셋 된 요소를 가리 킵니다. 즉, 표현식 이 배열 객체 P의- i번째 요소를 가리키는 경우, 표현식 (P)+N(동등하게, N+(P)) 및 (P)-N( N값이있는 곳) n각각 배열 객체 의- i+n번째 및- i−n번째 요소를 가리 킵니다. 있다. 또한 표현이P배열 객체의 마지막 요소를 (P)+1가리키고 표현식은 배열 객체 의 마지막 요소를 하나 지나고 , 표현식 이 배열 객체 Q의 마지막 요소를지나 하나를 가리키면 표현식 은 배열 객체 (Q)-1의 마지막 요소를 가리 킵니다. 포인터 피연산자와 결과가 동일한 배열 개체의 요소를 가리 키거나 배열 개체의 마지막 요소를 지나는 요소를 가리키면 평가에서 오버플로가 발생하지 않습니다. 그렇지 않으면 동작이 정의되지 않습니다. 결과가 배열 객체의 마지막 요소를 하나 지나면 *평가 되는 단항 연산자 의 피연산자로 사용되지 않습니다 .

표준은 포인터가 역 참조되지 않은 경우 배열의 끝을 지나서 한 요소를 가리킬 수 있도록 명시 적으로 허용 합니다. 6.5.2.1 및 6.5.3.2으로 발현이 &array[5]동등 &*(array + 5)에 해당하는 (array+5)어레이의 끝을지나 하나 가리키는. 이로 인해 역 참조 (6.5.3.2)가 발생하지 않으므로 합법적입니다.


그것은 이다 법적.

C ++에 대한 GCC 문서에 따르면 , &array[5]합법적이다. C ++ C 모두 에서 배열 끝을 지나서 요소를 안전하게 주소 지정할 수 있습니다. 유효한 포인터를 얻게됩니다. 그래서 &array[5]표현은 합법적입니다.

그러나 포인터가 유효한 주소를 가리키는 경우에도 할당되지 않은 메모리에 대한 포인터 역 참조를 시도하는 것은 정의되지 않은 동작입니다. 따라서 해당 표현식에 의해 생성 된 포인터를 역 참조하려는 시도는 포인터 자체가 유효하더라도 정의되지 않은 동작 (즉, 불법)입니다.

실제로는 일반적으로 충돌을 일으키지 않을 것이라고 생각합니다.

편집 : 그건 그렇고, 이것은 일반적으로 STL 컨테이너에 대한 end () 반복기가 구현되는 방법입니다 (끝을 한 번에 대한 포인터로), 그래서 그것은 합법적 인 관행에 대한 꽤 좋은 증거입니다.

편집 : 오, 이제 그 주소에 대한 포인터를 보유하는 것이 합법적인지 묻는 것이 아니라 포인터를 얻는 정확한 방법이 합법적인지 확인합니다. 나는 그것에 대해 다른 응답자들에게 연기 할 것입니다.


나는 이것이 합법적이라고 믿으며 'lvalue to rvalue'변환이 발생하는 것에 달려 있습니다. 마지막 줄 핵심 문제 232 는 다음과 같습니다.

우리는 표준의 접근 방식이 괜찮아 보인다는 데 동의했습니다. p = 0; *피; 본질적으로 오류가 아닙니다. lvalue에서 rvalue로 변환하면 정의되지 않은 동작이 발생합니다.

이것은 약간 다른 예이지만 '*'는 lvalue에서 rvalue로 변환되지 않으므로 표현식이 lvalue를 예상하는 '&'의 직접적인 피연산자 인 경우 동작이 정의됩니다.


나는 그것이 불법이라고 생각하지 않지만 & array [5]의 동작이 정의되지 않았다고 생각합니다.

  • 5.2.1 [expr.sub] E1 [E2]는 (정의상) * ((E1) + (E2))와 동일합니다.

  • 5.3.1 [expr.unary.op] 단항 * 연산자 ... 결과는 표현식이 가리키는 객체 또는 함수를 참조하는 lvalue입니다.

이 시점에서 ((E1) + (E2)) 표현식이 실제로 객체를 가리 키지 않았기 때문에 정의되지 않은 동작이 있으며 표준은 그렇지 않으면 결과가 무엇인지를 말합니다.

  • 1.3.12 [defns.undefined] 정의되지 않은 행동은이 표준이 행동의 명시적인 정의에 대한 설명을 생략 할 때 예상 될 수 있습니다.

다른 곳에 언급 된 바와 같이, array + 5그리고 &array[0] + 5어레이의 끝을 넘어 포인터를 얻는 유효한 잘 정의 된 방식이다.


위의 답변 외에도 operator &는 클래스에 대해 재정의 될 수 있음을 지적합니다. 따라서 POD에 유효하더라도 유효하지 않다는 것을 알고있는 객체에 대해 수행하는 것은 아마도 좋은 생각이 아닙니다 (처음에 operator & ()를 재정의하는 것과 유사합니다).


이것은 합법적입니다.

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 단항 연산자 '*': 단항 * 연산자는 간접을 수행합니다. 적용되는 표현식은 객체 유형에 대한 포인터이거나 함수 유형에 대한 포인터 여야하며 결과는 객체를 참조하는 lvalue입니다. 또는 표현식이 가리키는 함수 . 표현식의 유형이 "T에 대한 포인터"인 경우 결과 유형은 "T"입니다. [참고 : 불완전한 유형 (cv void 제외)에 대한 포인터는 역 참조 될 수 있습니다. 이렇게 얻은 lvalue는 제한된 방식으로 사용할 수 있습니다 (예 : 참조 초기화). 이 lvalue는 rvalue로 변환되지 않아야합니다 (4.1 참조). — 끝 참고]

위의 중요한 부분 :

'결과는 객체 또는 함수를 참조하는 lvalue입니다'.

The unary operator '*' is returning a lvalue referring to the int (no de-refeference). The unary operator '&' then gets the address of the lvalue.

As long as there is no de-referencing of an out of bounds pointer then the operation is fully covered by the standard and all behavior is defined. So by my reading the above is completely legal.

The fact that a lot of the STL algorithms depend on the behavior being well defined, is a sort of hint that the standards committee has already though of this and I am sure there is a something that covers this explicitly.

The comment section below presents two arguments:

(please read: but it is long and both of us end up trollish)

Argument 1

this is illegal because of section 5.7 paragraph 5

When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integral expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i + n-th and i − n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

And though the section is relevant; it does not show undefined behavior. All the elements in the array we are talking about are either within the array or one past the end (which is well defined by the above paragraph).

Argument 2:

The second argument presented below is: * is the de-reference operator.
And though this is a common term used to describe the '*' operator; this term is deliberately avoided in the standard as the term 'de-reference' is not well defined in terms of the language and what that means to the underlying hardware.

Though accessing the memory one beyond the end of the array is definitely undefined behavior. I am not convinced the unary * operator accesses the memory (reads/writes to memory) in this context (not in a way the standard defines). In this context (as defined by the standard (see 5.3.1.1)) the unary * operator returns a lvalue referring to the object. In my understanding of the language this is not access to the underlying memory. The result of this expression is then immediately used by the unary & operator operator that returns the address of the object referred to by the lvalue referring to the object.

Many other references to Wikipedia and non canonical sources are presented. All of which I find irrelevant. C++ is defined by the standard.

Conclusion:

I am wiling to concede there are many parts of the standard that I may have not considered and may prove my above arguments wrong. NON are provided below. If you show me a standard reference that shows this is UB. I will

  1. Leave the answer.
  2. Put in all caps this is stupid and I am wrong for all to read.

This is not an argument:

Not everything in the entire world is defined by the C++ standard. Open your mind.


Working draft (n2798):

"The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. In the first case, if the type of the expression is “T,” the type of the result is “pointer to T.”" (p. 103)

array[5] is not a qualified-id as best I can tell (the list is on p. 87); the closest would seem to be identifier, but while array is an identifier array[5] is not. It is not an lvalue because "An lvalue refers to an object or function. " (p. 76). array[5] is obviously not a function, and is not guaranteed to refer to a valid object (because array + 5 is after the last allocated array element).

Obviously, it may work in certain cases, but it's not valid C++ or safe.

Note: It is legal to add to get one past the array (p. 113):

"if the expression P [a pointer] points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow"

But it is not legal to do so using &.


Even if it is legal, why depart from convention? array + 5 is shorter anyway, and in my opinion, more readable.

Edit: If you want it to by symmetric you can write

int* array_begin = array; 
int* array_end = array + 5;

It should be undefined behaviour, for the following reasons:

  1. Trying to access out-of-bounds elements results in undefined behaviour. Hence the standard does not forbid an implementation throwing an exception in that case (i.e. an implementation checking bounds before an element is accessed). If & (array[size]) were defined to be begin (array) + size, an implementation throwing an exception in case of out-of-bound access would not conform to the standard anymore.

  2. It's impossible to make this yield end (array) if array is not an array but rather an arbitrary collection type.


C++ standard, 5.19, paragraph 4:

An address constant expression is a pointer to an lvalue....The pointer shall be created explicitly, using the unary & operator...or using an expression of array (4.2)...type. The subscripting operator []...can be used in the creation of an address constant expression, but the value of an object shall not be accessed by the use of these operators. If the subscripting operator is used, one of its operands shall be an integral constant expression.

Looks to me like &array[5] is legal C++, being an address constant expression.


If your example is NOT a general case but a specific one, then it is allowed. You can legally, AFAIK, move one past the allocated block of memory. It does not work for a generic case though i.e where you are trying to access elements farther by 1 from the end of an array.

Just searched C-Faq : link text


It is perfectly legal.

The vector<> template class from the stl does exactly this when you call myVec.end(): it gets you a pointer (here as an iterator) which points one element past the end of the array.

참고URL : https://stackoverflow.com/questions/988158/take-the-address-of-a-one-past-the-end-array-element-via-subscript-legal-by-the

반응형