포인터


포인터를 이용하면 메모리에 직접 접근이 가능하다.


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


ex)

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
 
int main()
{
    char ch1='A', ch2='Q';
    int num=7;
    
    return 0;
}
 

cs


예를 들어, 위와 같이 변수가 선언되었다고 가정을 하면 총 6바이트가 밑의 그림처럼 메모리 공간에 할당된다.



메모리 블록 상단에 있는 것이 메모리의 주소 값이다. 1바이트의 메모리 공간을 단위로 하나의 주소 값이 할당되며, 주소 값도 1씩 증가한다.

A가 저장되어 있는 변수 ch1은 0x12ff74 Q가 저장되어 있는 변수 ch2는 0x12ff75 정수 7이 저장되어 있는 변수 num은 0x12ff76번지에 할당이 되어 있다.

int형 변수 num은 0x12ff76 ~ 0x12ff79가지 할당되어 있다고 말할 수 있지만, C언어에서는 시작 번지만을 가지고 위치를 표현하기 때문에, 

int형 변수 num은 0x12ff76에 할당되어 있다고 말을 한다.


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


ex)포인터 변수 선언하기

1
2
3
4
5
6
7
8
int main()
{
    int num = 10;
    int *pnum;
    pnum = &num;
    ...
 
}

cs


4행 : 포인터 변수 pnum 선언
5행 : num의 주소 값을 포인터 변수 pnum에 저장

'포인터 변수 pnum이 int형 변수 num을 가리킨다'

포인터 변수의 크기는 32비트 시스템에서는 주소 값을 32비트로 표현하기 때문에 포인터 변수의 크기가 4바이트인 반면, 64비트 시스템에서는 주소 값을 64비트로 표현하기 때문에 포인터 변수의 크기가 8바이트이다. 따라서 주소값의 크기와 포인터 변수의 크기가 동일한 것은 당연하다 할 수 있다. 

32bit / 64bit시스템에 따라 4바이트 / 8바이트의 형태로 주소값 반환


int *pnum1;         // int * 는 int형 변수를 가리키는 pnum1의 선언을 의미

double *pnum2; // double * 는 double형 변수를 가리키는 pnum2의 선언을 의미

type *pnum3;     // type * 는 type형 변수를 가리키는 pnum3의 선언을 의미


int *                    // int형 포인터

int * pnum1;       // int형 포인터 변수 pnum1


double *             // double형 포인터

double * pnum2;// double형 포인터 변수 pnum2


type *                  // type형 포인터

type * pnum3;    // type형 포인터 변수 pnum3


& 연산자


주소값을 반환하는 & 연산자

int num = 5;

int *pnum = &num // num의 주소 값을 반환해서 포인터 변수 pnum을 초기화


&연산자의 피 연산자는 변수이어야 하며, 상수는 피연산자가 될 수 없다. 


ex) 변수의 자료형에 맞지 않는 포인터 변수의 선언은 문제가 된다.

1
2
3
4
5
6
7
8
9
10
int main()
{
    int num1 = 5;
    double *pnum = &num1;
    
    duoble num2 = 5;
    int * pnum2 = &num2;
    ....
    
}

cs

위의 예제는 컴파일에는 문제가 발생하지 않는다. 

하지만 포인터변수의 포인터형을 참조해서 메모리에 접근하기 때문에 *연산자를 사용하여 값을 사용할 시에 문제가 발생한다.

메모리 공간에 접근하는 *연산자가 접근방법을 결정하는 힌트를 제공한다. (밑의 예제 참조)


*연산자


ex)

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
 
int main()
{
    int num = 10;
    int *pnum = &num;
    *pnum = 20;
    printf("%d",*pnum);
    
    return 0;
}

cs

6행 : 포인터 변수 pnum이 변수 num을 가리키게 하는 문장

7행 : pnum이 가리키는 변수에 20을 저장하라

8행 : pnum이 가리키는 변수를 부호 있는 정수로 출력하라


*pnum = 20;    // 포인터 변수 pnum이 가리키는 메모리 공간인 변수 num에 정수 20을 저장해라

printf("%d",*pnum); // 포인터 변수 pnum이 가리키는 메모리 공간인 변수 num에 저장된 값을 출력하라

따라서 *pnum = 20 이나 num = 20 이나 같다.!


ex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
 
