포인터란?
- -
1. 메모리 주소
포인터에 대해 알아보기 전에 먼저 변수가 어떻게 메모리에 저장되는가에 대해 알아볼 것이다.
int num = 5;
5를 초기값으로 가지는 int형 변수 num을 선언했다.
이것이 메모리에 저장될 때에는 운영체제에서 메모리 공간의 특정 주소에 int 형의 크기인 4바이트만큼 공간을 할당하고 여기에 값 5를 저장한다.
0x00001 | 0x00002 | 0x00003 | 0x00004 |
그 결과, 위의 그림처럼 각각의 바이트 단위로 주소가 정해지고 다음 주소는 1씩 증가한다.
char str[4] = 'ab'
위 코드처럼 선언된 변수는 메모리에 밑의 그림처럼 저장된다.
0x00001 | 0x00002 | 0x00003 | 0x00004 |
a | b |
char 형은 1바이트만큼의 크기를 가지고 4칸이므로 총 4바이트 크기를 할당받는다.
나머지 빈 공간에는 널문자가 들어가며 예시에서 0x~~ 같은 주소를 '번지'라고 한다.
※ 정리
1. 변수는 메모리의 일정 위치를 운영체제로부터 자신의 크기만큼 할당받고 여기에 값을 저장하여 사용한다.
2. 메모리는 각각 바이트 단위로 주소가 있고 바이트가 증가할 때마다 1씩 커진다.
2. 포인터(Pointer)
2.1) 정의
포인터는 주소를 가리킨다. 이름만 포인터일뿐 int, double과 같이 변수라서 포인터 변수라고 부르기도 한다.
변수가 어떤 값을 저장하는 것처럼 포인터는 주소 값을 저장한다.
즉, 포인터는 메모리의 주소값을 저장하는 변수이다.
포인터 변수는 항상 4/8 바이트의 정해진 크기를 가진다. long형과 마찬가지로 32비트 운영체제에서 4바이트, 64비트 운영체제에서 8바이트를 가진다는 뜻이며, 해당 글에서는 64비트 운영체제를 기준으로 설명할 것이다.
2.2) 포인터 변수 맛보기
포인터 변수를 선언하는 예시를 보자
int num = 5;
int *pnum; // 포인터 변수 pnum의 선언
pnum = # // num의 주소 값을 포인터 변수 pnum에 저장
위 코드에서 int *pnum 문장이 포인터 변수를 선언하는 것이다.
구분 | 설명 |
pnum | 포인터 변수 이름 |
int * | int형 변수의 주소 값을 저장하는 포인터 변수의 선언 |
pnum은 int형 변수의 주소 값을 저장할 수 있는 포인터 변수가 된다.
pnum = #
위 코드는 & 연산의 결과로 변수 num의 시작 번지 주소 값이 반환되며, 이를 포인터 변수 pnum에 저장한다.
위 그림에서 서로 할당된 크기가 다른 이유는 변수 num은 위에서 설명했듯이 int형의 크기(4바이트)만큼을 운영 체제로부터 할당받기 때문이고 포인터 pnum은 64비트 운영체제 기준 포인터의 크기가 8바이트이기 때문이다.
2.3) 포인터 변수 선언
구분 | 설명 |
int *pnum1; | int형 변수를 가리키는 pnum1의 선언을 의미 |
double *pnum2; | double형 변수를 가리키는 pnum2의 선언을 의미 |
unsigned int *pnum3; | unsigned int형 변수를 가리키는 pnum3의 선언을 의미 |
포인터 변수는 가리키고자 하는 변수의 자료형에 따라서 선언하는 방법이 달라진다.
type *ptr; | type형 변수의 주소 값을 저장하는 포인터 변수 ptr의 선언 |
포인터 변수 선언의 기본 공식은 위 표와 같다.
int, double과 같이 변수의 선언 및 구분에 사용되는 키워드를 자료형이라고 하듯이 포인터 변수의 선언 및 구분에 사용되는 int *, double * 등을 가리켜 포인터 형(type)이라고 한다. 포인터 변수도 값을 저장하는 변수이기 때문에 포인터 형 역시 자료형의 범주에 포함시키기도 한다.
구분 | 설명 |
type * | type형 포인터 |
type *ptr | type형 포인터 변수 ptr |
※ 정리
1. 포인터는 메모리의 주소 값을 저장하는 변수이다.
2. 운영체제의 환경에 따라 포인터의 크기가 다르다. (32비트는 4바이트, 64비트는 8바이트)
3. & 연산을 통해 변수의 시작번지 주소 값이 반환된다.
4. 반환된 주소 값을 포인터 변수에 저장한다.
5. 포인터 변수는 가리키고자 하는 변수의 자료형에 따라 선언하는 방법이 달라진다.
3. 포인터 연산자
포인터 연산자에는 주소 연산자(&)와 참조 연산자(*) 2가지가 존재한다.
3.1) 주소 연산자(&)
주소 연산자는 변수의 이름 앞에 사용하며 해당 변수의 주소 값을 반환한다.
& 기호는 앰퍼샌드(ampersand)라고 읽으며 번지 연산자라고도 한다.
int num1 = 5;
double *pnum1 = &num1; // 일치하지 않음
double num2 = 5;
int *pnum2 = &num2; // 일치하지 않음
위 코드와 같이 변수의 자료형에 맞지 않는 포인터 변수의 선언은 잘못된 것이다. 컴파일 에러는 일어나지 않지만 포인터 관련 * 연산 시 문제가 발생하기 때문이다.
3.2) 참조 연산자(*)
참조 연산자는 포인터의 이름이나 주소 앞에 사용하며 포인터가 가리키는 주소에 저장된 값을 반환한다.
int num = 5;
int *pnum = # // 포인터 변수 pnum이 변수 num을 가리킴
*pnum = 20; // pnum이 가리키는 변수에 20을 저장
위 코드에서 *pnum=20은 포인터 변수 pnum이 가리키는 메모리 공간인 변수 num에 정수 20을 저장하라는 뜻이다.
포인터 변수의 값을 출력하는 예시를 보자
#include <stdio.h>
int main(void)
{
int num = 5;
int *pnum;
pnum = #
printf("num : %d \n",num);
printf("*pnum : %d \n",*pnum);
printf("pnum : %d \n",pnum);
printf("&num : %d \n",&num);
return 0;
}
주소 연산자(&)는 해당 변수의 주소 값을 반환하므로 pnum은 num의 주소 값이다.
참조 연산자(*)는 포인터가 가리키는 주소의 저장된 값을 반환하므로 *pnum은 num에 저장된 값이다.
그림을 보면서 다시 설명하자면 pnum은 num의 주소 값을 가지고 있고 *pnum을 출력하면 자신이 가리키고 있는 주소의 저장된 값이 나오는 것이다. 사실상 *pnum은 포인터 변수 pnum이 가리키는 변수 num을 의미한다. 따라서 참조 연산자(*)를 이용해 변수 num의 값을 바꿀 수 있다.
#include <stdio.h>
int main(void)
{
int num1 = 100, num2 = 100;
int *pnum;
pnum = &num1; // 포인터 pnum이 num1을 가리킴
(*pnum) += 30; // num1+=30; 과 동일
pnum = &num2; // 포인터 pnum이 num2를 가리킴
(*pnum) -= 30; // num2-=30; 과 동일
printf("num1 : %d, num2 : %d \n", num1, num2);
return 0;
}
위 코드는 참조 연산자(*)를 이용해 변수의 값을 바꾸는 코드이다.
※ 정리
1. 포인터 연산자는 주소 연산자(&), 참조 연산자(*)가 있다.
2. 주소 연산자는 변수의 이름 앞에 사용하며 해당 변수의 주소 값을 반환한다.
3. 참조 연산자는 포인터의 이름이나 주소 앞에 사용하며 포인터가 가리키는 주소에 저장된 값을 반환한다.
4. 참조 연산자를 통해 변수의 값을 바꿀 수 있다.
4. 포인터 변수 초기화 방법
포인터 변수도 똑같은 변수라고 설명했다. int, double과 같은 변수처럼 선언만 하고 초기화하지 않으면 포인터 변수는 쓰레기 값으로 초기화된다. 그렇다고 아무 값이나 대입하면 치명적인 결과로 이어질 수 있다. 예를 들어, *pnum에 100을 대입했을 때 주소 100번지에 무엇이 있을지 모른다. 만일 100번지가 메모리 공간에서 매우 중요한 위치였다면 시스템에 치명적인 문제를 일으킬 수 있다. 따라서 포인터 값을 특정해서 초기화할 수 없을 때는 0이나 NULL로 초기화해줘야 한다.
#include <stdio.h>
int main(void)
{
int *ptr1 = 0;
int *ptr2 = NULL;
printf("%d \n",ptr1);
printf("%d \n",ptr2);
return 0;
}
NULL 포인터 NULL은 숫자 0을 의미한다. 0은 0번지를 뜻하는 것이 아니라 아무것도 가리키지 않는다는 의미로 해석된다.
※ 정리
1. 포인터 값을 특정해서 초기화할 수 없는 경우 0이나 NULL로 초기화해야 한다.
2. 0은 아무것도 가리키지 않는다는 의미로 해석된다.
5. 참고자료
1. https://prosto.tistory.com/253
2. https://studyc.tistory.com/15
'프로그래밍 > C' 카테고리의 다른 글
구조체 (0) | 2020.06.10 |
---|---|
Call by Value, Call by Reference 비교 (0) | 2020.04.23 |
포인터를 쓰는 이유 (0) | 2020.03.27 |
아스키코드란? (0) | 2020.03.26 |
자료형이란? (0) | 2020.03.26 |
소중한 공감 감사합니다