저번 포스팅에서는 &연산자와 * 연산자 중 &연산자에 대해서 알아보았고,
우리의 첫 번째 의문인 주소값은 모양이나 생김새로 보아 정수형태인데 왜 굳이 포인터 변수를 선언해서 저장하는지 알아보았다.
이번 포스팅에서는 포인터의 * 연산자와 다양한 포인터 형이 존재하는 이유. 즉, type(자료형) 변수는 type* 포인터 변수에 저장하는지 한번 알아보겠다.
1. * 연산자
아래의 코드를 한번 봐보자
#include<stdio.h>
int main(void) {
int num1 = 100, num2 = 200;
int* pnum1;
pnum1 = &num1; //pnum1 이 num1을 가리킨다
(*pnum1) += 30; //num1 += 30 과 동일
printf("*pnum1 : %d, num1 : %d\n", *pnum1, num1);
pnum1 = &num2; //pnum2 이 num2를 가리킨다
(*pnum1) -= 50; //num2 -= 50 과 동일
printf("*pnum1 : %d, num2 : %d\n", *pnum1, num2);
return 0;
}
4~5번째 라인을 보면 int형 포인터 변수 pnum을 선언후 pnum1 이 num1을 가리키고 있다.
그러고 나서 * 연산자를 사용하여 num1에 값에 접근하여 num1에 저장된 값을 pnum이라는 포인터 변수를 이용하여 바꾸고 있다.
이를 통해 알 수 있는점은 * 연산자는 포인터가 가리키는 메모리를 참조하는 데 사용할 수 있다는 점이다.
앞선 글에서도 간간히 언급되서 나오긴 했는데 위의 예시 코드로 다시 한번 정리해 보겠다.
포인터 변수 num1을 선언하고 포인터 변수 pnum1을 선언하여 num1의 주소값을 얻는다.그렇게 되면 포인터 변수 pnum1이 변수 num1을 가리키게 된다. 이때 주소값만 얻으면 끝이고 땡인가?얻은 주소값을 토대로 직접 변수에 접근하여 값을 얻거나 바꾸거나 해야 유의미한 포인터 선언이 되지 않겠는가?이때 포인터가 가리키는 메모리를 참조하는데 * 연산자가 사용된다. 앞서 말한 내용들이 위의 코드 3~6번째 라인에서 동작하는 과정이다.
따라서 포인터 변수 pnum1이 num1변수의 주소를 가리키고 있기 때문에 *pnum1 += 30 연산과정을 거치게 되면 pnum1이 가리키는 주소에 있는 값을 수정하는 것과 같다. 즉, pnum1이 가리키는 주소는 num1의 주소이므로 (*pnum1)은 num1의 값을 수정하는 것과 동일하다. 이렇게 되면 printf("*pnum1 : % d, num1 : % d\n", *pnum1, num1); 의 출력결과로 둘 다 같은 값이 나오게 될 것이다.
또한 포인터 변수 pnum1 이 참조하는 대상을 변경할 수 있다.
pnum1 = &num2;
원래 pnum1이 참조하는 대상은 num1이었지만 이제 num2이다. 이렇게 되면 pnum1은 num1과의 참조관계를 끊고 이제 num2를 참조하게 된다.
위와 같은 상황을 그림으로 표현하면 아래와 같다.
포인터변수 pnum1이 num2를 참조한 이후의 코드는 위에서 설명한 것과 똑같은 논리이므로 패스하겠다.
2. 다양한 포인터 형이 존재하는 이유
다음 코드를 한번 살펴보자.
#include<stdio.h>
int main(void){
int num1 = 100;
int ptr = &num1;
....
}
num1이라는 int형 변수를 하나 선언하고 ptr변수를 포인터 변수로 선언하지 않고 그냥 int형 변수로 선언한 다음 ptr에 num1의 주소값을 저장했다고 해보자.
이때 여기서 ptr에는 int형이라는 정보와 변수 num1을 가리키고 있긴 하다.
하지만 ptr에는 현재 주소값만 저장된 상태이므로 ptr이 가리키고 있는 대상이 double형인지, char인지, int인지 모른다.
저번 포스팅에서 설명했듯이 주소값만 가지고는 내가 가리키는 대상에 대한 정보를 알 수 없다.
그렇다면 당연히 포인터 변수를 선언해서 주소값을 저장해야겠지 않는가?
포인터 변수를 선언해서 주소값을 저장하면 어떤 과정을 통해 이루어지는지 살펴보자.
#include<stdio.h>
int main(void){
int num1 = 100;
int* ptr = &num1;
}
사실상 위의 코드에서 * 연산자 하나만 추가했을 뿐이다. 그렇지만 내용은 조금 달라진다.
이렇게 되면 ptr이라는 포인터 변수가 num1을 가리키는 것까지는 똑같다.하지만, ptr이라는 포인터 변수가 가리키는 변수의 대상이 int형이라는 정보가 담겨있다.
이렇게 되면 ptr이 가리키는 메모리 공간은 int형 변수라고 판단 후 * 연산을 통해 값을 저장하거나 수정할 때 int형으로 저장하게 된다.
이제 우리들의 두 번째 의문 type형 변수는 type* 포인터 변수에 저장되는지 감이 잡히는가?
그렇다 내가 포인터 변수를 선언하고 그 포인터 변수를 통해 가리키는 데이터의 타입을 명시적으로 하기 위해서이다.
int * ptr = &num1; 과 같이 ptr이 가리키는 메모리 공간을 int형 변수임을 명시적으로 표현하면 컴파일러는 ptr 이 가리키는 주소값에 있는 데이터를 int 형으로 해석하여 연산하게 된다.(이는 double, char, short 등도 마찬가지이다.)
따라서 포인터 연산을 통해 값을 지정하거나 접근할 때 올바른 데이터 타입을 사용할 수 있어서 예상된 동작을 수행할 수 있게 된다.
다양한 포인터 형의 존재 이유에 대해서 정리해보겠다.
포인터 형은 메모리 공간을 참조하는 방법의 힌트가 된다. 다양한 포인터 형을 정의한 이유는 * 연산을 통한 메모리의 접근기준을 마련하기 위함이다.
int형 변수로 * 연산을 통해 메모리에 접근할 시 4 바이트 메모리 공간에 부호 있는 정수의 형태로 데이터를 읽고 쓴다.
double형 변수로 * 연산을 통해 메모리에 접근할 시 8 바이트 메모리 공간에 부호 있는 실수의 형태로 데이터를 읽고 쓴다.
다음과 같은 코드를 한번 봐보자
#include<stdio.h>
int main(void) {
double num = 3.14;
int* pnum = # //형 불일치 그러나 컴파일은 OK!
printf("%d", *pnum);
....
}
pnum이 가리키는 것은 double형 변수인데, pnum이 int형 포인터 변수이므로 int형 데이처럼 해석한다.
우리가 가진 두 가지 의문점
1. 주소값은 정수임에도 불구하고 왜 포인터 변수를 선언해서 담는가?
2. type형 변수의 주소값은 왜 type형 포인터 변수에 저장을 하는가?
이 두 가지 의문점에 대해 총정리를 해보겠다.
1. 포인터 변수를 선언해서 주소값을 저장하는 이유?
-> 변수에는 내가 담고 있는 주소값의 메모리 공간에 대한 정보가 부족하다.(변수가 int형인지, double형인지 어떤 자료형인지 알 수 없다)
그래서 포인터 변수를 선언하고 주소값을 저장한다.
2. type형 변수의 주소값을 왜 type형 포인터 변수에 저장을 하는가?
-> 포인터 연산을 통해 값을 지정하거나 접근할 때 올바른 데이터 타입을 사용함으로써 예상된 동작을 수행할 수 있다.
이로서 오늘은 * 연산자와 type형 변수의 주소값을 왜 type형 포인터 변수에 저장하는지에 대해서 알아보았다.
포인터에 대해 조금 원론적인 이야기를 해서 지루하게 느껴질 수도 있었겠지만 정리하는데 도움이 되었길 바란다:)
다음에는 포인터와 배열이라는 주제를 들고 돌아오도록 하겠다
'C언어' 카테고리의 다른 글
C언어 - 포인터와 함수에 대한 이해 (0) | 2023.07.21 |
---|---|
C언어 - 상수 형태의 문자열을 가리키는 포인터 (0) | 2023.07.20 |
C언어 - 포인터와 배열 (0) | 2023.07.12 |
C언어 - 포인터의 이해(2) (0) | 2023.07.07 |
C언어 - 포인터의 이해(1) (0) | 2023.07.04 |