int main()
{
    int num1 = 100, num2 = 100;
    int *pnum=NULL;
    
    pnum = &num1;
    (*pnum) += 30;
    
    pnum = &num2;
    (*pnum) -= 30;
    
    printf("num1 : %d , num2 : %d \n",num1,num2);
    
    return 0;
}
 

cs

출력결과

num1 : 130 , num2 : 70 

6행 : int형 포인터 변수이니, int형 변수의 주소 값을 저장할 수 있다.

8행 : 포인터 변수 pnum에 변수 num1의 주소값 저장, pnum은 num1을 가리키게 된다.

9행 : pnum이 num1을 가리키고 있으므로 num1의 값이 30 증가한다.

11행 : 포인터 변수 pnum에 변수 num2의 주소값 으로 변경, pnum은 num2을 가리키게 된다.

12행 : pnum이 num2을 가리키고 있으므로 num1의 값이 30 감소한다.


다양한 포인터 형이 존재하는 이유


return *pnum;


위의 문장으로는 몇바이트를 읽어 들여야 하는지, 읽어들인 데이터는 정수인지 실수인지 전혀 알지 못한다. 그러므로 포인터 형 정보가 필요하다. 

pnum을 int형 포인터 변수라고 가정했을 때, pnum이 int형 포인터 변수이므로 pnum에 저장된 주소를 시작으로 4바이트를 읽어 들여서 이를 정수로 해석해야 한다.

pnum을 double형 포인터 변수라고 가정했을 때, pnum이 double형 포인터 변수이므로 pnum에 저장된 주소를 시작으로 8바이트를 읽어 들여서 이를 실수로 해석해야 한다.


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

ex)

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
 
int main()
{
    double num = 3.14;
    int *pnum = &num;
    printf("%d"*pnum);
    
    return 0;
}
 

cs

출력결과

1374389535


위의 코드를 실행하게 되면 int형 포인터 변수 pnum은 double형 변수 num을 가리키게 된다. 따라서 pnum이 가리키는 메모리 공간에 저장된 값을 얻기 위해서 *연산을 하는 경우 ' 4바이트를 읽어 들여서 이를 정수로 해석한다 ' 로 해석을 하게 된다. 실제 저장할 때는 8바이트로 했으나 해석은 전혀 엉뚱하게 해버렸다. 이는 전혀 예측이 불가능한 출력이 된다.


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

또 한, 주소값은 정수인데 왜 int형 변수에 저장을 하지 않는지 물어볼 수 있다.

이는 주소값을 이용해서 메모리의 접근을 하기 위해 포인터를 사용하는 것이다. int형 변수에 저장하면 단순히 접근이 아닌 그 변수의 주소값만 출력이 가능하다. 변수의 주소값을 출력하는 것 이외에 메모리의 접근은 절대로 불가능하다.


ex) 잘못된 포인터의 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
    int *ptr;
    *ptr = 200;
    
    int * ptr = 125;
    *ptr = 10;
    
    ....
    
}
 
 

cs


3,4행 : int형 포인터 변수 ptr에는 현재 쓰레기 값이 들어가 있다. 이 쓰레기값의 주소에 메모리 공간에 접근하여 이를 200으로 바꾸는 것은 매우 위험한 코드이다. 그 곳이 매우 중요한 위치였다면 이는 시스템 전체에 심각한 문제를 일으킬 수도 있는 상황이다.

6,7행 : int형 포인터 변수에 125번지를 저장하였다. 그리고 125번지의 메모리공간에 접근하여 10으로 바꾸었다. 125번지가 어디인 줄 알고 변수를 125로 초기화를 하였는가. 매우 심각한 문제이다. 

위의 문제들을 막기 위해, 포인터 변수를 0 또는 NULL로 초기화를 시키는 것이 좋다. 
int *ptr = 0; , int *ptr = NULL ;
이는 0번지를 가리키는 것이 아닌 아무곳도 가리키지 않는 것이다. 그러므로 이는 안전한 프로그래밍이다.

'programming > C' 카테고리의 다른 글

C // 포인터와 함수  (0) 2017.12.21
C // 포인터와 배열  (0) 2017.12.20
C // 1차원배열  (0) 2017.12.18
C // 재귀함수  (0) 2017.12.16
C // 변수  (0) 2017.12.15

+ Recent posts