포인터(Pointer)란?
프로그래밍에서 *포인터(pointer)*는 메모리의 주소를 저장하는 변수이다. 일반적인 변수는 특정 값을 저장하지만, 포인터는 변수나 배열, 함수 등의 메모리 주소를 저장하여 직접 참조할 수 있도록 한다. 포인터를 사용하면 효율적인 메모리 관리와 고급 프로그래밍 기법을 활용할 수 있다.
1. 포인터 선언과 사용
포인터는 특정 자료형의 주소를 저장할 수 있도록 선언한다. 선언할 때 * 기호를 사용하여 해당 변수가 포인터임을 나타낸다.
#include <stdio.h>
int main() {
int a = 10; // 정수형 변수 a 선언 및 초기화
int *p; // 정수형 포인터 p 선언
p = &a; // 변수 a의 주소를 포인터 p에 저장
printf("a: %d\n", a);
printf("a 주소: %p\n", &a);
printf("포인터 p 주소: %p\n", p);
printf("포인터 p 값: %d\n", *p);
return 0;
}
실행결과

위 코드에서 p는 a의 주소를 저장하며, *p를 사용하면 a의 값을 참조할 수 있다.
2. 포인터의 주요 개념
2.1 주소 연산자 (&)
변수의 주소를 가져올 때 사용한다.
int a = 10;
int *p = &a; // 변수 a의 주소를 p에 저장
2.2 간접 참조 연산자 (*)
포인터가 가리키는 주소에 저장된 값을 참조하는 데 사용한다.
printf("p주소에 있는 값 : %d\n", *p); // 변수 a의 값을 출력
2.3 포인터의 크기
포인터의 크기는 운영 체제와 컴파일러에 따라 달라질 수 있다. 일반적으로 32비트 시스템에서는 4바이트, 64비트 시스템에서는 8바이트이다.
printf("포인터 크기: %lu\n", sizeof(p));
실행결과
p에 저장된 값 : 10
포인터 크기: 8 // 64비트 운영체제 - 32비트의 경우 4
3. 포인터와 배열
배열의 이름은 해당 배열의 첫 번째 요소의 주소를 나타내므로 포인터와 유사한 방식으로 동작한다.
#include <stdio.h>
int main() {
int arr[3] = {1, 2, 3};
int *p = arr; // 배열의 첫 번째 요소 주소를 포인터 p에 저장
printf("첫 번째 요소: %d\n", *p);
printf("두 번째 요소: %d\n", *(p + 1));
printf("세 번째 요소: %d\n", *(p + 2));
return 0;
}
배열 이름 자체가 주소를 의미하므로 arr은 &arr[0]과 동일한 값을 가진다.
실행결과
첫 번째 요소: 1
두 번째 요소: 2
세 번째 요소: 3
4. 동적 메모리 할당과 포인터
C 언어에서는 malloc(), calloc(), realloc() 등의 함수를 사용하여 동적으로 메모리를 할당할 수 있다. 할당된 메모리는 반드시 free()를 사용하여 해제해야 한다.
#include <stdio.h>
#include <stdlib.h> // malloc, calloc, realloc, free 사용을 위한 헤더 파일
int main() {
// 1. malloc() 사용: 동적 메모리 5개 할당 (초기화되지 않음)
int *arr1 = (int*)malloc(5 * sizeof(int));
if (arr1 == NULL) {
printf("메모리 할당 실패 (malloc)\n");
return 1;
}
// 초기 값 확인 (쓰레기 값 출력 가능)
printf("malloc() 사용 후 초기 값:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", arr1[i]); // 초기화되지 않은 값(쓰레기 값)
}
printf("\n");
// 2. calloc() 사용: 동적 메모리 5개 할당 (0으로 초기화됨)
int *arr2 = (int*)calloc(5, sizeof(int));
if (arr2 == NULL) {
printf("메모리 할당 실패 (calloc)\n");
return 1;
}
// 초기 값 확인 (모두 0 출력)
printf("calloc() 사용 후 초기 값:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", arr2[i]); // 0으로 초기화된 값
}
printf("\n");
// 3. realloc() 사용: 기존 arr1을 크기 10으로 재할당 (값 유지)
arr1 = (int*)realloc(arr1, 10 * sizeof(int));
if (arr1 == NULL) {
printf("메모리 재할당 실패 (realloc)\n");
return 1;
}
// realloc() 후 기존 값 유지 및 새로 할당된 공간 값 확인 (쓰레기 값 가능)
printf("realloc() 사용 후 값:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", arr1[i]); // 처음 5개는 유지, 나머지는 초기화되지 않음
}
printf("\n");
// 4. 동적 메모리 해제
free(arr1); // realloc된 메모리 해제
free(arr2); // calloc으로 할당된 메모리 해제
return 0;
}
실행결과
// ....? 0으로 초기화되어서 나왔는데 os나 컴파일러 최적화 때문에 이렇게 보일 수 있다고는 하는데...
// 항상 초기화 되지 않는다고 가정하는 것이 안전하다.
malloc() 사용 후 초기 값:
0 0 0 0 0
calloc() 사용 후 초기 값:
0 0 0 0 0
realloc() 사용 후 값:
0 0 0 0 0 0 0 0 0 0
5. 함수와 포인터
포인터는 함수의 매개변수로 전달될 수 있으며, 이를 통해 함수에서 원본 데이터를 직접 변경할 수 있다.
#include <stdio.h>
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 5, b = 10;
printf("교환 전: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("교환 후: a = %d, b = %d\n", a, b);
return 0;
}
실행결과
교환 전: a = 5, b = 10
교환 후: a = 10, b = 5
6. 이중 포인터
이중 포인터는 포인터를 가리키는 포인터이다. 즉, 포인터 변수의 주소를 저장하는 변수이다.
#include <stdio.h>
int main() {
int a = 100;
int *p = &a;
int **pp = &p;
printf("변수 a의 값: %d\n", a);
printf("포인터 p가 가리키는 값: %d\n", *p);
printf("이중 포인터 pp가 가리키는 값: %d\n", **pp);
return 0;
}
이중 포인터는 동적 메모리 할당, 다차원 배열, 포인터 배열 등을 다룰 때 유용하게 사용된다.
실행결과
변수 a의 값: 100
포인터 p가 가리키는 값: 100
이중 포인터 pp가 가리키는 값: 100
7. 마무리
포인터는 C 및 C++에서 메모리 관리를 효율적으로 수행할 수 있도록 돕는 중요한 개념이다. 기본적인 사용법을 이해하고 나면 포인터를 활용하여 배열, 함수, 동적 메모리 할당 등을 효율적으로 다룰 수 있다.