sourcetip

배열과 포인터를 포함하는 이 코드가 왜 그렇게 작동합니까?

fileupload 2023. 10. 20. 14:07
반응형

배열과 포인터를 포함하는 이 코드가 왜 그렇게 작동합니까?

다음 코드의 출력이 어떻게 되는지 질문 받았습니다.

int a[5] = { 1, 3, 5, 7, 9 };
int *p = (int *)(&a + 1);
printf("%d, %d", *(a + 1), *(p - 1));
  1. 3, 9
  2. 오류
  3. 3, 1
  4. 2, 1

정답은 NO.1 입니다.


구하기 쉽습니다.*(a+1)3 입니다.

근데 어때요?int *p = (int *)(&a + 1);그리고.*(p - 1)?

이에 대한 답은 C 표준을 읽는 방식에 따라 "1) 3,9" 또는 "2) 오류"(또는 더 구체적으로 정의되지 않은 동작)가 될 수 있습니다.

우선, 이것을 예로 들어보겠습니다.

&a + 1

&연산자가 배열의 주소를 가져옵니다.a우리에게 활자의 표현을 주는.int(*)[5]예를 들어 배열에 대한 포인터int5사이즈의여기에 1을 더하면 포인터는 배열의 첫번째 요소를 가리키는 것으로 취급됩니다.int [5], 결과 포인터가 바로 뒤를 가리키며a.

비록 이 ,&a단일 객체(이 경우 유형 배열)를 가리킵니다.int [5] 이 할 수 우리는 여전히 이 주소에 1을 추가할 수 있습니다.이는 1) 크기가 1인 배열의 첫 번째 요소에 대한 포인터로 처리될 수 있고, 2) 배열의 끝을 지나는 하나의 요소를 가리키는 포인터로 처리될 수 있기 때문에 유효합니다.

C 표준의 섹션 6.5.6p7은 오브젝트에 대한 포인터를 크기 1의 배열의 첫 번째 요소에 대한 포인터로 취급하는 것과 관련하여 다음과 같이 기술합니다.

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

그리고 섹션 6.5.6p8은 포인터가 배열의 바로 끝을 가리킬 수 있도록 하는 것에 대해 다음과 같이 말합니다.

포인터에 정수 유형을 포함하는 식을 추가하거나 포인터에서 뺄 때 결과는 포인터 피연산자의 유형을 갖습니다.포인터 피연산자가 배열 개체의 요소를 가리키고 배열이 충분히 큰 경우 결과 배열 요소와 원래 배열 요소의 첨자 차이가 정수 식과 같도록 원래 요소에서 오프셋된 요소를 가리킵니다.즉, 식 P가 배열 객체의 i번째 요소를 가리킨다면, 식들은(P)+N(equival적으로)N+(P)및 ) (P)-N(어디에N는 배열 객체의 i+n번째i-n번째 요소가 존재할 경우, 각각 n)을 가리킵니다.또한 표현식이 배열 개체의 마지막 요소를 가리키면 표현식은 배열 개체의 마지막 요소를 하나 가리키고, 표현식이 배열 개체의 마지막 요소를 하나 가리키면 표현식은 배열 개체의 마지막 요소를 가리킵니다.포인터 피연산자와 결과가 모두 동일한 배열 개체의 요소를 가리키거나 배열 개체의 마지막 요소를 가리키면 평가에서 오버플로가 발생하지 않습니다. 그렇지 않으면 동작이 정의되지 않습니다.결과가 배열 개체의 마지막 요소보다 1 앞을 가리킬 경우 단 하나의 피연산자로 사용할 수 없습니다.*평가되는 연산자.

이제 의문의 부분이 나옵니다. 바로 출연진들입니다.

(int *)(&a + 1)

다음 유형의 포인터를 변환합니다.int(*)[5]타이핑하다int *. 여기서의 목적은 1-요소 배열의 끝을 가리키는 포인터를 변경하는 것입니다.int [5]5개의 element 배열의 끝까지int.

