본문 바로가기
휴지통/C 프로그래밍

포인터의 이해

by 신재권 2021. 1. 21.

1. 포인터란 무엇 인가?

포인터는 C언어가 low레벨 언어의 특성을 지닌다. 왜냐하면 포인터를 이용하면 메모리에 직접 접근이 가능하기 때문이다.

-주소의 값의 저장을 목적으로 선언되는 포인터 변수

int main(void){
char ch1 = 'A' , ch2 = 'Q';
int num = 7;
.... 
}

 

위와 같이 선언됬을 때 총 6바이트(1바이트 +1바이트+4바이트)로 구성되어있어 메모리에 할당된다. 예를 들어 나란히 할당된다고 가정한다.

A값은 주소값 0x12ff74에 저장되어 있고, Q값은 주소값 0x12ff75에 저장되어 있고 숫자 7은 0x12ff76부터 0x12ff79까지 메모리에 할당된다.

'int형 변수 num은 어디에 선언되어 있는가?'

우리는 'int형 변수 num은 ox12ff76번지에서부터 0x12ff79번지에 걸쳐서 할당되어 있다.'라고 답할 수 있다.

하지만 C언어에서는 시작번지만 가지고 위치를 표현한다. 왜냐하면 int형 변수는 4바이트 이므로 변수의 끝이 어딘지는 쉽게 계산이 가능하기 때문이다 .

'int 형 변수 num은 ox12ff76번지에 할당되어 잇다'라고 답하면 된다.

주소 값 0x12ff76역시 정수이다. 따라서 이것도 저장이 가능한 값이다. 이의 저장을 위해 마련된 변수가 바로 '포인터 변수'이다.

포인터 변수란 메모리의 주소 값을 저장하기 위한 변수이다.

포인터는 변수형태의 포인터와 상수 형태의 포인터를 어우르는 표현이다. 그런데 포인터와 관련된 이야기의 대부분이 포인터 변수오 ㅏ관련이 있으므로 포인터라 하면 '포인터 변수'를 연상하면 된다.

-포인터 변수와 &연산자에 대해서

"정수 7이 저장된 int형 변수 num을 선언하고 이 변수의 주소 값 저장을 위한 포인터 변수 pnum을 선언하자. 그리고 나서 pnum에 num주소 값을 저장하자 "

위의 문장이 요구하는 바를 코드로 작성하면 ..

