Skip to content

56. 함수 포인터 매개변수


1. 키워드

  • 포인터 매개변수


2. 함수에서 포인터 매개변수 사용하기

  • C에서 값을 여러 개 반환하는 함수를 만들 때는 주로 포인터 매개변수를 사용한다.
  • 지금까지 매개변수는 함수에 값을 전달할 때 사용했고, 함수 바깥에서 값을 가져오는 용도로는 사용할 수 없었다.
  • 즉, 다음과 같이 두 값을 더하는 add 함수는 반환값이 1개이고, 매개변수 ab는 함수 안에서만 사용할 수 있었다.


001


  • 하지만 함수에서 포인터 매개변수를 사용하면 함수 안에서 바뀐 내용을 함수 바깥에서도 알 수 있다.
  • 따라서 함수 바깥으로 값을 전달할 수 있다.
  • 다음은 함수에서 포인터 매개변수를 사용하여 두 변수의 값을 서로 바꾸는 swapNumber 함수이다.


002


3. 포인터 매개변수 사용하기

  • 먼저 매개변수에서 일반적인 변수를 사용하면 변수의 내용이 어떻게 되는지 알아보자.


#include <stdio.h>

void swapNumber(int first, int second) // 반환값 없음, int 타입 매개변수 두 개 지정
{
    int temp; // 임시 보관 변수

    temp = first;
    first = second;
    second = temp;
}

int main()
{
    int num1 = 10;
    int num2 = 20;

    swapNumber(num1, num2); // 변수 num1과 num2를 넣어줌

    printf("%d %d\n", num1, num2); // swapNumber 함수와는 상관없이 처음 저장한 10과 20이 출력됨

    return 0;
}

// 10 20


  • void swapNumber(int first, int second)와 같이 int 타입 매개변수 두 개를 지정하고, 함수 안에서 임시 변수 temp를 이용하여 firstsecond에 저장된 값을 서로 바꾸었다.


void swapNumber(int first, int second) // 반환값 없음, int 타입 매개변수 두 개 지정
{
    int temp; // 임시 보관 변수

    temp = first;
    first = second;
    second = temp;
}


  • swapNumber를 호출할 때 10이 들어있는 num120이 들어있는 num2를 넣었다.
  • 그 뒤 printf로 값을 출력해 보면 num1num2의 값이 서로 바뀌지 않고 1020이 그대로 출력되는 것을 볼 수 있다.
  • 즉, 매개변수는 값을 전달하는 역할만 할 뿐 함수 바깥의 변수와는 상관이 없다.


int num1 = 10;
int num2 = 20;

swapNumber(num1, num2); // 변수 num1과 num2를 넣어줌

printf("%d %d\n", num1, num2); // swapNumber 함수와는 상관없이 처음 저장한 10과 20이 출력됨


  • 아무리 swapNumber를 호출해 봐야 num1num2의 값은 바뀌지 않는다.


003


  • 이번에는 매개변수를 포인터로 사용해 보자.
  • 다음과 같이 ()(괄호) 안에서 자료형 뒤에 *(애스터리스크)를 붙여서 매개변수를 포인터 형태로 만든다.


반환값자료형 함수이름(자료형 *매개변수1, 자료형 *매개변수2)
{
}
#include <stdio.h>

void swapNumber(int *first, int *second) // 반환값 없음, int 타입 포인터 매개변수 두 개 지정
{
    int temp; // 임시 보관 변수

    // 역참조로 값을 가져오고, 값을 저장함
    temp = *first;
    *first = *second;
    *second = temp;
}

int main()
{
    int num1 = 10;
    int num2 = 20;

    swapNumber(&num1, &num2); // &를 사용하여 num1과 num2의 메모리 주소를 넣어줌

    printf("%d %d\n", num1, num2); // swapNumber에 의해서 num1과 num2의 값이 서로 바뀜

    return 0;
}

