포인터란 무엇인가?
포인터(pointer)는 다른 변수의 메모리 주소를 저장하는 특수한 변수입니다. C 언어에서 포인터를 제대로 이해하면 메모리를 직접 제어하는 강력하고 유연한 프로그래밍이 가능해집니다.
포인터의 주요 특징
- 메모리 공간을 절약할 수 있습니다.
- 메모리 위치에 직접 접근하기 때문에 실행 속도가 빠릅니다.
- 동적 메모리 할당과 해제를 통해 메모리를 효율적으로 관리할 수 있습니다.
- 연결 리스트, 트리 등 다양한 자료구조를 구현할 때 필수적으로 활용됩니다.
포인터의 선언, 초기화, 접근
먼저 다음 문장을 살펴보겠습니다.
int qty = 179;
변수는 메모리상에서 이름, 주소, 값의 형태로 표현됩니다.
포인터 선언
아래 문장에서 'p'는 다른 정수형(int) 변수의 주소를 저장하는 포인터 변수임을 의미합니다.
int *p;
포인터 초기화
포인터 변수는 주소 연산자(&)를 사용하여 초기화합니다.
int qty = 175;
int *p;
p = &qty;
이제 포인터 p에는 변수 qty의 메모리 주소가 저장됩니다.
포인터를 통한 변수 값 접근
포인터가 가리키는 변수의 값을 읽으려면 간접 참조 연산자(*)를 사용합니다. 여기서 '*'는 "해당 주소에 저장된 값"을 의미한다고 생각하면 됩니다.
따라서 아래 두 문장은 서로 동일한 결과를 가져옵니다.
p = &qty;
n = *p; /* n = qty와 같음 */
포인터와 1차원 배열의 관계
- 컴파일러는 배열의 모든 요소에 대해 연속적인 메모리 공간을 할당합니다.
- 기준 주소(base address)란 배열의 첫 번째 요소, 즉 인덱스 0의 주소를 의미합니다.
예:int a[5] = {10, 20, 30, 40, 50};
배열 요소의 메모리 배치
다섯 개의 요소는 메모리에 순차적으로 저장됩니다.
'p'가 정수형 포인터로 선언되어 있다면, 배열 'a'는 다음과 같이 포인터에 대입할 수 있습니다.
p = a;
/* 또는 */
p = &a[0];
배열 이름 자체가 첫 번째 요소의 주소를 의미하기 때문에 두 표현은 같은 결과를 냅니다.
포인터 연산과 스케일 팩터
p++를 사용하면 한 요소에서 다음 요소로 이동하며 배열의 모든 값을 접근할 수 있습니다.- 포인터가 증가할 때 그 값은 포인터가 가리키는 자료형의 크기만큼 증가합니다. 이 크기를 스케일 팩터(scale factor)라고 부릅니다. 예를 들어 int형은 일반적으로 4바이트이므로 포인터가 1 증가할 때마다 주소는 4씩 늘어납니다.
'p'와 배열 'a'의 관계를 주소 기준으로 정리하면 다음과 같습니다.
p = &a[0] = 1000
p+1 = &a[1] = 1004
p+2 = &a[2] = 1008
p+3 = &a[3] = 1012
p+4 = &a[4] = 1016
요소 주소의 계산 원리
배열 요소의 주소는 인덱스와 해당 자료형의 스케일 팩터를 이용해 계산할 수 있습니다.
a[3]의 주소 = 기준 주소 + (3 × int의 스케일 팩터)
= 1000 + (3 × 4)
= 1000 + 12
= 1012
- 배열 인덱싱 대신 포인터를 사용하여 배열 요소에 접근할 수 있습니다.
*(p+3)은a[3]의 값을 나타냅니다.
즉, 인덱스 표기법과 포인터 표기법 사이에는 다음과 같은 일반적인 관계가 성립합니다.
a[i] = *(p+i)
예제 프로그램
다음은 포인터와 1차원 배열을 함께 활용한 C 프로그램입니다. 사용자로부터 5개의 정수를 입력받아, 포인터 연산으로 배열 요소를 출력합니다.
#include <stdio.h>
int main(void) {
int a[5];
int *p, i;
printf("5개의 정수를 입력하세요 : ");
for (i = 0; i < 5; i++)
scanf("%d", &a[i]);
p = &a[0];
printf("배열의 요소들 : ");
for (i = 0; i < 5; i++)
printf("%d ", *(p + i));
return 0;
}
실행 결과
위 프로그램을 실행하면 다음과 같은 결과가 출력됩니다.
5개의 정수를 입력하세요 : 10 20 30 40 50
배열의 요소들 : 10 20 30 40 50
이처럼 포인터를 활용하면 배열의 요소를 인덱스 없이도 자유롭게 탐색할 수 있으며, 이는 C 언어에서 메모리를 효율적으로 다루는 핵심 개념이 됩니다.