포인터(pointer)는 C와 C++에서 가장 강력하면서도 중요한 개념 중 하나입니다. 단순히 변수의 메모리 주소를 저장하는 것을 넘어, 포인터는 배열 접근, 동적 메모리 관리, 효율적인 함수 호출 등 다양한 용도로 활용됩니다. 이번 글에서는 포인터의 대표적인 활용 사례를 C와 C++ 예제 코드와 함께 살펴보겠습니다.
1. 배열 요소에 접근하기
배열의 이름은 사실상 첫 번째 요소를 가리키는 포인터처럼 동작합니다. 따라서 포인터 연산을 이용하면 인덱스 없이도 배열 요소에 접근할 수 있습니다.
C 예제
#include <stdio.h>
int main() {
int a[] = { 60, 70, 20, 40 };
printf("%d\n", *(a + 1));
return 0;
}
실행 결과
70
a + 1은 배열의 두 번째 요소 주소를 의미하며, 역참조 연산자 *를 붙이면 해당 위치의 값인 70이 출력됩니다.
C++ 예제
#include <iostream>
using namespace std;
int main() {
int a[] = { 60, 70, 20, 40 };
cout << *(a + 1);
return 0;
}
실행 결과
70
2. 동적 메모리 할당
프로그램 실행 중에 필요한 만큼 메모리를 할당받으려면 포인터가 필수입니다. C에서는 malloc() 함수를 사용하여 힙(heap) 영역에 메모리를 동적으로 확보할 수 있으며, 사용이 끝난 후에는 반드시 free()로 해제해야 메모리 누수를 방지할 수 있습니다.
C 예제
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, *ptr;
ptr = (int*) malloc(3 * sizeof(int));
if(ptr == NULL) {
printf("Error! memory not allocated.");
exit(0);
}
*(ptr+0) = 1;
*(ptr+1) = 2;
*(ptr+2) = 3;
printf("Elements are:");
for(i = 0; i < 3; i++) {
printf("%d ", *(ptr + i));
}
free(ptr);
return 0;
}
실행 결과
Elements are:1 2 3
C++ 예제
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
int i, *ptr;
ptr = (int*) malloc(3 * sizeof(int));
if(ptr == NULL) {
cout << "Error! memory not allocated.";
exit(0);
}
*(ptr+0) = 1;
*(ptr+1) = 2;
*(ptr+2) = 3;
cout << "Elements are:";
for(i = 0; i < 3; i++) {
cout << *(ptr + i);
}
free(ptr);
return 0;
}
실행 결과
Elements are:1 2 3
참고로 C++에서는 malloc()보다 new와 delete 연산자를 사용하는 것이 더 안전하고 권장되는 방식입니다.
3. 함수에 참조 형태로 인자 전달하기
포인터를 이용하면 함수에 인자를 '값(value)'이 아닌 '참조(reference)'로 전달할 수 있습니다. 이렇게 하면 원본 데이터를 직접 수정할 수 있고, 큰 데이터를 복사하지 않아도 되므로 성능 면에서도 유리합니다. 대표적인 예가 두 변수의 값을 맞바꾸는 swap() 함수입니다.
C 예제
#include <stdio.h>
void swap(int* a, int* b) {
int t = *a;
*a = *b;
*b = t;
}
int main() {
int m = 7, n = 6;
swap(&m, &n);
printf("%d %d\n", m, n);
return 0;
}
실행 결과
6 7
C++ 예제
#include <iostream>
using namespace std;
void swap(int* a, int* b) {
int t = *a;
*a = *b;
*b = t;
}
int main() {
int m = 7, n = 6;
swap(&m, &n);
cout << m << n;
return 0;
}
실행 결과
67
함수 호출 시 &m, &n처럼 변수의 주소를 넘기면, 함수 내부에서 포인터를 통해 원본 변수의 값을 직접 변경할 수 있습니다.
4. 자료구조 구현하기
포인터는 연결 리스트(linked list), 트리(tree), 그래프(graph) 같은 동적 자료구조를 구현하는 데에도 필수적입니다. 각 노드가 다음 노드의 주소를 포인터로 저장함으로써, 실행 중에 크기를 유연하게 늘리고 줄일 수 있는 구조를 만들 수 있습니다.
마무리
지금까지 포인터를 활용한 대표적인 네 가지 방법, 즉 배열 요소 접근, 동적 메모리 할당, 참조에 의한 인자 전달, 자료구조 구현을 알아보았습니다. 포인터는 처음에는 다소 어렵게 느껴질 수 있지만, 메모리의 동작 원리를 이해하고 나면 C/C++ 프로그래밍의 생산성과 효율을 크게 높여주는 필수 도구입니다.