[C] 포인터 헷갈리는 사람들 들어오세요.

C언어의 상징이자 생명과도 같은 존재 포인터.
그러나 학습자들에겐 분노 유발자이자 암덩어리 같은 존재일 것이다.

무수히 많은 학습자료들이 존재하지만,
현학적이고 눈에 들어오지 않는 것들이 태반이다.

그래서 마음먹었다.
정말 이해하기 쉽게 포인터의 전반적인 내용을 정리해보기로.

단, 독자들이 포인터의 개념정도는 알고 있다는 전제 하에 작성할 것이다.

1. 선언

포인터도 하나의 변수인데, 특정 변수의 주소값을 저장하는 변수다.

변수 이름 앞에 * 를 붙여 포인터 변수를 선언할 수 있고,
& 를 붙여 주소를 넘겨줄 수 있다.

int a = 1;
int *p = &a;

포인터변수p에는 변수a의 주소값이 담긴다.

가령, a라는 변수가 100번지에 저장되어 있다면,
p의 값은 100이 되는 것.

printf("%p\n", &a); //0x7ffc6193e28c
printf("%p\n", p);  //0x7ffc6193e28c

a 의 주소와 p 의 값이 같다. 기억하자. 포인터 변수에는 주소가 담긴다.

2. 역참조

현학적인 글들을 비판해놓고 이런 용어를 사용하게 된 것에 유감이다.

int a = 1;
int *p = &a;

위의 코드를 보고, 아래의 출력 결과를 예상해보자.

printf("%p\n", p); 
printf("%d\n", *p);  

첫 코드는 이제 익숙하다. a 라는 변수의 주소값이 출력될 것이다.
두 번째는? p 를 출력하는 것이 아니라 *p 를 출력하고 있다.

결과는 1 이다.

포인터를 선언하는 그 순간을 제외하고는 * 을 붙이면
주소가 아니라 그 주소에 들어있는 실제 값을 의미한다.

a의 값인 1 이 되는 것.

이렇게, * 을 사용해서 포인터가 가리키는 원래 변수에 접근하는 것을
역참조라고 부른다.

아래 코드의 출력 결과를 예상해보자.

int a  = 1;
int *p = &a;

*p = 10;
printf("%d\n", a);

정답은 10이다.

*p = 10; 에서 a 의 값도 10 으로 바뀐다.

왜?

포인터를 선언하는 딱 그 순간 외에는 * 을 붙인다는 것은 곧
그 포인터가 가리키는 변수인 a 를 사용하겠다는 의미이기 때문.

3. NULL 포인터

아무것도 가리키지 않는 포인터를 널 포인터라고 부른다.

아래와 같이 작성하면 된다.

int *p = NULL;

포인터를 만들어두긴 했는데, 당장은 가리킬 대상이 없다면
위의 코드처럼 NULL 을 명시적으로 할당해주는 것이 좋다.

int *p1;
printf("%p\n", p1); //0x7ffd264491a0

int *p2 = NULL;
printf("%p\n", p2); //(nil)

p1 은 가리키는 변수가 없음에도 어딘지 모를 쓰레기 주소가 출력되었고,
p2 는 명시적으로 NULL 을 주어 nil 이 출력되었다.

따라서, 가리키는 것이 없는 상황에서는 선언과 동시에 값을 NULL 로 초기화해야 한다.
이 경우 메모리상에서 0번지 에 저장된다.

4. 더블 포인터

포인터도 결국 변수다.
즉 포인터도 메모리상에 저장된 주소값이 존재할거다.

더블 포인터는 포인터의 주소를 담는 포인터다.

더블 포인터라는 이름에 맞게
선언과 역참조시에도 *을 두 개 써주면 된다.

  int a = 1;
  int *p1 = &a;
  int **p2 = &p1;

  printf("a의 주소값:   %p\n", &a);
  printf("p1에 담긴 값: %p\n", p1);
  printf("p1의 주소값 : %p\n", &p1);
  printf("p2에 담긴 값: %p\n", p2);

  printf("*p1의 결과:  %d\n", *p1);
  printf("**p2의 결과: %d\n", **p2);

위의 출력 결과는 아래와 같다.

a의 주소값:   0x7ffdb212f584
p1에 담긴 값: 0x7ffdb212f584
p1의 주소값 : 0x7ffdb212f588
p2에 담긴 값: 0x7ffdb212f588
*p1의 결과:  1
**p2의 결과: 1

포인터 p1a 의 주소를,
더블포인터 p2 는 포인터인 p1 의 주소를 저장한다.

