인프런 커뮤니티 질문&답변

Echapper4님의 프로필 이미지
Echapper4

작성한 질문수

홍정모의 따라하며 배우는 C언어

10.13 포인터의 배열과 2차원 배열

배열의 이중포인터

작성

·

290

·

수정됨

2

안녕하세요

학습중 개념에 혼동이 생겨 질문글을 남깁니다.

강의는 10.13, 4분 18초 쯤입니다.

 

예컨대*(*(parr + 1) + 2)가 있다 하면 이것이 어떤 값을 나타내는지 그 과정 중에서 혼란스러운 부분이 몇 개 있습니다. 질문과 더불어 제가 잘못 설명하는 부분이 있으면 지적해주시면 감사하겠습니다.

 

parr + 1는 포인터parr 배열의 2번 째 원소(arr[1])의 첫 주소, 즉 4의 주소를 가리키는 포인터입니다.

 

한편, *라는 기호는 포인터변수에 저장된 주소에 접근하여 그쪽에 저장된 데이터를 들고오는 역참조의 기능을 수행케 합니다.

 

그렇다면 *이 붙은 *(parr + 1)에서는 4의 주소로 접근하여 *에 의해 4라는 값을 역참조해오게 됩니다.

 

하지만 그러면 *(4 + 2)가 되는데 이는 전혀 말이 안 되고 본래의 +2는 포인터의 산술연산을 위한 것이기에 *(parr + 1)는 모종의 포인터가 돼야 하고 강의 중에서도 교수님이 그렇게 말씀하셨습니다.

(제가 오해하고 있는 부분과 달리 가장 밖에 있는 *는 실제로 역참조의 기능을 가지는 것이 자명한데도요...)

 

그래서 정리하자면 *(parr + 1)는 어떤 주소를 나타내것인지 아니면 모종의 포인터를 의미하는 건지 궁금하며

 

그리고*(*(parr + 1) + 2)의 전체적인 플로우를 정확하게 이해하고 싶습니다.

 

감사합니다.

 

답변 1

3

안녕하세요, 답변 도우미 Soobak 입니다.

 

*(parr + 1) 을 "4 의 주소로 접근하여 * 에 의해 4 라는 값을 역참조해오게 됩니다." 라는 부분이 잘못되었습니다.

*(parr + 1)parr[1] 을 역참조하는 것으로, parr[1] 또한 arr[1] 배열의 첫 번째 원소를 가리키는 포인터입니다.

parr이중포인터라는 것을 이해하시면 도움이 되실 것 같습니다.

 

Echapper4님의 프로필 이미지
Echapper4
질문자

감사합니다

 

그렇다면 말씀하신 부분인

*(parr + 1)parr[1] 을 역참조하는 것으로, ...

에서

(아래의 사진을 참고해주시면 감사하겠습니다.)

*(parr + 1)parr[1]를 역참조하기 위해서는 주소인parr + 1parr[1]의 주소여야 하는데

 

지적해주신 것 처럼 4의 주소이지는 않으니 parr + 1의 주소, 즉 parr[1]이 있는 곳의 주소는 12가 아닌 어디 다른 별도의 주소이다 라는 말씀인가요?

안녕하세요, 답변 도우미 Soobak 입니다.

 

제가 이전 답변에서 말씀을 조금 혼동을 드리게 드렸네요.

다시 명확하게 설명드리면 다음과 같습니다.

parr + 1parr 배열의 두 번째 원소, 즉, parr[1] 을 가리키는 포인터의 주소입니다.

여기서, parr[1]arr[1] 배열, 즉, {4, 5, 6} 배열의 첫 번째 원소인 4 의 주소를 가리키고 있습니다.

따라서, *(parr + 1)parr[1] 과 같고, 4의 주소를 가리키는 포인터입니다.

그렇기에, *(*(parr + 1) + 2) 와 같은 포인터 연산이 가능한 것입니다.

마지막에 말씀하신 parr[1] 자체의 주소와 관련된 말씀은 맞습니다.

Echapper4님의 프로필 이미지
Echapper4
질문자

답변 감사합니다.

중간에 말씀하신 parr[1]이 포인터 변수명 인가요? 일단 변수명이라 생각하고 제 설명 드려보겠습니다.

 