// 20 10


  • 먼저 함수를 정의할 때 매개변수 부분을 int *first, int *second와 같이 포인터 형식으로 지정해 준다.
  • 그리고 함수 안에서 매개변수를 역참조하여 firstsecond의 값을 바꾼다.


void swapNumber(int *first, int *second) // 반환값 없음, int 타입 포인터 매개변수 두 개 지정
{
    int temp; // 임시 보관 변수

    // 역참조로 값을 가져오고, 값을 저장함
    temp = *first;
    *first = *second;
    *second = temp;
}


  • num1에는 10, num2에는 20이 들어있다.
  • 이 상태에서 &(주소 연산자)를 사용하여 swapNumbernum1num2의 메모리 주소를 넣어준다.


int main()
{
    int num1 = 10;
    int num2 = 20;

    swapNumber(&num1, &num2); // &를 사용하여 num1과 num2의 메모리 주소를 넣어줌

    printf("%d %d\n", num1, num2); // swapNumber에 의해서 num1과 num2의 값이 서로 바뀜

    return 0;
}


  • printf로 값을 출력해 보면 swapNumber에 의해서 num1num2의 값이 서로 바뀐 것을 알 수 있다.
  • 즉, 변수의 메모리 주소를 함수에 전달하면 함수 안에서는 메모리 주소에 접근하여 값을 저장할 수 있다.


004


  • 함수의 반환값은 값을 하나만 반환할 수 있는데, 이처럼 매개변수를 포인터로 사용하면 함수 바깥으로 여러 개의 값을 전달할 수 있다.


  • 지금까지 scanf 함수를 사용할 때 변수를 그대로 넣지 않고 &를 사용하여 변수의 메모리 주소를 넣었다.
  • 왜냐하면 scanf 함수는 값을 여러 개 입력받을 수 있는데, 반환값만으로는 여러 개의 값을 함수 바깥으로 가져올 수 없었기 때문이다.


int num1;
int num2;
int num3;

scanf("%d %d %d", &num1, &num2, &num3); // scanf에서 값을 3개 가져옴(scanf는 바깥으로 값을 3개 전달)


  • 변수의 메모리 주소만 넘겨주면 값이 몇 개든 상관없이 값을 가져올 수 있다.


매개변수 포인터와 IN, OUT

  • C로 작성된 여러 프로그램을 보면 매개변수 부분에 IN, OUT, in, out 등의 단어가 추가로 붙어있는 경우가 있다.


//            ↓ 일반적인 매개변수
void GetValue(IN int a, OUT int *b)
{ //                     ↑ 값이 바깥으로 나오는 매개변수
    printf("%d\n", a);

    *b = 10;
}


  • IN은 함수 안으로 들어가기만 하는 일반적인 매개변수라는 표시이고, OUT은 함수 바깥으로 값이 나오는 매개변수라는 표시이다.
  • IN, OUT은 C의 문법은 아니지만 전처리기를 활용하여 사람만 알아볼 수 있도록 정의한 것이다.
  • 즉, 함수의 매개변수 부분만 보고도 매개변수의 방향을 쉽게 알 수 있다.


  • IN, OUT은 다음과 같이 정의되어 있으며, 컴파일할 때는 아무런 영향을 주지 않는다.


#define IN
#define OUT


4. void 타입 포인터 매개변수 사용하기

  • void 타입 포인터 매개변수를 사용하면 자료형 변환을 하지 않아도 모든 자료형을 함수에 넣을 수 있다.
  • 이번에는 char, int, float 타입을 매개변수로 받아서 값을 서로 바꿔보자.


#include <stdio.h>

enum TYPE
{
    TYPE_CHAR,
    TYPE_INT,
    TYPE_FLOAT
};