p1의 역참조인 *p1 은 곧 a의 값이므로 1 이다.
p2의 역참조인 **p2 도 값이 동일하게 1 이 나왔다.

그렇다. 위의 상황에서, **p2 == *p1 == a 이다.
**p2 == a 이므로, 아래의 코드처럼 더블포인터로 a의 값을 수정할 수 있다

  int a = 1;
  int *p1 = &a;
  int **p2 = &p1;

  **p2 = 10;
  printf("a값:%d\n", a);

출력 결과 a는 10이 될 것이다.

5. 포인터와 + 연산 (p + i란 대체 무엇인가?)

포인터에는 주소값이 들어있다.
주소값에 1을 더하면, 다음 번지의 주소값이 나온다.

말로는 어려우니 예제 코드와 함께 살펴보겠다.

  char a = 'a';
  char *p1 = &a;
  
  printf("%d\n", (int)p1);
  printf("%d\n", (int)(p1 + 1));
  printf("%d\n", (int)(p1 + 2));
  printf("%d\n", (int)(p1 + 3));

변수 a가 691378895 번지에 저장되어 있다고 가정하면,
위의 출력 결과는 어떻게 될지 예상해보자.

답은 아래와 같다.

691378895
691378896
691378897
691378898

즉, p + 1 의 의미는, 다음 번지의 주소를 알려달라는 것이다.
p1 + 3 은 691378895에 3을 더한 값인 691378898가 되는 것이다.

아래의 출력 결과도 예상해보길 바란다.
변수 a는 140731385761612번지에 저장되었다고 가정한다.

  int a = 1;
  int *p = &a;

  printf("%ld\n", (long)p);
  printf("%ld\n", (long)(p + 1));
  printf("%ld\n", (long)(p + 2));
  printf("%ld\n", (long)(p + 3));

답은 다음과 같다.

140731385761612
140731385761616
140731385761620
140731385761624

위의 예제와 달리, +1 을 했는데 주소값이 1이 아니라 4 가 늘어났다.
이유는 자료형에 있다.

위의 예제는 자료형이 char였고, 지금의 예제는 int이다.
char는 1바이트, int는 4바이트!

char의 경우 데이터 하나가 들어가기 위한 공간이 1바이트이므로,
다음 번지(공간이라 생각하자)의 주소값도 1이 늘어난다.

반면 int는 데이터를 저장하기 위해 4바이트의 공간이 필요하기 때문에,
다음 번지는 주소값이 4가 늘어난다.

정리하면,p + 1 은 다음 번지의 주소를 의미하고,
그 값은 자료형의 크기를 고려하여 결정된다.

이를 일반화하면,
p + i 의 실제 값은 p + i*n 가 되는 것이다. (n은 자료형의 크기: byte)

6. 배열과 주소

아래의 배열은 메모리상에 어떻게 저장될까?
상상하며 직접 그려보길 바란다.

 int arr[3] = {1, 2, 3};

첫 원소의 주소값을 편의상 100이라 하면, 아래와 같을 것이다.
기억해야 할 것은, 두 번째 요소는 101번지가 아니라 104번지라는 것.


엥? 싶다면 5번 내용 다시 읽어보기

짚고 넘어가야 할 것은,
배열의 각 원소들은 각자의 주소값을 가진다는 것.

7. 배열의 이름 == 첫 원소의 주소

6번 주제에서 배열의 각 원소는 각자의 주소값을 갖는다는걸 확인했다.

포인터에 배열의 이름을 할당해주면,
그 배열의 첫 원소의 주소값이 담긴다.

말이 또 복잡해지려 하니 예제 코드와 함께 살펴보자.

  int arr[3] = {1, 2, 3};
  int *p = arr;

배열 arr 는 원소가 3개이므로 주소값도 3개일텐데 포인터p 에 어떻게 주소를 다 할당할까?
결론은 배열의 이름을 넘겨주면 첫 원소(arr[0])의 주소값이 p 에 담긴다.

여기서 주의할 점은, 배열의 주소를 줄 땐 예외적으로 & 기호를 사용하지 않는다.
그냥 배열의 이름만 넘겨주면 된다.

이제, p 를 통해서 arr 라는 배열의 각 원소에 접근할 수도, 수정할 수도 있다.

