c언어 이중포인터 소스 설명해주세요 #include <stdio.h>
#include <stdio.h>
일단, 이걸 이해하려면 이중 포인터의 개념과 단일 포인터의 개념, 그리고 포인터 연산의 개념과 배열에 대한 이해가 필요합니다. 짤막하게 필요한 이론만 먼저 짧게 설명하고 소스 분석해 드리겠습니다.
단일 포인터 변수는 일반 변수의 메모리 주소를 대입할 수 있는 변수입니다. 포인터도 변수이기 때문에 변수가 할 수 있는건 똑같이 다 할 수가 있습니다.
단일 포인터 변수도 변수이기 때문에 당연히 일반 변수처럼 대입도 가능하고 값을 읽어오는 것도 아니고 값을 변경, 연산하는 것도 가능하구요. 포인터로 연산하는걸 말 그대로 포인터 연산이라고 부르는데 포인터 연산은 일반 변수끼리의 연산이랑 조금 다릅니다.
포인터 변수는 일반 변수랑 단지, 저장하는 값의 타입이 다를 뿐입니다. 다른 변수들은 정수나 부동소수점을 저장하는데 포인터는 메모리 주소를 저장하는 것일뿐이죠. 참고로 char 포인터에 문자열을 대입하는 경우도... 문자열이 그대로 포인터에 대입되는 게 아니라 해당 문자열이 위치한 메모리 주소를 가리키는 것입니다.
heep 영역의 동적 메모리 할당이 아니라 그냥 char 포인터에 바로 문자열을 대입한 경우에서의 이때 문자열은 읽기 전용 메모리에 생성되므로 읽기만 가능하고 쓰기가 안 됩니다. 쓰기를 시도하는 순간, 프로그램 종료됩니다. 포인터로 쓰기를 시도하는 걸 막을 수단이 없기 때문에 극단적인 경우로 강제 종료시키죠...
포인터가 이해되지 않는다는건 메모리 주소 체계를 이해하지 못해서 그런건데 메모리 주소를 이해하면 포인터는 전혀 어렵지 않습니다. 앞에서 설명한 것처럼 단일 포인터도 변수라고 했습니다. 당연히 다른 변수들처럼 포인터도 자신이 취하고 있는 변수의 메모리 주소뿐이 아니라 자기자신의 자체적인 메모리 주소도 당연히 존재합니다. 단일 포인터는 일반 변수의 포인터인데 그러면 단일 포인터의 포인터는 이중 포인터 변수에 담아야 합니다. 이중 포인터는 단일 포인터의 메모리 주소를 취하는 포인터입니다. 배열이란건 같은 자료형이 일렬로 나열되어 있는 자료구조를 의미하고 1차원 배열의 이름 자체는 첫 번째 요소의 메모리 주소를 가리키는 포인터입니다. 배열 이름 자체도 포인터이기 때문에 당연히 포인터에 할당해서 포인터를 배열처럼 인덱스로 접근하는게 가능하죠. 왜냐하면, 포인터 연산과 배열의 인덱스 연산은 완전히 동일하니까요. 2차원 배열을 포인터에 담을려면 다른 방법이 따로 있는데 이건 1차원 배열이라서 그냥 바로 포인터에 넣으면 됩니다.
포인터 연산은 자료형의 크기만큼 더하거나, 빼는 연산입니다. 만약에 int 포인터인데 1을 더하는 연산을 수행하면 int의 크기인 4바이트만큼 메모리 주소가 증가됩니다. 만약에 1을 빼주면 반대로 작동하겠죠.
이정도면 굉장히 자세하게 설명했으니까 이제 소스 한 줄씩 하나하나 자세하게 분석하겠습니다.
int vals[] = {5, 10, 15, 20}; 1차원 배열을 선언과 동시에 초기화 시킨것이고 배열의 크기를 생략했으므로 요소의 개수가 배열의 크기가 되죠. 배열 크기는 4입니다.
int *p = vals; 1차원 int 포인터 변수에 1차원 배열을 바로 할당, 앞에서 바로 넣을 수 있다고 했죠.
배열의 이름 자체가 배열의 첫 번째 요소의 포인터라고 했으므로 포인터 p 안에는 메모리 주소 상으로 5가 존재하는 위치를 가리키고 있는겁니다. int **pp = &p; int형 2차원 포인터 변수에 int형 1차원 포인터 변수의 메모리 주소를 대입했습니다. 참고로 이때 p 그냥 이렇게 넣으면 당연히 안 됩니다. 그냥 p 이렇게 넣으면 타입이 int**가 아니라 int*가 되죠. 그냥 p로만 넣으면 포인터 p의 메모리 주소가 아니라 포인터 p가 가지고 있는 메모리 주소가 되므로 int*가 되죠. 타입은 서로 일치해야 합니다. 그래서 주소 연산자를 붙혀줘야 하는거죠. int*에 주소 연산자를 붙혔으니까 타입은 int**니까 pp랑 타입이 일치하죠. int a, b, c; 이건 설명 생략합니다. 아시겠죠. a = **pp; a에 이중 포인터pp를 두 번 역참조한 값을 대입했죠. 이중 포인터를 한 번 역참조하면 이중 포인터에 대입이 되어진 메모리 주소에 접근하고, 두 번 역참조하면 이 메모리 주소 안에 들어있는 값에 접근을 합니다. 이중 포인터 안에 포인터 p의 메모리 주소가 있으니까 이중 포인터를 한 번 역참조를 해주면 포인터 p의 메모리 주소에 접근하게 되고 두 번 역참조를 하면 이 메모리 주소안에 들어있는 값에 접근하게 됩니다. 역참조를 아예 안하면 당연히 2차원 포인터의 자기자신, 자체적인 메모리 주소가 나옵니다. 앞에서 두 번 역참조하면 값에 접근한다고 그랬죠? 포인터 p가 가지고 있는 메모리 주소의 값에 접근한다고 했는데 포인터 p가 지금 배열의 첫 번째 요소의 메모리 주소를 가리키고 있잖아요. 그러니까 당연히 **pp는 5가 되니까 a에는 5가 들어갑니다. (*pp)++; 이게 앞에서 설명한 포인터 연산입니다. 포인터 연산은 타입의 크기만큼 연산을 한다고 했죠. 여기서는 int가 4바이트죠. (*pp)++; 괄호는 최상위 우선순위를 취합니다. 역참조가 먼저 되고 ++이 수행되는데요. pp를 한 번 역참조하면 앞에서 설명한 것처럼 p의 메모리 주소입니다. p의 메모리 주소를 1 증가시켜주는 코드입니다. p가 지금 배열의 첫 번째 요소의 포인터라고 했죠? p의 메모리 주소를 1 증가시켜줬고 int가 4바이트니까 p의 메모리 주소가 4바이트 크기만큼 늘어납니다. 배열은 같은 자료형 여러 개가 일렬로 나열된 형태라고 그랬습니다. int가 4바이트 크기니까 당연히 배열의 각 인덱스의 포인터는 4바이트만큼 차이가 납니다. p의 메모리 주소가 4바이트 늘어났으니까 이제 p가 가리키는건 배열의 첫 번째 요소가 아니라 두 번째 요소의 포인터입니다. int가 4바이트니까 배열의 각 인덱스의 메모리 주소 차이는 4바이트 크기만큼 차이납니다.
b = **pp; 이제 여기 살펴보시죠. **pp는 앞에서 설명한 것처럼 1차원 포인터 p가 가지고 있는 값에 접근을 합니다. 지금 1차원 포인터는 10을 가리키고 있으니까 b에는 10이 할당되죠.
(*pp)++; 이제 여기 보시죠. 앞에랑 똑같죠? 이제 포인터 p가 가리키는건 배열의 두 번째 요소가 아니라 세 번째 요소의 포인터입니다. 즉, 15를 가리킨다는 소리입니다.
c = *(p++); 다왔군요. 이제 여기 봅시다. 전위 증감과 후위 증감 연산의 차이는 구분하셔야 합니다.
전위는 수식에 해당 값이 표현 되기전에 먼저 증가를 시키는 것이고 후위는 수식에 먼저 해당 값이 표현이 된 다음에 증가를 시키는 차이가 있죠. c = *(p++); p는 지금 15를 가리키고 있죠.
c = *(p++); 증가보다 표현이 먼저 되니까 이해하기 쉽게 이렇게 되겠죠.
c = *p의 메모리 주소(p의 메모리 주소를 역참조)
c = *p의 메모리 주소(p의 메모리 주소를 역참조) 여기서는 아직 후위 증감 적용이 안 됐습니다. p의 메모리 주소가 먼저 수식에 표현이 됩니다.
증감이 안 된 상태에서 역참조를 했으니까 포인터는 여전히 세 번째 요소를 가리키고 이 값을 가져오므로
c = *(p++); 여기서 c에는 15가 대입됩니다.
이제 p++이 수행됩니다. 포인터가 기존 세 번째 요소를 가리키다가 이제는 네 번째 요소, 즉 마지막 인덱스를 가리킵니다. ++의 수행보다 c에 대입이 먼저 됐으니까 c에는 세 번째 요소의 값인 15가 들어갑니다. c에 대입이 되고나서 마침내 p++로 p의 메모리 주소가 4바이트 늘어납니다.
배열의 요소 값에다가 더하는게 아니고 메모리에다가 포인터 연산이 되는겁니다.
포인터가 가지고 있는 메모리 주소가 가리키고 있는, 위치한 값에다가 덧셈 연산을 하는 것과 포인터가 가지고 있는 메모리 주소에다가 덧셈 연산을 하는건 엄청난 차이가 있습니다. 절대 헷갈리시면 안 됩니다.
1차원 배열이 대입된 1차원 포인터의 메모리 주소에다가 1을 더해주면 포인터가 다음 인덱스로 가리키게 되고, 해당 메모리 주소에 위치한 값에다가 덧셈 연산을 하는건 메모리 주소 자체에는 변동이 없고 그냥 해당 값에다가 1을 더하는 겁니다.
이제 정리합니다. a에는 5, b에는 10, c에는 15 이걸 다 더해서 찍으면 당연히 30이죠.
설명은 여기서 마치겠습니다. 쉽게 설명하려고 1시간은 고민하면서 적은 것 같네요. 채택해 주시면 감사하겠습니다.
int vals[] = {5, 10, 15, 20};
int *p = vals;
int **pp = &p;
int a, b, c;
a = **pp;
(*pp)++;
b = **pp;
(*pp)++;
c = *(p++);
printf("%d\n", a + b + c);
글 목록으로 돌아가기