parr + 1parr 배열의 두 번째 원소, 즉, parr[1] 을 가리키는 포인터의 주소입니다.

말씀하신 위의 부분을 아래의 그림으로 표현해봤습니다.

(주소값은 예시)

Gâteau De Nous (1).jpeg.png

또한 parr[1]arr[1]배열 즉슨, 4의 주소를 가리키므로

Gâteau De Nous (2).jpeg.png

이렇게 될 것 같습니다.

 

하지만 *는 간접 참조 연산자로서 주소로 접근하여 그 주소의 데이터(내용물)를 읽어온다는 걸로 알고있는데 그렇다면

*(parr + 1)parr + 1이라는 주소로 접근하여 그 옆에 있는 저장된 주소값인 104를 가져오는데, 이는 어떤 포인터이거나 말씀하신 포인터변수parr[1]가 되는 것 같아 보이지는 않습니다.

제가 *의 역할에 대해 오해하고 있는 게 있을까요?

어디서부터 잘못 되었는지 정말 모르겠습니다...

안녕하세요, 답변 도우미 Soobak 입니다.

 

parr[1]int* parr[2] = {arr[0], arr[1]}; 으로 선언된 배열의 두 번째 원소를 나타냅니다.

 

그려주신 그림과 말씀해주신 설명 모두 정확하게 잘 이해하고 계신 것 같습니다.

"하지만 *는 간접 참조 연산자로서 주소로 접근하여 그 주소의 데이터(내용물)를 읽어온다는 걸로 알고있는데 그렇다면

*(parr + 1)parr + 1이라는 주소로 접근하여 그 옆에 있는 저장된 주소값인 104를 가져오는데, 이는 어떤 포인터이거나 말씀하신 포인터변수parr[1]가 되는 것 같아 보이지는 않습니다."

이 부분에 대해서는, *(parr + 1) 은 'parr + 1 이라는 주소로 접근하여, 저장된 주소값(이 또한 데이터입니다.) 104 를 역참조하는 것'으로 parr[1] 과 같은 동작을 수행합니다.

배열의 인덱싱 접근 [] 은, 일반적으로 포인터 연산으로 구현됩니다.
즉, parr[1] 로 배열의 원소에 접근하는 것은 *(parr + 1) 과 같은 방식으로 동작합니다.
'배열의 시작주소(배열의 이름)' 와 '포인터 연산을 통한 역참조', '주소와 데이터' 를 생각해보시면 이해가 수월하실 것 같습니다.

위 내용이 parr + 1parr[1] 의 주소를 가리키는 포인터라는 것을 이해하시는 데에 도움이 되셨으면 좋겠습니다.

정리하자면,

  • parr 은 포인터의 배열이며, int* 자료형의 포인터들을 저장합니다.

  • parr + 1parr 배열의 두 번째 원소(parr[1])의 주소를 가리킵니다. 즉, parr + 1parr[1] 의 주소를 가리키는 포인터입니다.

  • *(parr + 1)parr + 1 이 가리키는 주소에 있는 값, 즉, parr[1] 을 가져옵니다.

  • parr[1]arr[1] 배열의 첫 번째 원소의 주소를 가리키는 포인터입니다.

따라서, *(parr + 1)parr[1] 은 동일한 값을 나타내며, 이는 arr[1] 배열의 시작 주소입니다.

-추가 보충 설명-