아래 코드들의 출력 결과를 예상해보길 바란다.
편의상 arr[0] 의 주소값은 100이라 가정한다.

  int arr[3] = {1, 2, 3};
  int* p = arr;

  printf("arr[0]의 주소값: %p\n", &arr[0]);
  printf("p의 값: %p\n", p);

  printf("arr[1]의 주소값: %p\n", &arr[1]);
  printf("p + 1의 값: %p\n", p + 1);

  printf("arr[2]의 값: %d\n", *(arr + 2));
  printf("*(p+2)의 값: %d\n", *(p + 2));

정답은 아래와 같다

100
100
104
104
3
3

중요한 부분만 정리하고 넘어가자.

  1. printf("p + 1의 값: %p\n", p + 1); 는 배열의 두 번째 원소 arr[1] 의 주소값을 의미한다.

    즉, p + i&arr[i] 와 같다.

  2. printf("*(p+2)의 값: %d\n", *(p + 2)); 는 배열의 세 번째 원소 arr[2] 의 값(역참조)을 의미한다.
    즉, *(p + i)arr[i] 와 같다.

  3. printf("arr[2]의 값: %d\n", *(arr + 2)); 는 는 배열의 세 번째 원소 arr[2] 의 값을 의미한다. 배열의 이름인 arrarr[0] 의 주소를 의미하고, +2 는 다다음 번지인 arr[2] 의 주소를 의미하기 때문이다.

또, 포인터도 인덱싱을 통해 배열에 접근할 수 있다.
한마디로, p[i]arr[i] 이다.

  int arr[3] = {1, 2, 3};
  int* p = arr;

  p[2] = 100;
  
  printf("arr[2]의 값: %d\n", arr[2]);

위 코드를 수행하면 arr[2] 는 100이 된다.

결론: arr가 배열이고 p = arr일 때, p[i] == arr[i] == (p + i) == *(arr + i)

8. 배열포인터

배열포인터는 뒤에 나올 포인터 배열과 완전 다르다.

배열 포인터는 배열 전체를 가리킨다.
배열의 이름이 배열의 첫 원소의 주소를 가리키는 것과 달리
배열포인터는 배열 전체의 주소를 가리킨다.

배열 포인터는 다음과 같이 만들어줄 수 있다.
int (*p)[3] = &arr;
위 구문은 3칸짜리 int형 배열 전체를 가리키는 배열포인터 p 를 만든 것이다.

중요한 점은 배열포인터의 이름에 소괄호를 반드시 적어주어야 하며,
주소를 받을 배열 이름 앞에 & 를 붙여줘야 한다는 것이다.

말이 어렵기 때문에 직관적인 코드와 살펴보자.
아래의 코드를 이해하면, "배열 전체를 가리킨다" 는 것의 의미를 이해할 수 있다.

편의상 arr[0] 의 주소값을 100이라 한다.

  int arr[3] = {1, 2, 3};

  int *p1 = arr;
  int (*p2)[3] = &arr;

  printf("%ld\n", (long)(p1));
  printf("%ld\n", (long)(p1 + 1));
  printf("%ld\n", (long)(p2 + 1));

포인터 p1은 우리가 지금까지 본 방식으로, 배열의 이름을 전달하는 일반적인 포인터다.
이 경우 p1에는 arr[0] 의 주소값이 할당된다.

p2가 바로 배열포인터다. 소괄호와 &를 사용해야 한다는 점을 다시 한 번 기억하자.
이 경우, p2에도 arr[0] 의 주소가 할당된다.

차이는 +1 의 동작방식에 있다.
p1 + 1 은 배열 내부에서 다음 원소를 의미한다. 즉, 104 가 된다.

반면, p2 는 배열 전체를 하나로 보기 때문에,
p2 + 1 을 해주면 배열 전체 크기만큼의 주소값이 늘어난다. 즉, 112 가 된다.
(배열의 각 원소는 int이므로 4바이트, 원소가 3개이므로 배열은 12바이트)

추가로, 배열 포인터에서는 다음과 같은 방식으로 배열의 각 요소에 접근할 수 있다
(*p)[i]

아래와 같이 배열포인터로 배열의 각 원소에 접근 할 수 있다.

  int arr[2] = {1, 2};
  int (*p)[2] = &arr;

  for (int i = 0; i < 2; i++) {
    printf("%d\n", (*p)[i]);
  }

배열포인터의 개념이 어렵지만,
일단 무엇인지는 어느정도 정리가 됐다.

그렇다면, 대체 어디에 쓰려고 이런 복잡한 녀석을 배워햐 한단 말인가?