int main(void){ 
int num=7;
int *pnum; //포인터 변수 pnum의 선언 
pnum = # //num의 주소 값을 포인터 변수 pnum에 저장

위의 코드에서 int *pnum 문장이 포인터 변수의 선언이다.

위의 문장은 다음과 같이 해석된다

pnum : 변수의 이름

int * : int형 변수의 주소 값을 저장하는 포인터 변수의 선언

때문에 pnum은 int형 변수의 주소 값을 저장할 수 있는 포인터 변수가 된다.

pnum = &num

위의 문장에서 &연산자는 '오른쪽에 등장하는 피연산자의 주소 값을 반환하는 연산자'이다. 따라서 위의 문장에서 &연산의 결과로 변수 num의 주소 값이 반환되며, 이를 포인터 변수 pnum에 저장하게 된다 .

포인터 변수 pnum에는 변수 num의 시작번지 주소값이 저장된다.

그리고 다음과 같이 표현한다 . "포인터 변수 pnum이 int형 변수 num을 가리킨다"

//포인터 변수의 크기는 컴퓨터의 사양마다 다르다. 32비트 시스템에서는 주소 값을 32비트로 표현하기 때문에 포인터 변수의 크기가 4바이트인 반면 64비트 시스템에서는 주소 값을 64비트로 표현하기 때문에 포인터 변수의 크기가 8바이트이다.

-포인터 변수 선언하기

포인터 변수는 가리키고자 하는 변수의 자료형에 따라서 선언하는 방법이 달라진다. 사실 주소값은 동일한 시스템에서 그 크기가 동일하면 모두 정수의 형태를 띤다. 그래도 가리키고자 하는 변수의 자료형에 따라서 선언하는 방법이 달라진다.

int *pnum1;

double* pnum2;

unsigned int * pnum3 ;

따라서 기본공식은 다음과 같다.

type * ptr; //type형 변수의 주소 값을 저장하는 포인터 변수 ptr의 선언

type형 포인터 변수 ptr

2. 포인터와 관련 있는 연산자: &연산자와 *연산자

일반적으로 &연산자와 *연산자를 가리켜 포인터 연산자라고 한다. *연산자는 피연산자가 두 개인 이항 연산자로 사용할 수 도 있고, 포인터에서 사용되는 *연산자는 피연산자가 한 개인 단항연산자 이다. * 연산자는 사용되는 위치에 따라서 그 의미가 달라진다.

-변수의 주소 값을 반환하는 &연산자

&연산자는 피연산자의 주소 값을 반환하는 연산자이다.

int main(void){ 
int num = 5;
int *pnum = # //num의 주소 값을 반환해서 포인터 변수 pnum을 초기화
} 
-------------------------------------------------------------------------------- 
int main(void){
int num1 = 5;
double *pnum1 =&num1; //일치하지 않음 

위의 포인터 변수 pnum은 변수 num을 가리키고 있다. 따라서 *pnum이 의미하는 바는

"포인터 변수 pnum이 가리키는 메모리 공간인 변수 num에 접근을 해서 ..."

*pnum=20, printf(...) 문장은

"포인터 변수 pnum이 가리키는 메모리 공간인 변수 num에 20을 저장해라."

"포인터 변수 pnum이 가리키는 메모리 공간인 변수 num에 저장된 값을 출력해라" 이다.

사실상 *pnum은 포인터 변수 pnum 이 가리키는 변수 num을 의미하는 것이다.

//printf("%d" , *pnum)에 *연산자를 붙이지 않으면 pnum값이 출력되며 즉 주소값이 출력된다(주소값은 정수이다). *를 붙여 가리키는 변수를 출력해야 한다.

포인터 변수는 가리키는 대상을 변경할 수 있다.

포인터 형은 메모리 공간을 참조하는 기준이 된다.

즉 포인터 형을 정의한 이유는 *연산자를 통한 메모리 공간의 접근 기준을 마련하기 위함이다.

포인터형이 존재하는 이유는 포인터 기반의 메모리 접근기준을 마련하기 위함이다. 포인터에 형이 존재 하지 않는다면 *연산을 통한 메모리의 접근은 불가능하다.

-잘못된 포인터의 사용과 널 포인터

포인터 변수에는 메모리의 주소 값이 저장되고 ,이를 이용해서 해당 메모리 공간에 접근도 가능하기 때문에 포인터와 관련해서는 상당히 주의를 해야한다.

int main(void){
int *ptr; //포인터 변수가 쓰레기 값으로 초기화됨 
*ptr = 200 ; //ptr이 어디를 가리킬줄 알고?
} 
========================================================================= 
int main(void){
int *ptr = 125; //125번지가 어딘줄 알고?
*ptr = 10; 
} 
==========================================================================
int main(void){ 
int *ptr1= 0;
int *ptr2= NULL; //NULL은 사실상 0을 의미함
}

위와 같이 포인터 변수를 선언만하고 초기화 하지 않으면 포인터 변수는 쓰레기값으로 초기화가 된다. 즉 어디를 가리킬지 모르게 된다 . 떄문에 이러한 상태에서 *연산을 통해 200을 저장하는 것은 치명적인 결과로 이어질 수 있다. ptr이 가리키는 위치가 어디인줄 알고 저장하는 건가? 만약에 ptr가 메모리 공간의 중요한 위치였다면 이는 시스템 전체에 심각한 문제를 일으킨다.

하지만 요즘 운영체제는 잘못된 메모리 접근의 시도가 있을 때 감지하고 중지시킨다.

두번째는 125번지가 어딘줄 알고 초기화시키고 다시 10으로 초기화하는가? 위와 같은 이유이다.

포인터 변수를 우선 선언만 해놓고 , 이후에 유효한 주소 값을 채워넣을 생각이면 3번째와 같이 0(널 포인터)를 가리키게 해야한다 .

0(널포인터)는 0번지를 의미하는 것이 아니고 '아무데도 가리키지 않는다' 라는 뜻이다.

따라서 이를 이용한 *연산은 메모리 공간에 어떠한 영향도 미치지 않는다.

'휴지통 > C 프로그래밍' 카테고리의 다른 글

포인터의 포인터  (0) 2021.01.21
다차원 배열  (0) 2021.01.21
포인터와 함수에 대한 이해  (0) 2021.01.21
포인터와 배열  (0) 2021.01.21
1차원 배열  (0) 2021.01.21