C 언어는 메모리에 접근해서 우리가 원하는 방식으로 데이터를 사용할 수 있게 해주는 언어입니다. 변수를 사용해 메모리에 접근을 하려면 변수 선언으로 메모리에 공간을 확보하고, 그곳을 데이터를 넣고 꺼내 쓰는 공간으로 사용합니다. 포인터
변수를 사용하는 가장 쉬운 방법은 이름을 쓰는 것이고, 이 변수명은 메모리 공간을 식별하는 역할을 합니다. 그러나 선언된 블록({ }), 즉 함수 내부에서만 그 변수를 사용할 수 있고 같은 변수명을 사용한다 하더라도 블록이나 함수가 다르면 별도의 저장 공간을 확보하므로 전혀 다른 변수로 사용됩니다.
이 때 변수 대신 메모리의 주소 값을 이용해 메모리에 접근하는 포인터를 사용하면 사용 범위를 벗어난 경우에도 데이터를 공유할 수 있습니다. 하지만 포인터를 사용하려면 추가적인 변수 선언이 필요하고 주소 연산, 간접 참조 연산 등 각종 연산을 수행해야 합니다. 굳이 힘들게 포인터를 일부러 즐겨 사용할 필요는 없습니다. 그러나 임베디드 프로그래밍*을 할 때 메모리에 직접 접근하는 경우나 동적 할당한 메모리를 사용하는 경우에는 포인터가 반드시 필요합니다.
🤔임베디드 프로그래밍이란? 임베디드 프로그래밍은 임베디드 시스템(Embedded System : 내장형 시스템)을 제어하기 위한 프로그램으로, 오늘날 만들어지는 거의 모든 생활 기기에서 특정 기능을 제어하기 위해 구현됩니다. 예를 들어 정수기에서 정수, 냉수 등을 구분해 물이 나오게 한다든가, 자동차, 냉장고, 전기밥솥, 스마트워치 등에도 각 기능을 담당하는 하드웨어가 있는데, 이 하드웨어를 제어하는 소프트웨어를 만드는 일을 임베디드 프로그래밍이라고 합니다. |
✅포인터 없이 두 변수의 값을 바꾸는 함수는 불가능할까?
굳이 힘들게 포인터를 즐겨 쓸 필요가 없다면, 포인터 없이 두 변수의 값을 바꿀 수는 없을까요? 우선 swap 함수에서 main 함수의 a, b를 이름으로 직접 사용하는 방법을 생각해 보겠습니다.
📌다른 함수의 변수 사용하기
#include <stdio.h>
void swap(void); // 두 변수의 값을 바꾸는 함수 선언
int main(void){
int a = 10, b = 20; // 변수 선언과 초기화
swap( ); // 인수 없이 함수 호출
printf(“a:%d, b:%d\n”, a, b); // 변수 a, b 출력
return 0;
}
void swap(void){ // 인수가 없으므로 매개변수도 없음
int temp; // 교환을 위한 변수
temp = a; // temp에 main 함수의 a 값 저장
a = b; // main 함수의 a에 main 함수의 b 값 저장
b = temp; // main 함수의 b에 temp 값 저장
}
이 예제는 컴파일 과정에서 다음과 같은 에러 메시지를 볼 수 있습니다.
error C2065: ‘a’ : 선언되지 않은 식별자입니다. error C2065: ‘a’ : 선언되지 않은 식별자입니다. error C2065: ‘b’ : 선언되지 않은 식별자입니다. error C2065: ‘b’ : 선언되지 않은 식별자입니다. |
함수 안에 선언된 변수명은 사용 범위가 함수 내부로 제한되므로 main 함수에 있는 변수 a, b는 다른 함수인 swap 함수에서 그 이름을 사용할 수 없습니다. 좀 더 정확히 설명하면 변수가 선언된 시점부터 선언된 블록 끝까지로 제한됩니다. 즉, 6행에서 선언된 변수 a, b의 이름은 6행에서 12행까지만 사용할 수 있습니다. 변수의 사용 범위로 인해 이 방법은 컴파일 단계에서 문제가 발생합니다.
main 함수에서 a, b의 값을 swap 함수에 인수로 주면 어떨까요?
📌변수의 값을 인수로 주는 경우
#include <stdio.h>
void swap(int x, int y); // 두 변수의 값을 바꾸는 함수 선언
int main(void){
int a = 10, b = 20; // 변수 선언과 초기화
swap(a, b); // a, b의 값을 복사해서 전달
printf(“a:%d, b:%d\n”, a, b); // 변수 a, b 출력
return 0;
}
void swap(int x, int y){ // 인수 a, b의 값을 x, y에 복사해서 저장
int temp; // 교환을 위한 변수
temp = x; // temp에 x 값 저장
x = y; // x에 y 값 저장
y = temp; // y에 temp 값 저장
}
실행 결과 a:10, b:20 |
8행에서 함수를 호출할 때 main 함수의 변수 a, b의 값이 복사되어 14행의 매개변수 x, y에 저장됩니다. 결국 swap 함수 안에서는 a, b의 복사본을 바꾸므로 main 함수의 두 변수 a, b의 값은 변함이 없게 됩니다. swap 함수에서 매개변수의 이름을 a, b로 사용해도 결과는 같습니다. 이름이 같아도 함수가 다르면 메모리에 별도의 저장 공간을 확보하므로 전혀 다른 변수로 사용됩니다.
마지막으로 swap 함수에서 바꾼 값을 main 함수로 반환하는 방법을 생각할 수 있는데, 함수는 오직 하나의 값만을 반환할 수 있으므로 한 번의 함수 호출을 통해 두 변수의 값을 바꾸는 것은 불가능합니다.
📌포인터 없이 함수로 두 변수의 값을 바꿀 수 없는 이유
① 함수 안에 선언된 변수명은 사용 범위가 함수 내부로 제한되므로 다른 함수에서 해당 변수를 사용할 수 없음
② 이름이 같아도 함수가 다르면 메모리에 별도의 저장 공간을 확보하므로 전혀 다른 변수로 사용됨
③ 함수는 오직 하나의 값만을 반환할 수 있으므로 한 번의 함수 호출을 통해 두 변수의 값을 바꾸는 것은 불가능
✅두 변수의 값을 바꾸며 포인터 이해하기
두 변수의 값을 바꾸는 함수를 통해 포인터의 필요성을 확인해 보겠습니다.
📌포인터를 사용한 두 변수 값의 교환
#include <stdio.h>
void swap(int *pa, int *pb); // 두 변수의 값을 바꾸는 함수의 선언
int main(void){
int a = 10, b = 20; // 변수 선언과 초기화
swap(&a, &b); // a, b의 주소를 인수로 주고 함수 호출
printf(“a:%d, b:%d\n”, a, b); // 변수 a, b 출력
return 0;
}
void swap(int *pa, int *pb){ // 매개변수로 포인터 선언
int temp; // 교환을 위한 임시 변수
temp = *pa; // temp에 pa가 가리키는 변수의 값 저장
*pa = *pb; // pa가 가리키는 변수에 pb가 가리키는 변수의 값 저장
*pb = temp; // pb가 가리키는 변수에 temp 값 저장
}
실행 결과 a:20, b:10 |
이 예제는 두 변수의 값을 swap 함수 호출을 통해 바꿉니다. 먼저 8행의 함수 호출에서 바꿀 변수 a와 b의 주소를 인수로 줍니다.
swap(&a, &b); // 8행. a, b의 주소를 인수로 주고 함수 호출
따라서 14행의 매개변수는 포인터로 선언합니다. 함수가 호출되면 포인터 pa, pb는 main 함수의 변 수 a와 b의 주소를 저장하므로 각각 a와 b를 가리키는 상태가 됩니다.
void swap(int *pa, int *pb){ // 14행. 매개변수로 포인터 선언
이제 swap 함수에서 포인터 pa, pb에 간접 참조 연산을 수행하면 main 함수의 변수 a와 b를 자유롭게 사용할 수 있습니다. 15행의 temp 변수는 main 함수의 a, b를 바꾸는데 사용할 변수로 a, b와 같은 int형으로 선언합니다.
int temp; // 15행. 교환을 위한 임시 변수
17~19행이 두 변수의 값을 바꾸는 과정이며, 각 문장이 실행될 때마다 각 함수에 있는 변수의 값은 다음처럼 변합니다.
temp = *pa; // 17행. temp에 pa가 가리키는 변수의 값 저장
*pa = *pb; // 18행. pa가 가리키는 변수에 pb가 가리키는 변수의 값 저장
*pb = temp; // 19행. pb가 가리키는 변수에 temp 값 저장
교환 작업은 swap 함수 안에서 포인터를 통해 진행되지만, 실제로 바뀌는 값은 main 함수의 변수 a와 b가 됩니다. 결국 swap 함수는 포인터를 통해 main 함수의 변수 a, b를 공유하므로 두 변수를 직접 바꾸는 일이 가능해집니다. 이제 두 변수의 값을 바꾸고 싶을 때는 언제든지 swap 함수를 호출하면 됩니다.
1) 포인터 변수를 선언할 때 *는 기호적인 의미로 사용한다.
2) 포인터 변수를 선언할 때가 아니라, 역참조를 통해 포인터 변수가 가리키는 변수를 사용할 때의 *은 가리키는 놈을 사용하겠다는 의미를 내포한다.
위 콘텐츠는 『혼자 공부하는 C언어(개정판)』을 재구성하여 작성되었습니다.
아무리 공부해도 도무지 이해가 되지 않는 포인터, 앞으로도 계속 모를 순 없잖아요. 이제는 포인터의 개념을 제대로 짚고 싶다면 혼자 공부하는 C언어 한 권과 서현우 저자님의 동영상 강의로 시작해 보세요.
👀 도서 자세히 보기
✍️ 유튜브 강의
📑 네이버 카페(Q&A) 바로가기
Leave A Comment