배열포인터는 2차원 배열의 각 행 하나 하나를 가리키기 위한 용도로 자주 사용한다.
조금 더 자세히 말하면, 함수의 파라미터로 2차원 배열을 넘겨줄 때 사용하게 된다.
이 예제는 뒤에서 살펴볼 것이다.

9. 2차원 배열의 주소

2차원 배열이 메모리상에 어떻게 저장되는지 짚고 넘어가자.

int arr[2][2] = {{1, 2}, {3, 4}};

다음과 같은 2차원 배열은 메모리상에 아래와 같이 저장된다.

한마디로, arr[0][1] + 1arr[1][0] 이 된다.

10. 배열포인터 사용예제

8번 주제에서 배열포인터는 2차원 배열을 다루기 위해 사용한다고 했다.
이번 토픽에서는 배열포인터의 사용 예제를 살펴본다.

아래는 이차원 배열을 파라미터로 받아 각 원소들을 출력하는 함수이다.

void print_2D_array(int rows, int cols, int (*arr_ptr)[cols]) {
  for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
      printf("%d", arr_ptr[i][j]);
    }
    printf("\n");
  }
}

위의 함수에서 rows와 cols는 배열의 행과 열의 수를 의미한다.
배열포인터 arr_ptr은 원소의 개수가 cols개인 배열의 주소를 가리킨다.

출력문에서, printf("%d", arr_ptr[i][j]);printf("%d", (*(arr_ptr + i))[j]); 혹은 printf("%d", *(*(arr + i) + j)); 로 바꿔도 무방하다.

배열 포인터가 이차원 배열을 가리키면, 배열포인터는 이차원 배열의 첫 원소의 주소인
0행 1차원 배열의 주소를 가리킨다. 즉, +i 는 행의 이동, +j 는 행 내에서 열의 이동이다.

물론 위처럼 배열 포인터를 사용하지 않고 명시적으로 파라미터에 이차원 배열임을 직접 명시하여 작성할 수도 있다. 아래와 같이 말이다.

void print_2D_array(int rows, int cols, int arr[][cols]) {
  for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
      printf("%d", arr[i][j]);
    }
    printf("\n");
  }
}

하지만 이렇게 작성해도 내부적으로 arr는 배열포인터가 된다.
조금 더 엄밀히 말하면, arr도 결국 컴파일러에 의해 배열포인터로 변환된다.

그러니 두 작성법 모두 알아두길 바란다.

11. 포인터배열

포인터배열은 배열포인터랑 전혀 다른 개념이다.
혼동해선 안된다.

포인터배열은 포인터로 이루어진 배열이다.
원소가 포인터라는 뜻.

다음과 같이 선언할 수 있다.

int *p[5];

위 코드에서 p는 포인터배열이다.
정확히는, int형 변수의 주소를 담는 포인터 5개로 이루어진 배열이다.

포인터 배열 선언법을 일반화하면 아래와 같다.
자료형 *이름[원소개수]

배열 포인터의 선언법과 무엇이 다른지 보이는가?
바로 보이지 않는다면 배열 포인터 섹션으로 다시 올라가길 바란다.

정답은 소괄호의 유무에 있다.
int (*p)[3]int *p[3] 은 전혀 다르다!

포인터배열을 구성하는 포인터가 가리키는 주소에 들어있는 실제 값에 접근할 때도
포인터의 역참조 문법이 동일하게 적용된다.

다음과 같이 n번째 원소 포인터가 가리키는 주소지의 실제 값을 받아올 수 있다.
*p[n]

배열 포인터의 역참조인 (*p)[n] 과 헷갈리지 않도록 주의하자.

12. 함수포인터

함수도 메모리상에 저장되기에, 주소값을 갖는다.
함수의 주소를 저장하는 변수를 함수포인터라고 부른다.

함수 포인터는 다음과 같이 선언할 수 있다.
리턴타입 (*포인터이름)(매개변수타입);
소괄호를 빼먹어선 안되니 꼼꼼히 챙겨두자.

예를 들어 다음과 같은 함수가 있다고 할 때,
이 함수의 주소를 가리키는 포인터를 어떻게 만들 수 있을지 먼저 생각해보기 바란다.

int adder(int v1, int v2) {
    return v1 + v2; 
}

이 함수의 주소를 담을 포인터의 이름을 p라 하면,
함수포인터 p는 아래와 같이 작성할 수 있다.

int (*p)(int, int);

이제, 만든 함수 포인터에 adder함수의 주소를 할당해주자.
p = adder; 와 같이 함수의 이름을 전달하면 된다.