void swapValue(void *ptr1, void *ptr2, enum TYPE t) // 반환값 없음, void 타입 포인터 매개변수 두 개와
{                                                   // 변수의 자료형을 알려줄 열거형을 받음
    switch (t)
    {
    case TYPE_CHAR: // 문자면 char 타입 포인터로 변환한 뒤 역참조하여 값을 서로 바꿈
    {
        char temp;
        temp = *(char *)ptr1;
        *(char *)ptr1 = *(char *)ptr2;
        *(char *)ptr2 = temp;
        break;
    }
    case TYPE_INT: // 정수면 int 타입 포인터로 변환한 뒤 역참조하여 값을 서로 바꿈
    {
        int temp;
        temp = *(int *)ptr1;
        *(int *)ptr1 = *(int *)ptr2;
        *(int *)ptr2 = temp;
        break;
    }
    case TYPE_FLOAT: // 실수면 float 타입 포인터로 변환한 뒤 역참조하여 값을 서로 바꿈
    {
        float temp;
        temp = *(float *)ptr1;
        *(float *)ptr1 = *(float *)ptr2;
        *(float *)ptr2 = temp;
        break;
    }
    }
}

int main()
{
    char c1 = 'a';
    char c2 = 'b';
    swapValue(&c1, &c2, TYPE_CHAR); // 변수의 메모리 주소와 TYPE_CHAR를 넣음
    printf("%c %c\n", c1, c2);      // swapValue에 의해서 값이 서로 바뀜

    int num1 = 10;
    int num2 = 20;
    swapValue(&num1, &num2, TYPE_INT); // 변수의 메모리 주소와 TYPE_INT를 넣음
    printf("%d %d\n", num1, num2);     // swapValue에 의해서 값이 서로 바뀜

    float num3 = 1.234567f;
    float num4 = 7.654321f;
    swapValue(&num3, &num4, TYPE_FLOAT); // 변수의 메모리 주소와 TYPE_FLOAT를 넣음
    printf("%f %f\n", num3, num4);       // swapValue에 의해서 값이 서로 바뀜

    return 0;
}

// b a
// 20 10
// 7.654321 1.234567


  • swapValue 함수에 매개변수를 void 타입 포인터 두 개로 지정한다.
  • 단, void 타입 포인터는 역참조를 할 수 없으므로 어떤 자료형으로 역참조할지 알려주기 위해 TYPE 열거형도 함께 받는다.
  • 함수 안에서는 TYPE에 따라 각 자료형의 포인터로 변환한 뒤 역참조하여 값을 서로 바꾼다.


void swapValue(void *ptr1, void *ptr2, enum TYPE t) // 반환값 없음, void 타입 포인터 매개변수 두 개와
{                                                   // 변수의 자료형을 알려줄 열거형을 받음
    switch (t)
    {
    case TYPE_CHAR: // 문자면 char 타입 포인터로 변환한 뒤 역참조하여 값을 서로 바꿈
    {
        char temp;
        temp = *(char *)ptr1;
        *(char *)ptr1 = *(char *)ptr2;
        *(char *)ptr2 = temp;
        break;
    }
    case TYPE_INT: // 정수면 int 타입 포인터로 변환한 뒤 역참조하여 값을 서로 바꿈
    {
        int temp;
        temp = *(int *)ptr1;
        *(int *)ptr1 = *(int *)ptr2;
        *(int *)ptr2 = temp;
        break;
    }
    case TYPE_FLOAT: // 실수면 float 타입 포인터로 변환한 뒤 역참조하여 값을 서로 바꿈
    {
        float temp;
        temp = *(float *)ptr1;
        *(float *)ptr1 = *(float *)ptr2;
        *(float *)ptr2 = temp;
        break;
    }
    }
}


  • swapValue 함수를 사용할 때는 자료형 변환을 하지 않아도 다양한 자료형의 포인터(메모리 주소)를 넣을 수 있다.
  • 여기서는 &를 사용하여 char, int, float 타입 변수의 주소를 구해서 swapValue 함수에 넣었다.
  • 물론 어떤 자료형인지 알려줘야 하므로 TYPE 열거형 값도 넣어준다.


