메모리를 '서랍장'으로 이해하기
C++ 학습 과정에서 가장 헷갈리는 개념 중 하나는 바로 포인터와 참조입니다. 이를 쉽게 이해하기 위해 메모리를 일련의 번호가 붙은 서랍장이라고 상상해보세요. 각 변수는 특정 서랍에 저장되며, 그 서랍의 번호가 바로 주소입니다.
기호 '*'와 '&'의 이중적 역할
이 두 기호는 문맥에 따라 완전히 다른 의미를 가지므로 주의가 필요합니다.
'*' - 별표
- 선언 시 (타입 옆): 포인터 변수임을 선언합니다. 예:
double* ptr;→ double형 데이터의 주소를 담을 수 있는 포인터. - 실행 시 (변수 앞): 역참조(dereferencing)로 해석됩니다. 즉, 포인터가 가리키는 주소 안의 값을 의미합니다. 예:
*ptr = 3.14;→ ptr이 가리키는 위치에 3.14 저장.
'&' - 앰퍼샌드
- 실행 시 (변수 앞): 해당 변수의 메모리 주소를 반환합니다. 예:
&num→ 변수 num의 주소값. - 선언 시 (타입 옆): 참조(reference)를 선언합니다. 즉, 기존 변수의 별명(alias)을 만듭니다. 예:
int& ref = num;→ ref는 num의 또 다른 이름.
함수 인자 전달 방식 비교
함수에 데이터를 전달하는 세 가지 방법을 살펴보고, 각각의 동작 원리를 명확히 구분해보겠습니다.
1. 값 전달 (Pass by Value)
void increase(int val) {
val += 5;
}
이 방식은 원본의 사본을 함수에 전달합니다. 함수 내부에서 val을 수정해도 실제 인자는 전혀 영향을 받지 않습니다. 대신 복사 과정에서 성능 오버헤드가 발생할 수 있습니다.
2. 포인터 전달 (Pass by Pointer)
void increase(int* ptr) {
*ptr += 5;
}
여기서는 변수의 주소를 전달하므로, 함수는 포인터를 통해 원본 데이터에 직접 접근할 수 있습니다. 호출 시에는 increase(&original);처럼 주소 연산자를 사용해야 합니다. 유연성은 높지만, NULL 체크와 역참조 실수 등의 위험이 따릅니다.
3. 참조 전달 (Pass by Reference)
void increase(int& ref) {
ref += 5;
}
이 방식은 마치 원본 변수에 별명을 붙이는 것과 같습니다. 함수 내에서 ref를 수정하면 호출자 측의 원본도 함께 변경됩니다. 문법은 값 전달처럼 간단하면서도, 포인터 전달과 동일한 효율성을 제공합니다.
비교 요약표
| 방식 | 예시 | 원본 변경 여부 | 비유 |
|---|---|---|---|
| 값 전달 | func(int x) |
아니요 | 문서의 복사본 전달 |
| 포인터 전달 | func(int* p) |
예 (*p 수정 시) |
서랍 열쇠 전달 |
| 참조 전달 | func(int& r) |
예 | 서랍에 별칭 부여 |
실습 코드: 주소 출력을 통한 차이 관찰
다음 코드를 실행하면 각 전달 방식에서 변수의 주소가 어떻게 나타나는지 확인할 수 있습니다.
#include <iostream>
using namespace std;
void byValue(int x) {
cout << "[값 전달] x의 주소: " << &x << ", 값: " << x << endl;
}
void byPointer(int* p) {
cout << "[포인터 전달] p가 가리키는 주소: " << p << ", 값: " << *p << endl;
}
void byReference(int& r) {
cout << "[참조 전달] r의 주소: " << &r << ", 값: " << r << endl;
}
int main() {
int data = 42;
cout << "--- 원본 data의 주소: " << &data << " ---" << endl << endl;
byValue(data);
byPointer(&data);
byReference(data);
return 0;
}
결과 해석
- byValue: 출력된 주소는
main의data와 다릅니다 — 별도의 스택 프레임에 생성된 복사본입니다. - byPointer:
p가 가리키는 주소는data의 주소와 동일합니다 — 원본에 대한 직접 접근. - byReference:
r의 주소 역시data와 동일합니다 — 단순히 같은 객체에 대한 다른 이름일 뿐입니다.
혼동 방지를 위한 팁
기호의 역할을 판단할 때는 항상 문맥을 보세요:
int& r = a;→ 타입 옆의&는 참조 선언.ptr = &a;→ 타입 없이 사용된&는 주소 추출.int* p;→ 타입 옆의*는 포인터 선언.*p = 10;→ 단독 사용된*는 역참조.