이제 p로 adder함수에 접근이 가능해졌다.
즉, 함수포인터 p 로 adder함수를 호출할 수 있다.

int adder(int v1, int v2) {
    return v1 + v2; 
}

int (*p)(int, int);
p = adder;

int result = p(1,2)

위의 코드에서 p(1, 2)adder(1, 2) 와 완전히 동일한 것이다.

조금 더 응용해서,
함수의 매개변수로 함수포인터를 전달할 수도 있다.

간단한 예제를 살펴보자.

int add(int v1, int v2) {
  return v1 + v2;
}

int minus(int v1, int v2) {
  return v1 - v2;
}

int add_or_minus(int(*ptr)(int, int), int v1, int v2) {
  return ptr(v1, v2);
}

위의 add_or_minus 함수의 첫 번째 파라미터를 보자.
반환타입이 int이고, 2개의 int형 파라미터를 갖는 함수에 대한 포인터다.

이제, add_or_minus함수를 아래와 같이 호출할 수 있다.
add_or_minus(add, 20, 10);
add_or_minus(minus, 20, 10);
첫 인자로 addminus 함수 이름을 전달할 수 있는 것이다.

13. 문자열과 포인터

문자열도 배열이다. char형의 배열.

문자열을 선언하는 두 가지 방식을 정리하고 넘어가보자.

방법1 - 배열

가장 일반적인 방식으로, 아래의 두 가지 형태로 작성 가능하다.

char name[100] = {'L', 'e', 'e'};
char name[100] = "Lee";

만약 배열의 크기를 타이트하게 잡을 것이라면, null문자의 공간만 잘 생각해주자.

방법2 - 포인터

포인터를 사용해서도 문자열을 선언할 수 있다.

char *name = "Lee";

두 방식의 차이점

두 방식은 여러 결정적인 차이점을 갖는다.
숙지하고, 상황에 맞게 사용하자.

표의 내용에 약간의 설명을 더하면,
배열로 선언하면 인덱싱을 통해 문자열의 일부 값을 변경하는 것은 가능하지만,
완전히 새로운 문자열을 새로 할당하는 것은 제한된다.

예를 들어, 아래와 같이 인덱싱을 통해 문자열을 수정하는 것은 가능하다.

char name[100] = "kim";

name[0] = 'l';
name[1] = 'e';
name[1] = 'e';

하지만, 아래와 같은 코드는 에러가 난다.

char name[100] = "kim";

name = "lee"; //에러!!!!!! 

포인터로 선언한 문자열은 그 반대이다.

예를 들어, 아래와 같이 인덱싱을 통해 문자열을 수정하는 것은 제한된다.

char *name = "kim";
name[0] = 'l'; //에러!

하지만, 아래와 같이 통째로 다른 문자열을 할당하는 것은 가능하다.
문자열이 수정되는 것이 아니라, 포인터가 가리키는 주소지가 완전히 바뀌어버리는 것이다.
이 개념을 정확히 이해하길 바란다.

포인터로 문자열을 선언하면, 처음엔 kim이라는 문자열의 주소를 가리키다가,
lee라는 새로운 문자열을 할당하면 lee라는 문자열의 주소를 가리키도록 포인터의 방향이 바뀌는 것이라 이해하면 된다.

char *name = "kim";
name = "lee"; //가능! 포인터의 주소가 바뀜 

14. 구조체와 포인터

구조체도 당연히 주소를 갖는다.
고로, 구조체의 주소도 포인터에 담을 수 있다.

구조체 포인터의 선언법은 아래와 같이 일반화 해볼 수 있다.
구조체이름 *포인터이름 = &구조체변수이름

예제 코드로 살펴보자.

typedef struct {
  char name[30];
  int age;
} User;

int main() {
  User user1 = {"홍길동", 20};
  User *p = &user1;

  printf("%s\n", (*p).name);
  printf("%s\n", p -> name);
}

위 코드에서 p는 구조체변수 user1의 주소를 저장하는 구조체포인터다.
따라서, 출력문에서처럼 user1 대신 p로도 구조체의 멤버변수에 접근할 수 있다.

구조체에 대한 내용을 숙지하고 있다 생각하지만,
중요한 내용이니 정리해보자.

구조체의 포인터 멤버변수가 가리키는 주소지에 들어있는 실제 값에 접근하려면
아래와 같은 두 가지 방식 중 하나를 사용해야 한다.

(*p).name or p->name

15. 함수 call-by-address

C에서 함수를 호출하는 두 가지 방식이 있다.