그러나 C 표준은 이 변환 및 결과에 대한 후속 작업이 허용되는지 여부에 대해 명확하지 않습니다.포인터가 올바르게 정렬되어 있다고 가정할 때 한 개체 유형에서 다른 개체 유형으로 변환하거나 뒤로 변환할 수 있습니다.정렬은 문제가 되지 않지만 이 포인터를 사용하는 것이 좋습니다.

그래서 이 포인터는 다음에 할당됩니다.p:

int *p = (int *)(&a + 1)

그러면 다음과 같이 사용됩니다.

*(p - 1)

만약 우리가 그렇게 가정한다면,p배열의 끝을 지나는 하나의 요소를 유효하게 가리킵니다.a, 그것에서 1을 빼면 배열의 마지막 요소를 가리키는 포인터가 됩니다. 그*연산자는 이 포인터를 마지막 요소로 다시 참조하여 값 9를 산출합니다.

그래서 만약 우리가 가정한다면(int *)(&a + 1)결과적으로 유효한 포인터가 되고, 그 다음에 정답은 1) 3,9, 그렇지 않으면 정답은 2) Error.

줄을 서서.

int *p = (int *)(&a + 1);

라는 점에 주목합니다.&a작성중이지, 그렇지 않습니다.a 이것은 중요합니다.

간단히 말하면a만약 쓰여졌다면, 배열은 첫번째 원소에 대한 포인터로 붕괴되었을 것입니다, 즉&a[0]. 하지만 그 표현 이후로&a대신 사용되었으며, 이 표현의 결과는 다음과 같은 값을 갖습니다.a아니면&a[0]사용되었지만 유형이 다릅니다.유형은 5개의 배열을 가리키는 포인터입니다.int단일 포인터 대신 요소int요소.

포인터 산술에 대한 규칙에 따르면 포인터를 다음과 같이 증가시킵니다.1는 가리키는 개체의 크기만큼 메모리 주소를 증가시킵니다.포인터가 단일 요소를 가리키는 것이 아니라 5개 요소의 배열을 가리키기 때문에, 메모리 주소는 다음과 같이 증가합니다.5 * sizeof(int). 따라서 포인터를 증가시킨 후 포인터의 값은 다음과 같습니다.&a[5], 즉, 배열의 끝에서 한 번 지나갑니다.

이 포인터를 에 캐스팅한 후int *그리고 결과를 에 할당합니다.p, 어투, 어투, 어법p와 완전히 동치입니다.&a[5](값 및 유형 모두).

그래서 그 표현은*(p - 1)와 동치입니다.*(&a[5] - 1), 에 해당하는.*(&a[4]), 아니면 간단히a[4].

다음 내용:

&a + 1;

주소를 쓰고 있습니다.a, 배열, 그리고 하나의 크기를 추가하는 1을 추가합니다.a, 즉 5개의 정수그런 다음 색인 "백다운", 하나의 정수, 마지막 요소로 끝납니다.a.

보통 식에서 배열이 사용될 때마다 첫 번째 요소의 포인터로 "분해"됩니다.이 규칙에는 몇 가지 예외가 있고 그러한 예외 중 하나는&교환입니다.

&a따라서 유형 배열에 대한 포인터를 가져옵니다.int (*)[5].그리고나서&a + 1는 그러한 유형의 포인터 산술이며, 이는 포인터 주소가 1의 크기만큼 증가함을 의미합니다.int [5] 너머를 그한 할 수 해줍니다 우리는 배열 바로 너머를 가리키게 되지만, C는 우리가 그 위치를 역참조하지 않는 한 실제로 그렇게 할 수 있게 해줍니다.

그런 다음 포인터가 강제로 다음으로 유형 변환됩니다.(int *)우리도 할 수 있습니다. C는 참조를 취소하거나 오정렬 등을 일으키지 않는 한 거의 모든 방식의 와일드 포인터 변환을 허용합니다.

p - 1유형에 대한 포인터 산술을 수행합니다.int그리고 배열에 있는 데이터의 실제 유형 또한int, 그래서 그 장소에 대한 언급을 취소할 수 있습니다.우리는 배열의 마지막 항목에 도달합니다.

언급URL : https://stackoverflow.com/questions/69481018/why-is-this-code-involving-arrays-and-pointers-behaving-as-it-does

반응형