[C] Pointer

Jaehyun Park·2024년 3월 27일

1. 주소값의 이해

데이터의 주소값이란 해당 데이터가 저장된 메모리의 시작 주소를 의미한다.
C언어에서는 이러한 주소값을 1바이트 크기의 메모리 공간으로 나누어 표현한다.
예를 들어, int형 데이터는 4바이트의 크기를 가지지만, int형 데이터의 주소값은 데이터가 저장된 첫 번째 공간인 시작 주소 1바이트만을 가리킨다.


2. 포인터 (Pointer)

1) 포인터란?

C언어에서 포인터(pointer)란 메모리의 주소값을 저장하는 변수이며, 포인터 변수라고도 부른다.
char형 변수가 문자를 저장하고, int형 변수가 정수를 저장하는 것처럼 포인터는 주소값을 저장한다.

int n = 100;    // 변수의 선언
int *ptr = &n;  // 포인터의 선언, ptr에는 n이 저장된 주소 값을 저장함

다음 그림은 위의 예제에서 사용된 변수와 포인터가 메모리에서 어떻게 저장되는지를 보여주는 예제이다.

변수 n은 100이라는 값을 갖고 있고, 메모리 주소 0x12~0x15만큼의 공간을 차지하고 있다.
이때, 변수 n의 주소값은 메모리 공간의 첫 번째 공간인 0x12이다.

포인터 ptr은 변수 n의 주소값(0x12)를 가리키고(&n) 있으며, 0x12ptr에 저장되게 된다.

2) 포인터 연산자

C언어에서 포인터와 연관되어 사용되는 연산자는 주소 연산자(&), 참조 연산자(*)가 있다.

주소 연산자(&)는 변수의 이름 앞에 사용하여 해당 변수의 주소값을 반환한다.
참조 연산자(*)는 포인터의 이름이나 주소 앞에 사용하여 포인터에 가리키는 주소에 저장된 값을 반환한다.

3) 포인터 선언

syntax

타입 *포인터이름;

타입이란 포인터가 가리키고자 하는 변수의 타입을 명시하는 것이고, 포인터 이름은 포인터가 선언된 후에 포인터에 접근하기 위해 사용된다.
포인터를 선언한 후 참조 연산자(*)를 사용하기 전에 포인터는 반드시 먼저 초기화되어야 한다. 그렇지 않으면 의도하지 않은 메모리의 값을 변경하게 되고, C 컴파일러는 초기화하지 않은 포인터에 참조 연산자를 사용하면 오류를 발생시킨다.

따라서 다음과 같이 포인터의 선언과 동시에 초기화를 함께 하는 것이 권장된다고 한다.

type *ptr = &변수이름;

//또는
type *ptr = 주소값;

4) 포인터의 참조

C언어에서 선언된 포인터는 참조 연산자(*)를 사용하여 참조할 수 있다.

int x = 100;          // 변수의 선언
int *ptr = &x;      // 포인터의 선언, x의 주소값을 저장함
int *pptr = &ptr;   // 포인터의 참조, ptr의 주소값을 저장함

  1. 변수 x가 선언되었다. 이 변수의 값은 100이며, 주소값은 0x12이다.
  2. 포인터 ptr이 선언된 동시에 변수 x의 주소값으로 초기화되었다. ptr이 가리키고 있는 값은 변수 x의 주소값인 0x12이다.
  3. 포인터 pptr이 선언된 동시에 포인터 ptr의 주소값으로 초기화되었다. ptr의 주소값은 0x25이며, pptr은 포인터 ptr의 주소값인 0x25가 저장된다.
    => ptrx의 주소값을 초기화했고, pptrptr을 한 번 더 참조하는데, 이때 헷갈릴 수 있을 거 같다. 단순히 생각해서, ptr에 들어있는 값이 무엇이든 신경쓰지 말고, &ptr은 포인터 ptr의 주소값만 가리킨다고 생각하면 될듯 하다.

다음 예시를 보자.

int num01 = 1234;
int *ptr_num01 = &num01;

double num02 = 3.14;  
double *ptr_num02 = &num02;  


printf("포인터 ptr_num01이 가리키고 있는 주소값은 %p입니다.\n", ptr_num01);
printf("포인터 ptr_num02가 가리키고 있는 주소값은 %p입니다.\n", ptr_num02);

printf("포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num01);
printf("포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 %f입니다.\n", *ptr_num02);
output:
포인터 ptr_num01이 가리키고 있는 주소값은 0x7ffce6459cbc입니다.
포인터 ptr_num02가 가리키고 있는 주소값은 0x7ffce6459cb0입니다.
포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 1234입니다.
포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 3.140000입니다.

포인터 ptr_num01ptr_num02는 저장된 값을 출력했을 때 각각 자신들이 가리키고 있는 변수가 저장된 주소값을 갖고 있다는 것을 확인할 수 있다.

유심히 봐야할 것은, 두 번째 출력 단에서 이번에는 각각의 포인터에 참조 연산자(*)를 붙이고 있다.
이것은 포인터가 가리키고 있는 주소에 저장된 값을 참조하겠다는 연산자로, 다음과 같이 사용할 수 있다.

int x = 100;     // 변수 x 초기화
int *ptr = &x;   // 포인터 ptr 초기화 - x의 주소값
int y = *ptr;    // ptr이 가리키고 있는 주소값의 값을 참조
// 예시
#include <stdio.h>  

void local(int* num) {
  *num += 10;
}  

int main(void) {

  int var = 10;
  printf("변수 var의 초깃값은 %d입니다.\n", var);  

  local(&var);

  printf("local() 함수 호출 후 변수 var의 값은 %d입니다.\n", var);

  return 0;
}  
output:
변수 var의 초기값은 10입니다.
local() 함수 호출 후 변수 var의 값은 20입니다.

위와 같은 방식을 참조에 의한 전달(call-by-refenernce)라고 하는데, 이 부분도 나중에 추가로 정리해서 올리겠다.

profile
Technologically solve everyday challenges

0개의 댓글