함수를 호출할 때 인자에 값을 넘겨주는 call-by-value 방식과,
인자로 주소값만 넘겨주는 call-by-address방식.

이 역시 함수를 공부하면서 등장하는 내용이기에,
어느정도의 내용은 알고 있다는 전제 하에, 포인터와 관련된 내용에 무게를 두어 정리하려 한다.

이번 포스팅의 내용을 모두 이해했다면,
함수를 call-by-address방식으로 호출한다는 것은 인자에 포인터나 배열의 이름을 전달한다는 것과 같은 의미인 것을 이해할 수 있을 것이다.

중요한 것은, 뭐하러 주소값을 전달하느냐는 것이다.
표로 정리하면 아래와 같다.

가장 중요한 것은, 주소값을 전달해 함수 안에서 값을 수정하면,
그 수정 내역이 전역적으로 반영된다는 것이다.

예제 코드로 이해해보자.

#include <stdio.h>

// 1. Call by Value
void levelUp_Value(int level) {
    level += 1;
    printf("  [Value 함수 안] 레벨 업 시도... 현재 레벨: %d\n", level);
}

// 2. Call by Address
void levelUp_Address(int *level_ptr) {
    *level_ptr += 1;
    printf("  [Address 함수 안] 레벨 업 시도... 현재 레벨: %d\n", *level_ptr);
}

int main() {
    int myLevel = 10;

    printf("--- 게임 시작! 현재 내 레벨: %d ---\n\n", myLevel);

    // [CASE 1] Call by Value
    printf("1. Call by Value 호출 중...\n");
    levelUp_Value(myLevel);
    printf(">> 호출 후 메인 함수 레벨: %d (변화 없음!)\n\n", myLevel);

    // [CASE 2] Call by Address
    printf("2. Call by Address 호출 중...\n");
    levelUp_Address(&myLevel); // 주소를 넘김
    printf(">> 호출 후 메인 함수 레벨: %d (레벨 업 성공!)\n", myLevel);

    return 0;
}

위 코드에서 핵심은 함수를 호출하는 부분에 있다.
[CASE1] 에서는 levelUp_Value(myLevel); 이렇게 값을 직접 전달했고,
[CASE2] 에서는 levelUp_Address(&myLevel); 이렇게 주소를 전달했다.

[CASE1] 이 Call by Value, [CASE2]가 Call by Address 방식이 된다.

 levelUp_Value(myLevel);
 printf(">> 호출 후 메인 함수 레벨: %d (변화 없음!)\n\n", myLevel);

이 경우, 함수에는 데이터의 값이 복사되어 전달되고,
함수 안에서 level의 값을 1 증가시켰지만, 그 작업은 해당 함수 안에서만 유효하다.
levelUp_Value함수 밖인 main함수 안에서 level을 출력해보니 그대로 10인 것을 확인할 수 있을 것이다. (직접 실행해보길 바란다)

 levelUp_Address(&myLevel); // 주소를 넘김
 printf(">> 호출 후 메인 함수 레벨: %d (레벨 업 성공!)\n", myLevel);

이 경우 & 기호를 통해 myLevel 의 값인 10 이 아니라,
myLevel 이라는 변수가 저장된 메모리상의 주소값을 전달하고 있다.

고로, levelUp_Address 함수는 복사된 값을 수정하는게 아니라,
함수 호출시 들어온 주소지를 타고 넘어가 그 주소지에 있는 변수의 값을 바꾸는 것이다.

고로, levelUp_Address 함수 밖인 main 함수에서도 level 의 값이 11 로 증가한 것을 확인할 수 있을 것이다.

정리하면, 주소값을 전달하면 함수에서의 작업내용이 전역적으로 반영된다는 것!
상황에 맞게 잘 사용하자.

16. 마치며..

포인터의 개념은 처음에 다소 생소하게 다가오지만,
Python이나 JavaScript같은 언어만 사용해 온 사람들에게는 신선한 자극을 주기도 한다.

"주소" 라는 개념 자체가 어색하겠지만,
이를 이해하는 순간 소스코드를 바라보는 안목이 아주 조금은 높아질 것이다.

프로그래머들은 항상 why, how에 집중하여 끊임없이 생각하는 사람이 되어야 할 것이다.

멋진 척 가득했지만 나도 배우는 대학생일 뿐이다.
현학적으로 보였다면 고개 숙여 사과한다. 너그러이 봐주길 바란다.

이상으로 글을 마친다.

profile
Backend Engineer | Sungkyunkwan University

0개의 댓글