아래 첨자를 통해 한 번 지나간 배열 요소의 주소를 가져옵니다.C++ 표준에 따라 합법적인가요?

StackOverflow https://stackoverflow.com/questions/988158

문제

나는 C++ 표준에서 다음 코드가 허용되지 않는다는 주장을 여러 번 보았습니다.

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

~이다 &array[5] 이 맥락에서 합법적인 C++ 코드는 무엇입니까?

가능하다면 표준을 참조하여 답변을 받고 싶습니다.

C 표준을 충족하는지 아는 것도 흥미로울 것입니다.그리고 표준 C++이 아니라면 왜 이를 C++와 다르게 취급하기로 결정했습니까? array + 5 또는 &array[4] + 1?

도움이 되었습니까?

해결책

예제는 합법적이지만 실제로는 바운드 외 포인터를 사용하지 않기 때문입니다.

먼저 바운드 포인터를 다루겠습니다.

일반적으로, 당신은 허용되지 않습니다 만들다 바운드 외 포인터. 포인터는 배열 내의 요소를 가리거나 끝을 지나서. 다른 곳은 없습니다.

포인터는 존재하지 않아도됩니다. 즉, 당신은 분명히 그것을 피할 수 없습니다.

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

5.7:5:

적분 유형을 갖는식이 포인터에 추가되거나 빼면 결과는 포인터 피연산자의 유형을 갖습니다. 포인터 오페라가 배열 객체의 요소를 가리키고 배열이 충분히 큰 경우 결과는 원래 요소에서 요소 오프셋을 가리켜 결과 및 원래 배열 요소의 첨자 차이가 적분 표현식과 동일합니다. 다시 말해, 표현식 P가 어레이 객체의 I-th 요소를 가리키면 표현식 (p)+n (동등하게, n+(p)) 및 (p) -n (여기서 n이 값 n을 갖는) 포인트 배열 객체의 I+N-th 및 I-N-TH 요소가 각각 존재하는 경우. 또한, 표현식 P가 배열 객체의 마지막 요소를 가리키면, 표현식 (p) +1은 배열 객체의 마지막 요소를 지나서 한 점을 지적하고, 표현식 q가 배열 객체의 마지막 요소를 지나치는 경우. 표현식 (Q) -1은 배열 객체의 마지막 요소를 가리 킵니다. 포인터 피연산자와 결과 지점이 동일한 배열 객체의 요소 또는 배열 객체의 마지막 요소를 지나면 평가는 오버 플로우를 생성하지 않아야합니다. 그렇지 않으면, 행동은 끝납니다.

(강조 광산)

물론 이것은 연산자+를위한 것입니다. 따라서 배열 구독에 대한 표준이 다음과 같습니다.

5.2.1:1:

표현식 E1[E2] (정의에 의해) 동일합니다 *((E1)+(E2))

물론, 명백한 경고가 있습니다. 예제는 실제로 바운드 외 포인터를 보여주지 않습니다. 그것은 "끝의 끝"포인터를 사용합니다. 포인터는 (위의 말과 같이) 존재할 수 있지만, 내가 볼 수있는 한 표준은이를 불러 일으키는 것에 대해 아무 말도하지 않습니다. 내가 찾을 수있는 가장 가까운 것은 3.9.2 : 3입니다.

참고 : 예를 들어, 배열의 끝을 지나간 주소 (5.7)는 해당 주소에 위치 할 수있는 배열 요소 유형의 관련없는 객체를 가리키는 것으로 간주됩니다. - 엔드 참고

그렇습니다. 그렇습니다. 법적으로 부정 할 수 있지만 위치를 읽거나 쓰는 결과는 지정되지 않습니다.