char c1 = 'a';
char c2 = 'b';
swapValue(&c1, &c2, TYPE_CHAR); // 변수의 메모리 주소와 TYPE_CHAR를 넣음
printf("%c %c\n", c1, c2);      // swapValue에 의해서 값이 서로 바뀜

int num1 = 10;
int num2 = 20;
swapValue(&num1, &num2, TYPE_INT); // 변수의 메모리 주소와 TYPE_INT를 넣음
printf("%d %d\n", num1, num2);     // swapValue에 의해서 값이 서로 바뀜

float num3 = 1.234567f;
float num4 = 7.654321f;
swapValue(&num3, &num4, TYPE_FLOAT); // 변수의 메모리 주소와 TYPE_FLOAT를 넣음
printf("%f %f\n", num3, num4);       // swapValue에 의해서 값이 서로 바뀜


  • void 타입 포인터 매개변수에는 변수의 메모리 주소뿐만 아니라 메모리를 할당한 포인터도 넣을 수 있다.
  • 또한, 일반 자료형의 포인터뿐만 아니라 구조체, 공용체, 열거형 등의 포인터도 넣을 수 있다.


5. 이중 포인터 매개변수 사용하기

  • 지금까지 함수에서 포인터 매개변수를 이용해서 정수, 실수 등의 값을 가져왔다.
  • 그럼 일반적인 값 대신 포인터(메모리 주소)를 어떻게 얻어오는지 알아보기 위해 먼저 함수에 포인터를 넘겨준 뒤 메모리를 할당해 보자.


#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일

void allocMemory(void *ptr, int size) // 반환값 없음, void 타입 포인터 매개변수 지정
{
    ptr = malloc(size); // ptr은 allocMemory를 벗어나면 사용할 수 없음
}

int main()
{
    long long *numPtr = NULL;

    // numPtr과 할당할 크기를 넣어줌
    allocMemory(numPtr, sizeof(long long));

    *numPtr = 10; // 메모리가 할당되지 않았으므로 실행 에러
    printf("%lld\n", *numPtr);

    free(numPtr);

    return 0;
}


  • 잘 될 것 같지만 실행을 해보면 에러가 발생한다.
  • void allocMemory(void *ptr, int size)와 같이 매개변수를 void 타입 포인터로 지정한 뒤 ptr에 메모리를 할당했다.
  • 하지만 ptr에 메모리를 할당해 봐야 allocMemory 함수를 벗어나면 사용할 수가 없다.
  • 결국 메모리 누수가 발생한다.


void allocMemory(void *ptr, int size) // 반환값 없음, void 타입 포인터 매개변수 지정
{
    ptr = malloc(size); // ptr은 allocMemory를 벗어나면 사용할 수 없음
}


  • 이번에는 이중 포인터를 사용하여 함수 안에서 메모리를 할당한 뒤 가져와보자.


#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일

void allocMemory(void **ptr, int size) // 반환값 없음, void 타입 이중 포인터 매개변수 지정
{
    *ptr = malloc(size); // void **ptr을 역참조하여 void *ptr에 메모리 할당
}

int main()
{
    long long *numPtr = NULL;

    // 단일 포인터 long long *numPtr의 메모리 주소는 long long 타입 이중 포인터와 같음, 할당할 크기도 넣음
    allocMemory((void **)&numPtr, sizeof(long long));

    *numPtr = 10;
    printf("%lld\n", *numPtr);

    free(numPtr); // 동적 메모리 해제

    return 0;
}

// 10


  • allocMemory 함수를 만들 때 void allocMemory(void **ptr, int size)와 같이 void 타입 이중 포인터 ptr을 받도록 만든다.
  • 물론 할당할 메모리 크기도 알아야 하므로 size도 함께 받는다.
  • 함수 안에서는 매개변수 void **ptr을 역참조하여 void *ptr이 되도록 만든 뒤 malloc 함수로 메모리를 할당한다.