오해하고 계신 부분을 점검하는 데에 도움을 드리기 위해, 각 변수들과 관계를 최대한 정리하여 설명드립니다.

 

  • arr 배열

    • arr2차원 정수 배열 입니다.

    • int arr[2][3] = { {1,2,3}, {4,5,6} }; 과 같이 선언되었으며, 두 개의 행과 각 행에 세 개의 열(원소)을 가진 배열입니다.

    • arr[0]{1, 2, 3} 배열을 가리킴과 동시에 해당 배열의 시작 주소를 가리키는 포인터와 호환이 되는 형태입니다. arr[1] 또한 {4, 5, 6} 배열을 가리킴과 동시에 해당 배열의 시작 주소를 가리키는 포인터와 호환이 되는 형태입니다.

    • 따라서 arr[0]int* 자료형의 포인터로 사용할 수 있습니다.
      예) int* ptr = arr[0]; 과 같이 선언하면, ptr{1, 2, 3} 배열의 첫 번째 원소인 1 의 주소를 가리키게 됩니다.

 

  • parr 배열

    • parr 은 포인터의 배열입니다.

    • int* parr[2] = { arr[0], arr[1] }; 과 같이 선언되었으며, parr 배열의 각 원소는 정수를 가리키는 포인터 (int* 자료형) 입니다.

    • parr[0]arr[0] 이라는 주소를 가리키며, arr[0]{1, 2, 3} 배열의 첫 번째 원소의 주소입니다.

    • parr[1]arr[1] 이라는 주소를 가리키며, arr[1]{4, 5, 6} 배열의 첫 번째 원소의 주소입니다.

    • parr + 1parr 배열의 두 번째 원소인 parr[1] 의 주소를 나타내고, *(parr + 1)parr[1] 의 값을 가져옵니다. 즉, *(parr +1)parr[1] 이며, arr[1] 배열의 첫 번째 원소의 주소입니다.

 

  • 요약

    • arr2차원 배열 입니다.

    • parr포인터의 배열 로, 각 포인터는 arr 의 각 행을 가리키는 포인터입니다.

       

혹시 이해가 안되시는 부분이 있으시면 편하게 댓글 남겨주세요.

Echapper4님의 프로필 이미지
Echapper4
질문자

정말 정말 정성스러운 답변 다시 한 번 감사합니다.

 

요약하자면,

parr포인터 자체가 원래 parr[0]의 주소를 담고있었는데

포인터 산술연산 +1 을 통해 주소를 int 자료형 크기만큼 건너뛰어가서

포인터 parr + 1parr[1]의 주소가 저장되도록 즉,

포인터 parr + 1parr[1]의 주소를 가리키게 했고

*parr + 1에 붙여서 ,*(parr + 1)가 "arr[1](4의 주소)라는 주소가 포인터에 저장된 즉, 이 주소를 가리키는 포인터변수 parr[1]" 가 되도록 했다.

 

따라서 남아있는 것은 *(parr[1] + 2)이 됐고

+2라는 포인터 산술연산을 통해

포인터변수 parr[1]안에 저장된 주소를 조정하여 목적지인

4의 주소로 도달했고 *를 통해 4를 읽어냈다.

라고하면 정확한 이해일까요?

안녕하세요, 답변 도우미 Soobak 입니다.

 

거의 맞지만, 몇 가지 중요한 부분에서 혼동이 있으신 것 같습니다.

 

보다 자세하게 요약해보면 다음과 같습니다.

  • parr 배열과 포인터 연산


    : parr 은 포인터의 배열입니다. 이 배열에는 arr[0]arr[1] 이라는 두 개의 주소가 저장되어 있습니다. 여기서 parr[0]arr[0] 을, parr[1]arr[1] 을 가리킵니다.
    arr[0]arr[1] 은 각각 {1, 2, 3}{4, 5, 6} 배열의 시작 주소입니다.

  • 포인터 산술 연산과 *(parr + 1)
    : parr + 1parr 배열의 두 번째 원소, 즉, parr[1] 의 주소를 가리킵니다.
    그리고 *(parr + 1) 은 이 주소에 저장된 값을 역참조하여 가져옵니다. 이 값은 parr[1] 과 동일하며, arr[1] 배열의 시작 주소를 가리키고 있으므로, parr[1] + 2arr[1] 배열의 세 번째 원소의 주소를 가리킵니다. 이 주소는 {4, 5, 6} 배열에서 6 의 위치입니다.
    따라서, *(parr[1] + 2)6 을 역참조하여 가져옵니다.

Echapper4님의 프로필 이미지
Echapper4
질문자

전에는 바로위의 수박님께서 말씀해주신 내용을 따라가다보면 자꾸 갓길로 빠져서 계속 도돌이표를 돌았는데 지금 보니 내용 그대로 납득이 잘 되는 것 같네요. 감사합니다 (_ _)

Echapper4님의 프로필 이미지
Echapper4

작성한 질문수

질문하기