마지막 비트를 수정 해 준 Ilproxyil에게 감사의 말을 전하며 질문의 마지막 부분에 답하십시오.

  • array + 5 실제로는 아무런 회의가없는 것이 아니며 단순히 끝을 지나서 한 과거에 대한 포인터를 만듭니다. array.
  • &array[4] + 1 불균형array+4 (완벽하게 안전합니다), 해당 LValue의 주소를 가져 와서 해당 주소에 하나를 추가하여 한 번의 목록 포인터를 초래합니다 (그러나 그 포인터는 절대 설득하지 않습니다.
  • &array[5] Dereferences array+5 (내가 볼 수있는 한 합법적이며 위의 말과 같이 "배열의 요소 유형의 관련없는 객체"를 초래 한 다음 해당 요소의 주소를 취하며 합법적으로 보입니다.

따라서 똑같은 일을하지 않지만이 경우 최종 결과는 동일합니다.

다른 팁

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

§6.5.2.1, 단락 2 :

사후 괄호 안의 표현식 후 표현식 [] 배열 객체의 요소의 구역 지정입니다. 첨자 연산자의 정의 []그게 다 E1[E2] 동일합니다 (*((E1)+(E2))). 이진에 적용되는 전환 규칙으로 인해 + 운영자, if E1 배열 객체 (동일, 배열 객체의 초기 요소에 대한 포인터)입니다. E2 정수, E1[E2] 지정합니다 E2-TH 요소 E1 (0에서 계산).

§6.5.3.2, 단락 3 (강조 광산) :

단지 & 연산자는 피연산자의 주소를 산출합니다. 피연산자에 유형이있는 경우유형` ', 결과에는 타입' '포인터가 있습니다. 유형''. 피연산자가 외교의 결과 인 경우 * 연산자, 해당 연산자 나 & 연산자가 평가되고 결과는 연산자의 제약 조건이 여전히 적용되고 결과는 LValue가 아니라는 점을 제외하고 두 가지 모두 생략 된 것처럼 발생합니다. 비슷하게, 피연산자가 a의 결과 인 경우 [] 연산자, & 운영자 나 단독이 아닙니다 * 그것은 [] 평가되고 결과는 마치 마치 & 연산자가 제거되었습니다 [] 연산자가 a로 변경되었습니다 + 운영자. 그렇지 않으면 결과는 피연산자로 지정된 물체 또는 기능에 대한 포인터입니다.

§6.5.6, 8 항 :

정수 유형을 갖는식이 포인터에 추가되거나 빼면 결과는 포인터 피연산자 유형을 갖습니다. 포인터 오페라가 배열 객체의 요소를 가리키고 배열이 충분히 큰 경우 결과는 원래 요소에서 요소 오프셋을 가리켜 결과 및 원래 배열 요소의 첨자 차이가 정수 표현식과 동일합니다. 다시 말해, 표현이라면 P 지적 i배열 객체, 표현식의 제한 요소 (P)+N (동등하게, N+(P)) 그리고 (P)-N (어디 N 가치가 있습니다 n) 각각을 가리킨다 i+n-TH와 i−n배열 객체의 제 1 요소가 존재하는 경우. 또한, 표현이라면 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-to-RValue 변환은 정의되지 않은 동작을 제공합니다

이것은 약간 다른 예이지만, 그것이 보여주는 것은 '*'가 lvalue 변환에 대한 lvalue를 초래하지 않는다는 것입니다. 표현이 '&'의 즉각적인 피연산자라는 점을 감안할 때, 이는 lvalue를 기대하는 즉각적인 오페라라는 점을 감안할 때 동작이 정의됩니다.

나는 그것이 불법이라고 믿지 않지만, & 배열 [5]의 행동이 정의되지 않았다고 믿는다.

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

  • 5.3.1 [expr.unary.op] unery * 연산자 ... 결과는 표현식이 가리키는 객체 또는 함수를 나타내는 lvalue입니다.

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

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

다른 곳에서 언급했듯이 array + 5 그리고 &array[0] + 5 배열 끝을 넘어서 포인터를 얻는 유효하고 잘 정의 된 방법입니다.

위의 답변 외에도 운영자를 지적하고 클래스에 대해 무시할 수 있습니다. 따라서 포드에 유효하더라도 아마도 유효하지 않은 객체에 대해서는 좋은 생각이 아닐 것입니다 (우선 연산자를 재정의하는 것과 매우 흡사).

이것은 합법적입니다 :

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 공극 제외)에 대한 포인터를 해석 할 수 있습니다. 이렇게 얻은 LValue는 제한된 방식으로 사용될 수 있습니다 (예 : 참조를 초기화하기 위해). 이 lvalue는 rvalue로 변환되어서는 안됩니다. 4.1 참조. - 끝 참고

위의 중요한 부분 :

'결과는 객체 또는 함수를 언급하는 lValue입니다.'

단술 연산자 '*'는 int (de-refeenceation 없음)를 언급하는 lvalue를 반환합니다. 단술 연산자 '&'는 lvalue의 주소를 가져옵니다.

외부 경계 포인터의 중심이없는 한 작업은 표준에 의해 완전히 다루고 모든 동작이 정의됩니다. 그래서 내 읽음으로써 위의 것은 완전히 합법적입니다.

많은 STL 알고리즘이 잘 정의 된 행동에 의존한다는 사실은 표준위원회가 이미 이것에 대해 가지고 있다는 힌트이며, 나는 이것을 명시 적으로 다루는 것이 있다고 확신합니다.

아래의 의견 섹션은 두 가지 주장을 제시합니다.

(읽으십시오 :하지만 길고 우리 둘 다 결국 트롤리쉬를 끝내십시오)

인수 1

이것은 5.7 항 5 항로 인해 불법입니다

적분 유형을 갖는식이 포인터에 추가되거나 빼면 결과는 포인터 피연산자의 유형을 갖습니다. 포인터 오페라가 배열 객체의 요소를 가리키고 배열이 충분히 큰 경우 결과는 원래 요소에서 요소 오프셋을 가리켜 결과 및 원래 배열 요소의 첨자 차이가 적분 표현식과 동일합니다. 다시 말해, 표현식 P가 어레이 객체의 I-th 요소를 가리키면 표현식 (p)+n (동등하게, n+(p)) 및 (p) -n (여기서 n이 값 n을 갖는) 포인트 배열 객체의 i + n-th 및 i-n-th 요소가 각각 존재하는 경우. 또한, 표현식 P가 배열 객체의 마지막 요소를 가리키면, 표현식 (p) +1은 배열 객체의 마지막 요소를 지나서 한 점을 지적하고, 표현식 q가 배열 객체의 마지막 요소를 지나치는 경우. 표현식 (Q) -1은 배열 객체의 마지막 요소를 가리 킵니다. 포인터 피연산자와 결과 지점이 동일한 배열 객체의 요소 또는 배열 객체의 마지막 요소를 지나면 평가는 오버플로를 생성하지 않아야합니다. 그렇지 않으면 동작이 정의되지 않습니다.

그리고 섹션은 관련이 있지만; 정의되지 않은 행동을 나타내지 않습니다. 우리가 말하는 배열의 모든 요소는 배열 내 또는 끝 부분 (위 단락에 의해 잘 정의 됨) 내에 있습니다.

인수 2 :

아래에 제시된 두 번째 주장은 다음과 같습니다. * 드론 연산자입니다.
그리고 이것은 '*'연산자를 설명하는 데 사용되는 일반적인 용어이지만; 이 용어는 'De-Reference'라는 용어가 언어 측면에서 잘 정의되지 않으므로 기본 하드웨어에 대한 의미가 없으므로 표준에서도 의도적으로 피합니다.

메모리에 액세스하는 것은 배열의 끝을 넘어서는 반드시 정의되지 않은 동작입니다. 나는 확신하지 못한다 unary * operator 메모리에 액세스합니다 (읽기/쓰기 메모리) 이 맥락에서 (표준이 정의되는 방식이 아닙니다). 이러한 맥락에서 (표준에 의해 정의 된대로 (5.3.1.1 참조) unary * operator 반환 a lvalue referring to the object. 언어에 대한 나의 이해에서 이것은 기본 메모리에 대한 접근이 아닙니다. 이 표현의 결과는 즉시 unary & operator operator that returns the address of the object referred to by the lvalue referring to the object.

Wikipedia 및 비 정식 출처에 대한 많은 다른 언급이 제시됩니다. 나는 모두 관련이 없습니다. C ++는 표준에 의해 정의됩니다.

결론:

나는 내가 고려하지 않은 표준의 많은 부분이 있고 위의 논쟁이 잘못을 증명할 수 있음을 인정하고 있습니다. 아래에 제공됩니다. 이것이 UB임을 보여주는 표준 참조를 보여 주면. 그럴게요

  1. 답을 남겨주세요.
  2. 모든 모자에 넣어서 이것은 어리석은 일이며 모든 사람이 읽는 것이 잘못되었습니다.

이것은 논쟁이 아닙니다.

전 세계의 모든 것이 C ++ 표준에 의해 정의되는 것은 아닙니다. 당신의 마음을 열어주세요.

작업 초안 (N2798):

"외교 및 운영자의 결과는 오페라에 대한 포인터입니다. 피연산자는 LValue 또는 자격을 갖춘 ID이어야합니다. 첫 번째 경우, 표현식 유형이"t 인 경우 "결과의 유형은"결과 유형 "입니다. T.에 대한 포인터 ""(p. 103)

배열 [5]는 내가 말할 수있는 최선을 다해 자격을 갖춘 ID가 아닙니다 (목록은 87 페이지에 있습니다). 가장 가까운 것은 식별자 인 것처럼 보이지만 배열은 식별자 배열 [5]입니다. "lvalue는 물체 나 기능을 의미하기 때문에"(p. 76). 배열 [5]는 분명히 함수가 아니며 유효한 개체를 참조하는 것이 보장되지 않습니다 (배열 + 5는 마지막 할당 된 배열 요소 이후).

분명히 특정 경우에 작동 할 수 있지만 유효한 C ++ 또는 안전하지는 않습니다.

참고 : 배열을 지나서 추가하는 것이 합법적입니다 (p. 113).

"표현 P [포인터]가 배열 객체의 마지막 요소를 가리키면, 표현식 (p) +1은 배열 객체의 마지막 요소를 지나서 Q가 마지막 요소를 지나치는 경우. 배열 객체, 표현식 (q) -1은 배열 객체의 마지막 요소를 가리 킵니다. 포인터 오페라와 결과 지점이 동일한 배열 객체의 요소 또는 배열 객체의 마지막 요소를 지나면 평가 과도한 흐름을 생산해서는 안됩니다. "

그러나 &를 사용하는 것은 합법적이지 않습니다.

합법적이더라도 왜 협약에서 출발합니까? 어쨌든 배열 + 5는 짧고 내 의견으로는 더 읽기 쉽습니다.

편집 : 대칭으로 원한다면 쓸 수 있습니다.

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

다음과 같은 이유로 정의되지 않은 동작이어야합니다.

  1. 방향으로 외부 요소에 액세스하려고하면 정의되지 않은 동작이 발생합니다. 따라서 표준은이 경우 예외를 던지는 구현을 금지하지 않습니다 (즉, 요소에 액세스하기 전에 구현 점검 경계). 만약에 & (array[size]) 정의되었습니다 begin (array) + size, 외부 액세스의 경우 예외를 던지는 구현은 더 이상 표준을 준수하지 않습니다.

  2. 이 수확량을 만드는 것은 불가능합니다 end (array) 배열이 배열이 아니라 임의의 수집 유형 인 경우.

C ++ 표준, 5.19, 단락 4 :

주소 상수 표현식은 lvalue에 대한 포인터입니다 .... 포인터는 단수 및 연산자를 사용하여 명시 적으로 만들어 져야합니다. 구독 연산자 [] ... 주소 상수 표현식을 작성하는 데 사용될 수 있지만 이러한 연산자를 사용하여 객체의 값에 액세스해서는 안됩니다. 구독 연산자가 사용되는 경우, 오페라 중 하나는 필수 상수 표현이어야합니다.

나에게 Like & Array [5]는 합법적 인 C ++이며 주소 상수 표현식입니다.

예제가 일반적인 경우가 아니라 특정 사례 인 경우 허용됩니다. 당신은 할 수 있습니다 합법적으로, afaik, 할당 된 메모리 블록을 지나서 한 번 이동하십시오. 배열 끝까지 1만큼 더 멀리 요소에 액세스하려고하는 일반적인 경우에는 작동하지 않습니다.

방금 검색 한 C-FAQ : 링크 텍스트

완벽하게 합법적입니다.

STL의 벡터 템플릿 클래스는 MyVec.end ()를 호출 할 때 정확히이를 수행합니다. 배열 끝을 지나서 한 요소를 가리키는 포인터 (반복자로)가 제공됩니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top