[서론입니다 중요한 내용이 아니니 넘어가도 좋습니다 ㅎㅎ;]
대학교에서 들었던 첫 프로그래밍 강의는 C언어였습니다.
당시 프로그래밍 자체에 대해서 아는 것이 없는 소위 말하는 노베이스였기 때문에 이해에 시간이 오래걸렸던 기억이 납니다.
그 중 제일 힘들었던 것은 바로 Pointer과 동적 할당 부분이었는데, 이 때에는 수박 겉핥기 식으로만 알고 넘어갔습니다.
지금은 휴학을 하고 C++을 공부하고 있는 상황인데, C와 비슷한 부분이 많다보니 Pointer에 대해서 제대로 알고싶은 마음이 들었고, 공부를 조금씩 하기 시작했습니다.
하지만 ㅎ;; 역시나 Pointer의 벽에 부딫혀버렸고, &와 *를 어떤 식으로 써야하는지, 왜 메모리 누수가 일어나는지, 매개변수를 전달할 때에는 어떻게 해야 할지 헷갈려도 너어어어무 헷갈렸습니다.
그러던 와중 mycodeschool이라는 youtube 채널을 발견했고(광고 아님!), 컴퓨터 구조와 연관지어 설명을 해주어 이해에 많은 도움이 됐습니다.
대부분 7~8년 전 영상이긴 하지만 pointer와 call stack 등과 같은 개념 이해에 많은 도움이 되었기 때문에, 이를 아이패드 안에만 기록해놓고 싶지 않았습니다.
블로그에 기록해두면 혹시나 pointer를 와장창 까먹어버린 미래의 저.. 혹은 친구들에게 설명해주고 싶을 때 편하게 볼 수 있을 것 같아 글을 올려보려고 합니다.
언어는 C와 C++을 기준으로 설명해보려고 합니다.
https://www.youtube.com/watch?v=h-HBipu_1P0&list=PL2_aWCzGMAwLZp6LMUKI3cc7pgGsasm2_
제가 참고한 Pointer과 관련된 youtube 재생 목록은 위의 링크를 타고 들어가서 보시면 됩니다.
⭐️ Introductions to pointers
1. Pointer를 이해하기 위해서는 우선 다양한 변수들이 컴퓨터 메모리에 어떻게 저장되나 알아 볼 필요가 있습니다.
왼쪽 사진을 우리 컴퓨터의 메모리의 일부분을 가시화한 것이라고 생각해봅시다.
왼쪽에 써져있는 숫자는 각 우리가 임시로 정한 메모리의 주소이며(실제 주소하고는 다릅니다! 가정이에요!), 각 칸의 크기는 1 byte입니다.
우리가 변수를 선언하면 메모리에 할당하게 되는데, 해당 변수가 메모리를 얼마나 차지할지는 변수의 data type(자료형)과 컴파일러(Compiler)에 의해 결정됩니다.
우리가 int a를 선언했다고 가정해봅시다. 컴파일러마다 차이는 있을 수 있지만 대부분 int형은 4 byte를 차지합니다. 변수 a가 메모리를 204~207까지 할당받았다고 가정합시다.
이 때, 컴퓨터 내부 구조에서는 "variable a, type integer, located address 204(시작 주소)"를 저장합니다.
이번엔 char c를 선언했다고 가정해봅시다. charater형은 1 byte를 차지합니다. 이 상황에서 변수 c가 메모리 주소 209에 할당받았다고 가정합니다.
위의 경우와 마찬가지로 컴퓨터 내부 구조에서 "variable c, type character, located address 209"를 저장합니다.
그렇다면 이러한 변수들을 가지고 연산을 하고 싶을 때에는 어떻게 할까요?
위에서 컴퓨터 내부 구조에 정보를 저장한다고 했죠? 이러한 정보들은 table 형식으로 저장됩니다.
우리가 a = 5;와 같은 작업을 수행한다면 컴퓨터는 변수의 정보가 적혀있는 table을 참조하고, 해당 변수의 메모리에 값을 대입합니다.
그렇다면 우리는 프로그램에서 주소를 어떻게 알 수 있을까요?
네, 바로 pointer를 이용해서 알 수 있습니다.
2. Pointer란 무엇인가?
우선 간단하게 Pointer의 정의를 내려봅시다.
pointer의 정의는 바로 "다른 변수의 주소(address)를 저장할 수 있는 변수"입니다.
1) pointer 선언 방법
우리가 일반적으로 변수를 선언할 때에는 int a와 같이 (자료형) (변수명)을 적습니다.
포인터 변수를 선언하고 싶을 때에는 (자료형) *(변수명) 형식(변수명 앞에 *)을 사용하면 됩니다.
int *p 처럼요.
이제 p는 정수형 변수의 주소를 담을 수 있는 정수형 포인터입니다.
(정수형 포인터인 p는 int형이 아닌 다른 자료형 변수의 주소는 담지 못합니다. 자세한 내용은 나중에 다시 다루도록 하겠습니다.)
2) &연산자
포인터를 사용하면서 많이 쓰게 될 연산자가 두 개 있는데, 하나는 *이고 다른 하나는 &입니다.
주소를 알고 싶은 변수명 앞에 &를 붙인다면 해당 변수의 주소를 반환합니다.
(정확히는 해당 변수의 포인터를 반환합니다)
따라서 만약 정수형 변수 a의 주소를 p에 담고 싶다면 p = &a;를 하면 됩니다.
3) * 연산자
그렇다면 * 연산자는 무엇일까요? 포인터 변수를 선언할 때 앞에 붙인 것은 알겠는데 어떻게 활용하냐는 겁니다.
여기서 de-referencing이라는 컨셉이 등장합니다.
우선 변수를 몇 개 선언해봅시다.
int a = 4; // a와 b는 정수형 변수
int b = 10;
int* p; // int형 포인터 변수
여기서 우리는 1. p가 a의 주소를 가리키게 하고 2. p를 통해서 a의 값을 바꾸려고 합니다.
먼저 p가 a의 주소를 가리키게 하려면 위에서 설명했던대로 & 연산자를 이용해서 p에 대입하면 됩니다.
p = &a; // a의 주소를 p에 저장
포인터 변수의 앞에 *을 붙이면 포인터가 가리키는 위치에 있는 값을 반환합니다. 이러한 컨셉을 de-referencing이라고 합니다.
따라서 2번을 실행하고 싶다면 밑과 같은 코드를 실행하면 됩니다.
*p = 5;
⭐️Working with pointers
1. Pointer variable은 strongly type입니다.
2. Example in C/C++ program
int main(void){
int a;
int *p;
a = 10;
p = &a;
*p = 12; // de-referencing
int b = 20;
*p = b; // Will the address in p change to point b?
// No! a의 값이 b의 값으로 변경
return 0;
}
3. Pointer의 연산
int a = 12;
int *p = &a;
printf("Address of p is %d", p); // 예를 들어 201이라고 가정
printf("value at address p is %d", *p); // 12
printf("Address p+1 is %d", p+1); // int는 4byte이므로 결과값은 205
printf("value at address p+1 is %d", *(p+1)); // 아무것도 안들어있으므로 쓰레기 값
많이 쓰이는 방법인지는 모르겠지만 pointer도 연산이 가능합니다.
위의 코드에서 포인터 변수 p를 int형 포인터 변수로 선언하고, a의 주소를 대입했습니다.
이 때, p+1연산을 하게 되면 p는 int*이기 때문에 integer의 size(즉, 4bytes)만큼 증가한 값을 반환합니다
위의 경우에는 p가 가리키는 주소가 201이었으니 p+1은 201+4 = 205가 됩니다.
하지만 주소 205에는 할당된 변수 혹은 값이 없으므로 printf를 해도 쓰레기 값이 출력됩니다.
⭐️ Pointers to pointers
참고 영상: https://www.youtube.com/watch?v=h-HBipu_1P0&list=PL2_aWCzGMAwLZp6LMUKI3cc7pgGsasm2_