Coding & Programming/C언어 기초(A-Z)

[C언어 기초코딩: 예제로 간단 정리] 9. 포인터(Pointer)

mainCodes 2021. 3. 16. 19:50

JollyTree의 C언어 기초코딩: 예제로 간단 정리 - 9. 포인터(Pointer)

 

안녕하세요 JollyTree입니다(•̀ᴗ•́)و 


모든 변수는 컴퓨터의 메모리 공간에 저장되며 저장과 동시에 운영체제가 정한 주소를 할당받게 됩니다. 그럼 선언한 변수가 메모리의 어느 주소에 할당되었는지 그리고 어떤 주소에 어떤 값이 저장되어 있는지는 어떻게 확인할까요 ?

C언어의 강력한 기능인 포인터(Pointer)를 통해 가능하게 됩니다. 아래와 같은 변수를 선언했다고 가정해 봅시다. 참고로, 선언된 변수의 크기는 sizeof(score);, sizeof(ch);, sizeof(vaue); 로 확인할 수 있습니다.

    int score = 20;     //정수형 4바이트 크기로 메모리에 할당
    char ch = 'j';       //문자형 1바이트 크기로 메모리에 할당
    float value = 30;  //실수형 4바이트 크기로 메모리에 할당

각각의 변수는 주소를 할당 받고 해당 주소에 저장되게 되는데 다음과 같은 형태로 저장됩니다. 일반적으로 4바이트 또는 8바이트 크기의 주소 체계(예; 0x01234567, 0x0123456789ABCDEF)를 갖는데 아래 그림에서는 메모리 주소를 편의상 0~n으로 표기하였습니다. 

변수명 score ch value    
저장된 값 20 'j' 30    
메모리 주소 0 1 2 3 4 5 6 7 8 ... n

변수는 데이터 타입(자료형)의 크기에 따라 메모리의 공간을 차지하게 되는데 위 그림에서와 같이 int형 변수 score는 4바이트, ch 변수는 1바이트 그리고 value는 4바이트의 공간을 차지 합니다. 기본적으로 32bit냐 64bit 컴퓨터냐에 따라 다를 수 있고 운영체제가 32bit/64bit를 모두 지원하면 대체로 코드를 컴파일할 때 32bit, 64bit를 선택할 수 있습니다. 컴파일 옵션에서 32bit를 선택하면 4바이트, 64bit를 선택하면 8바이트 크기의 주소체계를 갖게 됩니다. 

 

위 그림에서 0번지에 score 변수가 있고 4번지에 ch 그리고 5번지에는 value를 변수가 저장되어 있습니다.  '&' 연산자를 이용하면 변수가 몇 번지의 메모리 공간에 할당되었는지를 확인할 수 있습니다. 다음은 &연산자를 사용한 예입니다.

 

    printf(">> score 변수의 주소  : %u\n", &score);
    printf(">> ch 변수의 주소     : %u\n", &ch);
    printf(">> value 변수의 주소  : %u\n", &value);

 

포인터 변수는 '*'연산자를 이용하여 "자료형 *변수명"의 형식으로 다음과 같이 선언할 수 있습니다.

    int *p_score;           //int형 포인터 변수
    int* a;              
    char* b;                 //char형 포인터 변수
    double* c;              //double형 포인터 변수
    char* p_str = NULL;  //char형 포인터 변수

 

(참고) 아래 그림은 Visual Studio 2019에서 각각 x86, x64로 설정하여 컴파일 한 후 변수의 주소를 확인한 화면입니다.

 

그럼, 예제를 통해 C언어에서의 포인터를 정리해 보겠습니다. 아래 예제는 다음의 내용이 포함되어 있습니다.

  - 포인터 변수 선언하기
  - '&'  연산자를 이용한 변수의 주소를 출력하기
  - 포인터를 이용하여 포인터 변수값(주소)이 가리키는 곳의 값 출력하기
  - 포인터를 이용하여 배열의 변수값을 수정하고 출력하기
  - 포인터를 이용하여 참조에 의한 함수 호출(Call By Reference) 하기

 

포인터(Pointer) 예제:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <stdio.h>
 
//일반 함수(값에 의한 함수 호출용 함수
int add_value(int a, int b)
{
    return (a + b);
}
 