void allocMemory(void **ptr, int size) // 반환값 없음, void 타입 이중 포인터 매개변수 지정
{
    *ptr = malloc(size); // void **ptr을 역참조하여 void *ptr에 메모리 할당
}


  • 이제 long long *numPtr;과 같이 단일 포인터를 선언한 뒤 numPtr의 메모리 주소를 구해서 allocMemory 함수에 넣어준다.
  • 즉, long long *numPtr;의 메모리 주소는 long long 타입 이중 포인터와 같으므로 매개변수 void **ptr로 받을 수 있다.


long long *numPtr = NULL;

// 단일 포인터 long long *numPtr의 메모리 주소는 long long 타입 이중 포인터와 같음, 할당할 크기도 넣음
allocMemory((void **)&numPtr, sizeof(long long));

*numPtr = 10;
printf("%lld\n", *numPtr);

free(numPtr); // 동적 메모리 해제


  • 다음과 같이 매개변수 void **ptr을 역참조하여 실제로는 numPtr에 메모리를 할당하게 된다.


005


6. 문자열 매개변수 사용하기

  • 함수에서 매개변수로 문자열을 받으려면 다음과 같이 매개변수를 문자열 포인터로 지정하면 된다.


#include <stdio.h>

void helloString(char *s1) // 반환값 없음, char 타입 포인터 매개변수 한 개 지정
{
    printf("Hello, %s\n", s1); // "Hello, "와 매개변수를 조합하여 문자열 출력
}

int main()
{
    helloString("world!"); // 함수를 호출할 때 문자열을 전달

    return 0;
}

// Hello, world!


  • 먼저 함수를 정의할 때 () 안에 char *s1과 같이 매개변수를 문자열 포인터로 지정한다.
  • 여기서는 printf"Hello, " 문자열을 출력하면서 서식 지정자 %s로 매개변수의 값을 함께 출력한다.


void helloString(char *s1) // 반환값 없음, char 타입 포인터 매개변수 한 개 지정
{
    printf("Hello, %s\n", s1); // "Hello, "와 매개변수를 조합하여 문자열 출력
}


  • 함수를 호출할 때는 "world!"와 같이 문자열을 넣어주면 문자열의 주소가 매개변수에 전달된다.


helloString("world!"); // 함수를 호출할 때 문자열을 전달


  • 다음과 같이 배열 형태의 문자열도 문자열 포인터 매개변수에 전달할 수 있다.
  • 이때도 매개변수는 char 타입 포인터로 지정한다.


#include <stdio.h>

void helloString(char *s1) // 반환값 없음, char 타입 포인터 매개변수 한 개 지정
{
    printf("Hello, %s\n", s1); // "Hello, "와 매개변수를 조합하여 문자열 출력
}

int main()
{
    char s1[10] = "world!"; // 배열 형태의 문자열

    helloString(s1); // 함수를 호출할 때 배열 전달

    return 0;
}

// Hello, world!


  • 매개변수로 문자 배열을 받는다는 것을 확실히 해주려면 다음과 같이 매개변수 뒤에 [](대괄호)를 붙여주면 된다.
  • 단, [] 안에 들어가는 배열의 크기는 생략한다.


#include <stdio.h>

void helloString(char s1[]) // 반환값 없음, char 타입 배열을 매개변수로 지정, 크기 생략
{
    printf("Hello, %s\n", s1); // "Hello, "와 매개변수를 조합하여 문자열 출력
}

int main()
{
    char s1[10] = "world!"; // 배열 형태의 문자열

    helloString(s1);       // 함수를 호출할 때 배열 전달
    helloString("world!"); // 함수를 호출할 때 문자열 전달

    return 0;
}

// Hello, world!
// Hello, world!


  • 매개변수를 char s1[]과 같이 지정하더라도 배열뿐만 아니라 문자열이나 메모리가 할당된 포인터도 전달할 수 있다.

References