programing

'*(*(&array + 1)) - 1)'은 자동 배열의 마지막 요소를 얻기 위해 사용해도 안전합니까?

css3 2023. 10. 12. 23:23

'*(*(&array + 1)) - 1)'은 자동 배열의 마지막 요소를 얻기 위해 사용해도 안전합니까?

크기를 알 수 없는 자동 배열의 마지막 요소를 구하려고 합니다.나는 내가 사용할 수 있다는 것을 알고 있습니다.sizeof연산자는 배열의 크기를 얻고 그에 따라 마지막 요소를 얻습니다.

.*((*(&array + 1)) - 1)안전합니까?

좋아요:

char array[SOME_SIZE] = { ... };
printf("Last element = %c", *((*(&array + 1)) - 1));
int array[SOME_SIZE] = { ... };
printf("Last element = %d", *((*(&array + 1)) - 1));

기타

아니, 그건 아니야.

&array입니다에 입니다.char[SOME_SIZE](제공된 첫 번째 예에서).s을 의미합니다.&array + 1합니다가 끝난 .array)에서와하는 것.(*(&array+1))정의되지 않은 동작을 제공합니다.

더 이상 분석할 필요가 없습니다.식에 정의되지 않은 동작을 주는 부분이 있으면 전체 식이 정의됩니다.

안전하지 않다고 생각합니다.

@dasblinkenlight답변에서 인용한 표준(현재 삭제)에서 추가하고 싶은 것도 있습니다.

C99 섹션 6.5.6.8 -

[...]
식 P가식 (P)+1은 [...]]을 가리킵니다.
결과가 배열 객체의 마지막 요소보다 1 위를 가리킬 경우 평가되는 단항 * 연산자의 피연산자로 사용할 수 없습니다.

말처럼,이 를 는 안 됩니다.*(&array + 1)그것은 에를 입니다.*사용해서는 안 됩니다.

또한 승인되지 않은 메모리 위치를 가리키는 참조 해제 포인터가 정의되지 않은 동작을 초래한다는 것은 잘 알려진 사실입니다.

피터가 대답에서 언급한 이유 때문에 명확하지 않은 행동이라고 생각합니다.

에 대해 이 벌어지고 .*(&array + 1) 으로, 에서 열린 &array + 1다에서 인 것 . 왜냐하면 그것은 단지 유형을 바꿀 뿐이기 때문입니다.T (*)[]다시T [] 한편으로는 여전히 되지 않은,, 할당되지 예: , , )에

제 대답은 다음과 같습니다.

C99 6.5.6.7 (가법 연산자의 의미)

이러한 연산자의 목적을 위해 배열의 요소가 아닌 개체에 대한 포인터는 개체 유형을 요소 유형으로 하는 길이 1의 배열의 첫 번째 요소에 대한 포인터와 동일하게 동작합니다.

부터.&array 포인터가 는 다음과 다. 따라서 이에 따르면 코드는 다음과 같습니다.

char array_equiv[1][SOME_SIZE] = { ... };
/* ... */
printf("Last element = %c", *((*(&array_equiv[0] + 1)) - 1));

은,&array는 10 문자의 배열에 대한 포인터이므로, 각 원소가 10 문자의 배열인 길이 1의 배열의 첫 번째 원소에 대한 포인터와 동일하게 동작합니다.

자, 다음에 나오는 조항과 함께 (이미 다른 답변에서 언급되었습니다; 이 정확한 발췌문은 노골적으로 으로부터 도난당했습니다.CU 답변):

C99 섹션 6.5.6.8 -

[...]
식 P가식 (P)+1은 [...]]을 가리킵니다.
결과가 배열 객체의 마지막 요소보다 1 위를 가리킬 경우 평가되는 단항 * 연산자의 피연산자로 사용할 수 없습니다.

합니다. UB 의 를 역참조하는. 마지막 요소를 지나 하나를 가리키는 포인터를 역참조하는 것과 같습니다.array_equiv.

네, 실제로는 원래 코드가 메모리 위치를 참조하지 않기 때문에 실제로는 대부분 메모리에서 유형 변환이 가능합니다.T (*)[].T []한 표준 에서 볼 때,되지 않은 즉 , 합니다이라고