//참조에 의한 함수 호출용 함수(매개변수에 포인터 이용)
int add_reference(int* a, int* b)
{
    return (*+ *b);
}
 
//매개변수를 이용하여 결과값이 반환되도록 만든 함수
void add_reference_arg(int a, int b, int c, int* sum, int* avg)
{
    *sum = a + b + c;
    *avg = (*sum) / 3;
}
 
int main()
{
    //1. 포인터를 통한 변수 주소 및 값 출력
    {
        int score = 20;    //정수형 4바이트 크기로 메모리에 할당
        char ch = 'j';     //문자형 1바이트 크기로 메모리에 할당
        float value = 30;  //실수형 4바이트 크기로 메모리에 할당
 
        //포인터 변수 선언
        int* p_score = 0;
 
        printf(">> score 변수의 주소  : %u\n"&score);
        printf(">> ch 변수의 주소     : %u\n"&ch);
        printf(">> value 변수의 주소  : %u\n"&value);
 
        //score변수의 주소를 포인터 변수에 대입
        p_score = &score;
 
        //포인터 변수 p_score에 저장된 score 변수의 주소를 출력
        printf(">> p_score 의 값 출력          : %u\n", p_score);
 
        //score 변수에 저장된 값을 출력(간접 참조)
        printf(">> *p_score 의 값 출력          : %u\n"*p_score);
    }
 
    //2. 포인터 변수의 데이터 타입과 크기
    {
        int* a;
        char* b;
        double* c;
 
        a = (int*)1000;
        b = (char*)1000;
        c = (double*)1000;
 
        printf(">> 초기 설정 값      : a = %d, b = %d, c = %d\n", a, b, c);
 
        a = a + 1;
        b = b + 1;
        c = c + 1;
        printf(">> +1 증가 후 설정 값 : a = %d, b = %d, c = %d\n", a, b, c);
    }
 
 
    //3.포인터를 통한 배열 변수 값 출력
    {
        char str[15= "HelloWorld";
        char* p_str = NULL;
 
        int num[5= { 12345 };
        int* p_num = num;   //&연산자 없이 num 배열 변수 주소대입
 
        p_str = str;  //str배열 변수의 주소를 포인터에 대입, 배열은 & 연산자 없이 주소 대입
        printf("p_str = %s\n", p_str);
 
 
        //포인터 변수 *p_num을 통해 num 배열의 원소를 순차적으로 출력(간접 참고)
        printf("\n");
        for (int i = 0; i < 5; i++)
            printf("*(p_num + %d) = %u\n", i, *(p_num + i));
 
 
        printf("\n");
        *(p_num + 2= 5000;  //num배열의 3번째 원소인 num[2] 값 변경
        *(p_num + 3= 6000;  //num배열의 4번째 원소인 num[3] 값 변경
 
        //num의 모든 원소 출력
        for (int i = 0; i < 5; i++)
            printf("num[%d] = %u\n", i, num[i]);
        printf("\n");
    }
 
    //4. 배열과 포인터와의 관계
    {
        int num[5= { 12345 };
        printf("*(num + 2) = %d\n"*(num + 2));
    }
 
    //5. 포인터를 이용한 참조에 의한 함수 호출(Call By Reference)
    {
        int a = 100, b = 200;
 
        printf("add_value = %d\n", add_value(a, b));
        printf("add_reference = %d\n", add_reference(&a, &b));
    }
 
    //6. 매개변수를 이용한 결과값 리턴
    {
        int a, b, c;
        int sum = 0, avg = 0;
 
        a = 1000;
        b = 2000;
        c = 3000;
 
        add_reference_arg(a, b, c, &sum, &avg);
        printf("\nsum = %d, avg = %d\n", sum, avg);
    }
 
    return 0;
}
cs

 

실행결과(Output):

 

1. 포인터를 통한 변수 주소 및 값 출력

4p_score = &score;는 변수의 주소를 포인터 변수에 대입하는 코드로 p_score는 변수의 주소값이 저장되고 *p_score라고 표기하면 해당 주소가 가리키는 주소에 저장되어 있는 값을 나타냅니다. 그래서 아래 코드를 통해 변수 score에 저장된 20이라는 정수값이 출력되게 되는 것입니다.  이것을 간접 참조 라고 합니다.

 printf(">> *p_score 의 값 출력          : %u\n", *p_score);

2. 포인터 변수의 데이터 타입과 크기

변수와 포인터 변수의 데이터 타입은 동일해야 하는데 예제 코드는 그 이유를 설명합니다. 예제를 보면 동일하게 +1씩 했는데도 선언한 데이터 타입에 따라 4바이트, 1바이트, 8바이트로 증가한 수가 다르게 됩니다. 만약, 데이터 타입이 다르다면 오류가 발생합니다.   

 

        a = a + 1;
        b = b + 1;
        c = c + 1;

3.포인터를 통한 배열 변수 값 출력

포인터에 배열의 주소를 대입할 때는 p_num = num, p_str = str과 같이 & 연산자를 사용하지 않아도 됩니다. 만약 배열의 특정 원소 1개만을 대입할 때는 &num[0]과 같이 사용합니다.

예제에서는 *(p_num + i);를 이용하여 num배열의 각 원소를 순차적으로 출력하는데, 이는 "2. 포인터 변수의 데이터 타입과 크기"에서 설명했듯이 i는 0~4까지 +1씩 증가하지만, 데이터 타입이 int형이기 때문에 실제 메모리에서는 4바이트씩 이동하여 해당 주소에 저장된 값들을 출력하게 됩니다.

예제에서 nump[5]는 5개의 int형 원소들을 가지고 있습니다. 예제에서는 p_num 포인터 변수에 num 변수의 주소를 대입하여 *(p_num + i)를 통해 배열 원소들을 출력합니다. 그리고 포인터를 이용하여 num[2], num[3] 원소의 값을 각각 5000과 6000으로 변경합니다. 이후 다시 한번 num의 모든 원소를 출력하여 num[2]와 num[3]의 값이 변경된 것을 보여줍니다.

4. 배열과 포인터와의 관계

 

포인터를 통해 별도의 포인터 변수 없이 특정 원소의 값을 출력하는 것을 보여주는 예제입니다. 배열은 포인터 변수를 별도로 선언하지 않아도 간접참조연산자('*')를 이용할 수 있습니다.

5. 포인터를 이용한 참조에 의한 함수 호출(Call By Reference)

 

포인터는 함수의 인수 전달에도 영향을 줍니다. 값을 전달하는 방법은 크게 다음과 같이 두가지 방법이 있습니다.


   - Call By Value : 값에 의한 호출
   - Call By Reference : 참조에 의한 호출(포인터 이용)

아래 문장은 전형적인 값에 의한 호출로 인수 전달시 값을 복사해서 전달하는 방식으로 흔히 사용되는 방식입니다.

    printf("add_value = %d\n", add_value(a, b));

그리고 아래 문장은 포인터를 이용한 참조에 의한 함수 호출(Call By Reference)로 인수로 변수의 주소를 복사하여 전달하는 방식입니다.  


    printf("add_reference = %d\n", add_reference(&a, &b));

 

함수 몸체 부분을 보면 함수 원형에서도 매개변수에 차이점이 있음을 알 수 있습니다. 

 

    int add_value(int a, int b);        //일반 함수(값에 의한 함수 호출용 함수

    int add_reference(int* a, int* b) //참조에 의한 함수 호출용 함수(매개변수에 포인터 이용)

6. 매개변수를 이용한 결과값 n개 리턴 방법

 

일반적으로 함수는 반환값을 지정 할때 return문을 이용하여 1개의 값만을 반환할 수 있습니다.  하지만, 참조에 의한 함수 호출 기법을 이용하면 예제의 add_reference_arg() 함수와 같이 여러개의 값을 매개변수(파라미터)로 리턴할 수 있습니다.


void add_reference_arg(int a, int b, int c, int* sum, int* avg) //매개변수를 이용하여 결과값이 반환되도록 만든 함수


이 외에도 포인터는 동적 메모리 할당 등 메모리 상의 주소를 기반으로 다양한 용도로 활용이 됩니다. 이런 활용적인 부분은 코딩 관련 팁들로 "Code & Languages" 카테고리에 꾸준히 정리할 예정입니다.


이상 JollyTree였습니다 (•̀ᴗ•́)و