본문 목표
C언어의 핵심 중 하나가 '포인터' 이다.
포인터를 사용할 줄 알아야 C언어를 통해서 개발을 수월하게 할 수 있다.
단순히 변수를 사용하면 해당 소스 파일에서만 사용이 가능한데,
다른 외부 소스 파일에서 해당 변수 값을 참조할 때 사용하는게 '포인터'이다.
메모리 주소에 대해서 이해하여 포인터가 무엇인지 이해하고 사용하는 방법을 알아보자.
개념 정리
포인터는 영어로 'pointer' 이다.
뜻을 생각해보면, '가르키는 것' 이라고 할 수 있겠다.
실제로 C언어에서의 포인터는 해석 그대로의 개념이다.
가르키는 것인데, 무엇을 가르키냐면
바로 '메모리 주소 값' 이다.
컴퓨터의 RAM 이라는 부분을 메모리라고 하는데,
그 메모리에 우리가 사용하고자 하는 변수들이 저장 되는 것이다.
우리가 char A 라고 'A' 라는 변수를 생성하면 메모리에 일정 크기가 할당되어진다.
A = 10; 이라고 값을 저장시키면, 해당 메모리 주소에 메모리 값이 10 이 저장된다.
정확한 표현은 아니다. 이해하기 쉽게 만들어본 이미지다.
char 는 8bit 로 즉 1byte의 크기를 가지고 있다.
메모리 비트가 8개 있다는 뜻이다.
char A 라고 변수를 선언하면 컴퓨터는 메모리 비트 8개를 묶어서 메모리 주소를 지정해버린다.
그 메모리 주소는 프로그램이 실행될 때마다 값이 바뀐다.
우리가 강의실에서 먼저온 사람이 맘에 드는 자리에 앉듯이 매번 랜덤이라고 보면 된다.
메모리 주소가 지정되었으면, 해당 비트에 값을 주어서 사용자가 부여한 변수 값을 저장한다.
예를 들어, 10을 변수 값에 저장했다면, 비트로는 0000 1010 이 저장된 것이다.
위의 내용으로 메모리 주소와 메모리 값에 대해서 이해를 해야 한다.
이해를 못했으면 계속 이해해보려 해야된다.
이제 본격적으로 포인터에 대해서 설명해보겠다.
포인터는 다시말해서 메모리 주소를 가르키는 것이다.
포인터 사용방법부터 알아보자.
변수를 선언할 때, * <- 이런 별표 문자를 붙이면 된다.
int *ptr;
int* ptr;
int * ptr;
위의 세가지 경우는 다 똑같은 뜻이다. *는 자료형 뒤에 붙여도 되고, 변수 앞에 붙여도 된다.
즉, 위치가 상관이 없다. 변수 앞에만 쓰면 된다.
위와같이 변수 앞에 * 를 붙임으로써, 포인터를 만들어 준 것이다.
그러면 저 포인터는 선언만 했지, 아직 주소값을 배정해주지 않았다.
주소값을 이해하려면 & <- 주소 연산자를 이해해야 한다.
& 뒤에 변수 이름을 적으면, 해당 변수 이름의 메모리 주소값을 나타나게 된다.
int A = 10;
printf(" %p \n", &A);
위와 같이 코드를 작성하고 실행하면,
위와 같이 출력이 된다.
출력된 값이 변수 A가 메모리에서 차지하고 있는 부분의 주소인 것이다.
따라서 &는 주소를 가리키는 연산자인 것이다.
그리고 주소를 출력하려면 출력연산자는 %p 를 사용한다.
여기서 p는 pointer 에서 따온 p 이다.
이제 메모리 주소를 확인하는 방법까지 다뤘으니,
본격적으로 포인터에 메모리 주소를 배정하는 것을 알아보자.
int *Ptr;
int A = 10;
Ptr = &A;
위의 코드처럼 사용하면, 포인터 변수 Ptr에 변수A의 메모리 주소를 저장하게 되는 것이다.
첫 줄부터 설명하자면
포인터 변수 Ptr을 선언하였고,
변수 A를 선언하고 값을 10으로 초기화 하였다.
포인터 변수 Ptr 에 변수 A의 메모리 주소를 저장하였다.
라는 해석이 된다.
Ptr에는 10이 저장된 것이 아니라 변수 A의 메모리 주소가 저장된 것이다.
그러니깐 Ptr 의 값은 현재 0000 0024 인 것이다.
&A 와 Ptr 은 같은 것이 되었다.
여기까지 이해했으면, 포인터의 절반을 이해하게 된 것이다.
이제 포인터가 가리키는 값을 다뤄보겠다.
포인터가 주소값인 것까지는 이해를 했다.
그러면 이제 반대로 포인터를 통해서 해당 메모리 주소에 저장된 값은 어떻게 확인할 수 있을까 알아보자.
printf(" %d \n", *Ptr);
위와 같이 코드를 작성하고 실행하면
변수 A의 값 그대로인 10이 출력됨을 확인할 수 있다.
Ptr 변수 이름 앞에 * 별표를 붙여주면 된다.
그러니깐 초기에 별표를 붙이는 것은 포인터를 선언하겠다는 뜻이고,
선언된 이후에는 지정된 메모리 주소에 해당하는 메모리 값을 뜻하게 된다.
정리
(포인터 변수) = 메모리 주소
& : 주소 연산자
&(변수) = (변수)에 할당 된 메모리 주소
*(포인터 변수) = 포인터가 가르키는 '메모리 주소'의 '메모리 값'
정말 이해하기 쉽게 우리가 살아가는 세상과
지역변수, 전역변수, 메모리 주소 를 대응시켜 설명해보겠다.
저번에도 설명했었지만,
집에서 가족끼리 아빠, 엄마, 공주, 왕자 등의 애칭으로 부르는 것은
해당 지역(집)에서만 선언(약속)된 것으로, 그 지역에서만 유효하다.
=> 애칭, 별명은 지역변수 같은 개념이다.
만약 그 애칭을 다른 지역(집)에서 듣게 된다면, 그게 누구를 가르키는건지 알 수가 없다.
다른 지역(집)에서도 알게끔 하려면 사회적으로 약속한 본명을 써야 한다.
그래야 다른 지역(집)에서 누구를 부르는지 알 수 있다.
본명 - 전역 변수
별명 - 지역 변수 의 개념이라고 이해하고 넘어가자.
그런데 사실 변수들 간의 연산을 할 때에는
항상 메모리 값을 참조해야 하는데, 그러려면 메모리 주소를 알아야한다.
그런 메모리 주소를 일상과 연관시켜보았다.
같은 아파트에 살고 있는 친구끼리 집주소를 물어보면, 호수만 알려줘도 충분하다.
코드에서도 마찬가지다. { } 중괄호로 묶여있는 상황에서 선언되어 있는 지역변수들은 자기들끼리 대충 말해도 다 알아듣는다.
지역변수 끼리 연산을 할 때에는 위와 같이 " ~시 ~구 ~동 ~아파트 ~동 "을 제외해버리고 주소값을 참조하므로 연산속도도 빠르게 되는 것이다.
대신 다른 지역이든, 같은 단지라도 아파트 동이 달라버리면 그 주소를 알 수가 없다.
다른 아파트 사는 친구가 집주소를 물어봤을 때, 호수만 말해주면 알아들을 수가 없다.
이처럼 다른 지역에 있는 변수와 연산을 할 때에는 보다 명확한 주소가 필요해진다.
그나마 같은 아파트 단지(지역)이니깐 동과 호수만 말해도 금방 알아듣게 된다.
그런데, 이제 "801동 502호" 만 봤을 때, 전국적으로 얼마나 해당 주소가 있을까?
몇십만~ 몇백만 까지도 있을 것이다.
부산에 사는 친구가 빛유라한테 집주소를 물어봤을때 "801동 502호" 라고만 말하면 알아들을 수 있을까?
더 붙여서 "길음뉴타운 801동 502호" 라고 말해도 못알아 듣게 된다.
확실하게 "서울특별시 성북구 길음동 길음뉴타운 801동 502호" 라고 말해야 알아들을 수 있다.
이걸 그림으로 표현하자면
다른 소스 코드에서 main.c에 있는 변수의 값을 알고 싶으면
주소를 풀네임으로 불러야 한다는 뜻이다.
부산에 있는 홍길동(sub.c 에서 선언된 변수 또는 함수)이 서울에 있는 빛유라(main.c 에서 선언된 변수 또는 함수)를 호출하려면 주소의 풀네임(메모리 주소)를 이용해야 되는 것이다.
프로젝트가 거대해질수록 가독성을 높이기 위해서 소스코드를 여러개로 저장하게 된다.
문제는 그냥 변수를 선언했다면, 다른 소스코드에서는 그 소스코드의 변수에 접근할 수가 없다.
이러한 문제를 해결해주는 것이 '포인터' 인 것이다.
컴퓨터의 메모리 주소(절대 값)를 참조해버리는 것이다.
원리는 이정도로만 이해해두자.
코드
<1> 포인터 변수 선언과 메모리 주소 저장, 및 해당 메모리 값 출력
#include <stdio.h>
void main()
{
int* Ptr;
int A;
Ptr = &A;
A = 10;
printf("\n\n\n\n");
printf("\t %d\n", *Ptr);
return;
}
<2> 포인터 값에 새로운 값을 저장하고, 참조하는 변수의 값 변화 살펴보기
#include <stdio.h>
void main()
{
int* Ptr;
int A;
Ptr = &A;
printf("\n\n\n\n");
A = 10;
printf(" A 초기값 : %d \n", A);
*Ptr = 15; // 할당된 메모리 주소에 해당하는 메모리 값에 15를 저장
printf(" 포인터값에 값을 저장해서 메모리값 바꾼 값 : %d\n", *Ptr);
printf(" 변수 A에 저장된 값 : %d \n", A);
return;
}
<3> 사용자 정의 함수를 만들고, 주소로 변수값 입력받기
#include <stdio.h>
void double_f(int* input, int* output)
{
*output = *input * 2;
return;
}
void main()
{
int A = 10;
int result;
double_f(&A, &result); //사용자정의 함수에서 주소값을 입력받으므로 변수의 주소를 입력해야함.
printf("\n\n\n\n");
printf("\t result 의 값 : %d\n", result);
return;
}
실행 결과 및 해석
<1> 포인터 변수 선언과 메모리 주소 저장, 및 해당 메모리 값 출력
변수 A 를 선언하고 10으로 초기화 하였다.
포인터 변수 Ptr에 변수 A의 주소(&A)를 저장하였다.
포인터 변수 Ptr이 가리키는 주소의 메모리 값을 cmd 로 출력하였더니
변수 A의 값인 10이 출력되었다.
<2> 포인터 값에 새로운 값을 저장하고, 참조하는 변수의 값 변화 살펴보기
변수 A 를 선언하고 10으로 초기화 하였고,
포인터 변수 Ptr을 선언하면서 변수 A의 메모리 주소를 가리키게 하였다.
변수 A를 건드리지 않고, 포인터 변수 Ptr이 가리키는 값 ( *Ptr )에 새로운 숫자 15를 저장시켰다.
그리고 변수 A의 값을 cmd로 출력하였더니 15가 출력되었다.
<3> 사용자 정의 함수를 만들고, 주소로 변수값 입력받기
입력받은 값을 2배로 반환해주는 사용자 정의함수를 선언하였다.
이 때, 입력받는 두 값은 메모리 주소로 받는다.
main( ) 에서 변수 A와 result를 선언했고, 변수 A에만 값 10을 저장하였다.
그리고 사용자 정의 함수에 두 값을 차례로 집어넣었다.
사용자 정의함수에서는 A와 result의 메모리 주소값을 참조해서 A값에 2를 곱한 값을 result의 메모리 값에 저장한다.
cmd에 result 값을 출력하였더니 A 값의 2배인 20이 출력되었다.
결론
포인터는 시스템 메모리 주소를 가리키는 것이다.
포인터를 선언했으면, 변수의 주소를 대응되게 해야한다.
(이때, 포인터 자료형과 대응시키려는 변수의 자료형은 같아야한다.)
변수의 주소는 변수이름 앞에 & 를 붙이면 된다. ( 메모리 주소 = &변수 )
포인터 변수가 가리키는 메모리 주소의 값은 포인터 변수이름 앞에 *를 붙이면 된다.
( 메모리 값 = *포인터변수 )
포인터를 제대로 사용하려면 메모리 주소와 메모리 값을 정확히 이해해야한다.
포인터를 사용하면 소스파일이 여러개라도 서로 참조할 수 있다.
모든 자료형에 대해서 포인터로 만들 수 있다.
도움이 되었거나, 문제가 있는 경우 댓글로 알려주세요~!
감사의 댓글은 작성자에게 큰 힘이 됩니다 ^^
'코딩 > C 언어' 카테고리의 다른 글
C언어 기초 - 쓰레드 이해하고 사용하는 방법 (4) | 2020.07.09 |
---|---|
C언어 기초 - 헤더파일 만드는 방법과 사용하는 방법 (0) | 2020.07.08 |
C언어 기초 - 사용자 정의함수 만들기 (0) | 2020.07.06 |
C 언어 기초 - 반복문 for 이해하기 (0) | 2020.07.06 |
C언어 기초 - 반복문 while 이해하기 (0) | 2020.07.05 |
댓글