안전할 수도 있지만 주의사항이 있습니다.

우리가 가지고 있다고 가정해 보겠습니다.

T array[LEN];

그리고나서&array입니다.T(*)[LEN].

다음 분.&array + 1입니다 입니다.T(*)[LEN] 있습니다..

다음 분.*(&array + 1)입니다.T[LEN] , 될 수 .T* 있습니다 .: .*연산자가 평가되지 않음).

다음 분.*(&array + 1) - 1입니다.T* 배열 있습니다..

마지막으로, 다음과 같이 언급합니다(배열 길이가 0이 아니면 합법적입니다).*(*(&array + 1) - 1)는 배열 값,합니다를 합니다.T.

이 마지막 단계에서 포인터를 실제로 참조하지 않는 것이 유일합니다.

이제, 잠재적인 경고입니다.

,*(&array + 1)공식적으로 잘못된 메모리 위치를 가리키는 포인터를 역참조하려는 시도처럼 보입니다.하지만 정말 그렇지 않습니다.이것이 배열 포인터의 특성입니다. 이렇게 형식적으로 참조를 해제하면 포인터의 유형이 변경될 뿐 참조된 위치에서 값을 검색하려는 시도가 실제로 발생하지는 않습니다.은,array입니다.T[LEN] 암묵적으로다 도 있습니다.&T첫 , .&array입니다를 입니다.T[LEN] , .*(&array+1)입니다 입니다.T[LEN]될 수 .&T어떤 되지 않습니다 어떤 지점에도 포인터가 실제로 참조되지 않습니다.

,&array + 1잘못된 주소일 수도 있지만 실제로는 그렇지 않습니다. 제 C++11 참조 매뉴얼은 "어레이 끝 너머의 요소 1로 포인터를 가져가면 작동이 보장됩니다"라고 명시적으로 알려주고, K&R에서도 비슷한 문구가 나와 있기 때문에 항상 표준 동작이었다고 생각합니다.

마지막으로, 영-길이 배열의 경우 식을 배열 직전의 메모리 위치를 역참조하며, 이는 할당되지 않거나 유효하지 않을 수 있습니다.입니다를 이할 수 있습니다.sizeof()0이 아닌 길이에 대해 먼저 테스트하지 않습니다.

간단히 말해서, 저는 이 표현의 행동에 정의되지 않거나 구현에 의존하는 것이 있다고 생각하지 않습니다.

나는 그것이 효과가 있을지도 모르지만 아마 현명하지 못할 것이라고 생각합니다.SW 설계를 꼼꼼히 검토하고 어레이의 마지막 엔트리를 원하는 이유를 스스로에게 물어보셔야 합니다.배열의 내용이 사용자에게 완전히 알려지지 않았거나 c 구조 및 조합 측면에서 구조를 정의할 수 있습니까?예를 들어, 문자 배열에서 복잡한 포인터 작업을 멀리하고 가능한 경우 c 코드, 명령어 및 조합에서 데이터를 적절하게 정의합니다.

그래서 대신:

 printf("Last element = %c", *((*(&array + 1)) - 1));

다음이 될 수 있습니다.

 printf("Checksum = %c", myStruct.MyUnion.Checksum);

코드가 명확해집니다.배열의 마지막 글자는 이 배열의 내용에 익숙하지 않은 사람에게 아무런 의미가 없습니다. myStruct.my유니온.체크섬은 누구에게나 의미가 있습니다.myStruct 구조를 연구하면 누구에게나 전체 데이터 구조를 설명할 수 있습니다.그런 식으로 신고가 가능하다면 그런 것을 사용해 주시기 바랍니다.만약 당신이 할 수 없는 드문 상황에 있다면, 위의 답을 공부하세요, 그것들은 말이 된다고 생각합니다.

a)

포인터 피연산자와 [P + N]의 결과가 모두 동일한 배열 객체의 요소를 가리킬 경우 또는 배열 객체의 마지막 요소를 지난 부분을 가리킬 경우, 평가에서 오버플로우가 발생하지 않아야 합니다.
[...]
식 P가 배열 객체의 원소를 가리키거나 배열 객체의 마지막 원소를 가리키고, 식 Q가 동일한 배열 객체의 마지막 원소를 가리키면, 식 ((Q)+1)-(P)는 (Q)-(P)+1과 같고, 식 P가 마지막 원소를 가리키면 0의 값을 갖습니다.식 (Q)+1이 배열 개체의 요소를 가리키지 않더라도 배열 개체의.

이것은 배열 요소를 사용하는 계산이 마지막 요소보다 한 번 지난 것은 사실 완전히 문제가 없다는 것을 의미합니다.존재하지 않는 물체를 계산에 사용하는 것은 이미 불법이라고 여기 계신 분들도 계시기 때문에 저는 그 부분을 포함한다고 생각했습니다.

그럼 이 부분을 신경써야겠네요.

결과가 배열 객체의 마지막 요소보다 1 위를 가리킬 경우 평가되는 단항 * 연산자의 피연산자로 사용할 수 없습니다.

다른 답변들이 생략된 중요한 부분이 하나 있는데 바로 다음과 같습니다.

포인터 피연산자가 배열 객체의 요소를 가리킬 경우

이것은 사실이 아닙니다.포인터 피연산자는 배열 개체의 요소에 대한 포인터가 아니라 포인터에 대한 포인터입니다.그래서 이 전체 조항은 전혀 무관합니다.그러나 다음과 같은 내용도 있습니다.

이러한 [additive] 연산자의 경우 배열의 요소가 아닌 개체에 대한 포인터가 개체 유형을 요소 유형으로 하는 길이 1의 배열의 첫 번째 요소에 대한 포인터와 동일하게 동작합니다.

이것은 무엇을 의미합니까?

포인터에 대한 포인터가 실제로는 길이 [1]의 배열에 대한 포인터임을 의미합니다.이제 루프를 닫을 수 있습니다. 첫 번째 단락에서 언급하듯이 배열을 지나쳐 계산을 수행할 수 있기 때문에 배열을 사용하여 길이[2]의 배열인 것처럼 계산을 수행할 수 있습니다.

보다 그래픽적인 방법:

ptr -> (ptr to int[10])[0] -> int[10]
    -> (ptr to int[10])[1]

따라서 기술적으로 길이 배열[1]을 벗어나더라도 (ptr to int[10])[1]로 계산할 수 있습니다.

b)

발생하는 단계는 다음과 같습니다.

array 입]합니다.SOME_SIZE]을(를) 첫 번째 요소 배열에 연결

&arrayint[t]의 ptrt] .SOME_SIZE] 배열의 첫 번째 요소에 연결

+ 1ptr, type int[의 ptr에서 첫 로의 , intSOME_SIZE], int apptr보다 하나 더 ptr

아직 int[]에 대한 포인터가 아닙니다.SOME_SIZE+1], C99 섹션 6.5.6.8에 의거.이건 아직 아닙니다.ptr + SOME_SIZE + 1

*포인터를 포인터에 대한 참조를 해제합니다.이제 참조 해제 후 C99 섹션 6.5.6.8에 따른 포인터가 있습니다. 이 포인터는 배열의 요소를 지나쳤으며 참조 해제가 허용되지 않습니다.이 포인터는 존재할 수 있으며 단항 * 연산자를 제외한 연산자를 사용할 수 있습니다.하지만 그 포인터에는 아직 그것을 사용하지 않습니다.

-1이제 우리는 배열의 마지막 요소 뒤에 있는 int형의 ptr에서 1을 빼서 배열의 마지막 요소를 ptr이 가리키게 합니다.

*배열의 마지막 요소에 대한 재참조는 합법적입니다.

c)

그리고 마지막이지만 역시 중요한 것은:

만약 그것이 불법이라면, 매크로의 오프셋 또한 불법이 될 것이고, 이것은 다음과 같이 정의됩니다.
((size_t)(&((st *)0)->m))

언급URL : https://stackoverflow.com/questions/32537471/is-array-1-1-safe-to-use-to-get-the-last-element-of-an-automatic