Skip to content

54. 함수 반환값


1. 키워드

  • 반환값(Return Value)


2. 함수에서 반환값 사용하기

  • 함수에서 값을 꺼내올 때는 반환값을 이용한다.
  • 함수 입장에서 보자면 반환값은 함수를 호출해 준 바깥에 결괏값을 알려주기 위해 사용한다.


3. 정수, 실수, 불 반환값 사용하기

  • 반환값은 함수를 정의할 때 반환값의 자료형을 지정해 주고, 함수 안에서 return 키워드로 값을 반환하면 된다.


반환값자료형 함수이름()
{
    return 반환값;
}


  • 한 가지 중요한 점은 반환값과 반환값의 자료형이 일치해야 한다는 점이다.
  • 즉, 반환값의 자료형을 반환값의 형태와 크기에 따라 결정한다.


int one() // 정수 1을 반환하므로 반환값 자료형은 int 타입을 지정
{
    return 1; // 정수 1을 반환
}


  • 이제 반환값을 가지는 함수를 호출해 보자.


#include <stdio.h>

int one() // 반환값이 int 타입인 one 함수 정의
{
    return 1;
}

int main()
{
    int num1;

    num1 = one(); // int 타입을 반환했으므로 int 타입 변수에 저장

    printf("%d\n", num1);

    return 0;
}

// 1


  • 다음과 같이 반환값의 자료형은 int 타입, 이름은 one인 함수를 선언 및 정의했으며 {} 안에서 return 키워드로 값 1을 반환했다.


/*
↓ 반환값 자료형 */
int one() // 반환값이 int 타입인 one 함수 정의
{
    return 1; // 1을 반환
} //       ↑ 반환값


  • 반환값을 저장하려면 반환값을 저장할 변수에 =(할당 연산자)를 사용한 뒤 함수를 호출해 주면 된다.


int num1;

num1 = one(); // int 타입을 반환했으므로 int 타입 변수에 저장


  • 반환값이 변수에 저장되는 과정을 그림으로 표현하면 다음과 같은 모양이 된다.


001


  • 만약 반환값을 변수에 저장하지 않고 바로 사용하고 싶다면 다음과 같이 printf 함수 안에 one 함수를 넣어서 실행해도 된다.


printf("%d\n", one()); // one 함수의 반환값을 바로 사용


  • 이번에는 함수에서 실수, 불값을 반환해 보자.


#include <stdio.h>
#include <stdbool.h> // bool, true, false가 정의된 헤더 파일

float realNumber() // 반환값이 float 타입인 realNumber 함수 정의
{
    return 1.234567f; // float 타입을 반환
}

bool truth() // 반환값이 bool 타입인 truth 함수 정의
{
    return true; // bool 타입을 반환
}

int main()
{
    float num1;
    bool b1;

    num1 = realNumber(); // float 타입을 반환했으므로 float 타입 변수에 저장
    b1 = truth();        // bool 타입을 반환했으므로 bool 타입 변수에 저장

    printf("%f\n", num1);
    printf("%d\n", b1);

    return 0;
}

// 1.234567
// 1


  • float realNumber()와 같이 반환값 자료형을 float 타입으로 지정해 주고 return으로 실수를 반환하면 된다.
  • 마찬가지로 bool truth()와 같이 반환값 자료형을 bool 타입으로 지정해 주고 return으로 불값을 반환하면 된다.


반환값의 자료형과 자료형 변환

  • 만약 반환값의 자료형과 반환하는 값의 자료형이 다르다면 형 변환을 하면서 반환하면 된다.


return (자료형)변수;
return (자료형);
#include <stdio.h>

int one() // 반환값이 int 타입인 one 함수 정의
{
    float a = 1.1f;

    return (int)a; // a를 int 타입으로 변환하여 반환
}

int main()
{
    int num1;

    num1 = one(); // int 타입을 반환했으므로 int 타입 변수에 저장

    printf("%d\n", num1);

    return 0;
}

// 1


  • 함수 반환값의 자료형과 반환값을 저장할 변수의 자료형이 다를 때도 있다.
  • 이때도 다음과 같이 자료형을 ()(괄호)로 묶은 뒤 함수 앞에 붙여서 반환값의 자료형을 강제로 변환한다.


(자료형)함수();
#include <stdio.h>

float onePointOne() // 반환값이 float 타입인 onePointOne 함수 정의
{
    return 1.1f; // 실수 1.1을 반환
}

int main()
{
    int num1;

    num1 = (int)onePointOne(); // onePointOne의 반환값을 int 타입으로 변환하여 저장

    printf("%d\n", num1);

    return 0;
}

// 1


4. 포인터 반환하기

  • 포인터를 반환하려면 반환값 자료형과 함수 이름 사이에 *(애스터리스크)를 붙여주면 된다.


반환값자료형 *함수이름()
{
    return 반환값;
}
#include <stdio.h>

int *ten() // int 타입 포인터를 반환하는 ten 함수 정의
{
    int num1 = 10; // num1은 함수 ten이 끝나면 사라짐

    return &num1; // 함수에서 지역 변수의 주소를 반환하는 것은 잘못된 방법
}

int main()
{
    int *numPtr;

    numPtr = ten(); // 함수를 호출하고 반환값을 numPtr에 저장

    printf("%d\n", *numPtr); // 값이 나오긴 하지만 이미 사라진 변수를 출력하고 있음

    return 0;
}


  • 먼저 함수 반환값의 자료형을 지정할 때 int *ten()과 같이 int 타입 포인터로 지정했다.
  • 그리고 변수를 선언하고 값을 할당한 뒤 변수의 메모리 주소를 반환한다.


int *ten() // int 타입 포인터를 반환하는 ten 함수 정의
{
    int num1 = 10; // num1은 함수 ten이 끝나면 사라짐

    return &num1; // 함수에서 지역 변수의 주소를 반환하는 것은 잘못된 방법
}


  • 컴파일을 해보면 지역 변수의 주소를 반환한다는 컴파일 경고가 발생한다.
  • 즉, num1은 함수 ten 안에서만 사용할 수 있는 지역 변수이며 함수가 끝나면 사라진다.
  • 그래서 return &num1;과 같이 지역 변수의 주소를 반환하는 것은 잘못된 방법이다.


  • 포인터를 반환하려면 다음과 같이 malloc 함수로 메모리를 할당한 뒤 반환해야 한다.


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

int *ten() // int 타입 포인터를 반환하는 ten 함수 정의
{
    int *numPtr = malloc(sizeof(int)); // int 타입 크기만큼 동적 메모리 할당

    *numPtr = 10; // 역참조로 10 저장

    return numPtr; // 포인터 반환. malloc으로 메모리를 할당하면 함수가 끝나도 사라지지 않음
}

int main()
{
    int *numPtr;

    numPtr = ten(); // 함수를 호출하고 반환값을 numPtr에 저장

    printf("%d\n", *numPtr); // 메모리를 해제하기 전까지 안전함

    free(numPtr); // 다른 함수에서 할당한 메모리라도 반드시 해제해야 함

    return 0;
}

// 10


  • 함수 안에서 malloc 함수로 메모리를 할당한 뒤 역참조로 값을 저장했다.
  • 그리고 포인터를 그대로 반환한다.


int *ten() // int 타입 포인터를 반환하는 ten 함수 정의
{
    int *numPtr = malloc(sizeof(int)); // int 타입 크기만큼 동적 메모리 할당

    *numPtr = 10; // 역참조로 10 저장

    return numPtr; // 포인터 반환. malloc으로 메모리를 할당하면 함수가 끝나도 사라지지 않음
}


  • 이제 main 함수에서 ten 함수를 호출한 뒤 반환된 포인터를 numPtr에 저장한다.
  • 그리고 numPtr을 역참조해서 값을 출력해 보면 앞에서 저장한 10이 잘 출력된다.
  • 즉, 다른 함수에서 할당한 메모리라 하더라도 해제하기 전까지는 그대로 사용할 수 있다.


int main()
{
    int *numPtr;

    numPtr = ten(); // 함수를 호출하고 반환값을 numPtr에 저장

    printf("%d\n", *numPtr); // 메모리를 해제하기 전까지 안전함

    free(numPtr); // 다른 함수에서 할당한 메모리라도 반드시 해제해야 함

    return 0;
}


  • 메모리가 main이 아닌 다른 함수(ten)에서 할당되었다 하더라도 반드시 free 함수로 해제를 해준다.
  • 왜냐하면 동적 메모리는 함수를 벗어나도 계속 유지되므로 메모리를 해제하지 않으면 그대로 메모리 누수가 발생한다.


  • 이번에는 문자열을 반환하고 사용해 보자.


#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
#include <string.h> // strcpy 함수가 선언된 헤더 파일

char *helloLiteral() // char 타입 포인터를 반환하는 helloLiteral 함수 정의
{
    char *s1 = "Hello, world!";

    return s1; // 문자열 "Hello, world!"는 메모리에 저장되어 있으므로 사라지지 않음
               // 문자열 포인터 리턴
}

char *helloDynamicMemory() // char 타입 포인터를 반환하는 helloDynamicMemory 함수 정의
{
    char *s1 = malloc(sizeof(char) * 20); // char 타입 20개 크기만큼 동적 메모리 할당

    strcpy(s1, "Hello, world!"); // "Hello, world!"를 s1에 복사

    return s1; // 문자열 포인터 리턴
}

int main()
{
    char *s1;
    char *s2;

    s1 = helloLiteral();
    s2 = helloDynamicMemory();

    printf("%s\n", s1);
    printf("%s\n", s2);

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

    return 0;
}

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


  • helloLiteral, helloDynamicMemory 함수 두 개를 작성했다.
  • 먼저 helloLiteral에서는 문자열 리터럴 "Hello, world!"의 주소를 포인터에 저장한 뒤 반환했다.
  • 소스 코드상에서 입력한 문자열 리터럴은 실행 파일이 실행될 때 메모리에 저장되므로 함수가 종료되더라도 계속 사용할 수 있다.
  • helloDynamicMemory에서는 문자열 포인터에 동적 메모리를 할당한 뒤 strcpy 함수로 "Hello, world!"를 복사했다.
  • 이때는 포인터를 반환하여 문자열을 사용한 뒤에는 반드시 free 함수로 동적 메모리를 해제해야 한다.


5. void 타입 포인터 반환하기

  • 자료형에 상관없이 값을 꺼내오고 싶을 때는 void 타입 포인터를 활용하면 된다.


#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
#include <string.h> // strcpy 함수가 선언된 헤더 파일

void *allocMemory() // void 타입 포인터를 반환하는 allocMemory 함수 정의
{
    void *ptr = malloc(100); // 100바이트만큼 동적 메모리 할당

    return ptr; // void 타입 포인터 반환
}

int main()
{
    char *s1 = allocMemory();    // void 타입 포인터를 char 타입 포인터에 넣어서 문자열처럼 사용
    strcpy(s1, "Hello, world!"); // s1에 "Hello, world!" 복사
    printf("%s\n", s1);
    free(s1); // 동적 메모리 해제

    int *numPtr1 = allocMemory(); // void 타입 포인터를 int 타입 포인터에 넣어서 정수 배열처럼 사용
    numPtr1[0] = 10;              // 첫 번째 요소에 10 저장
    numPtr1[1] = 20;              // 두 번째 요소에 20 저장
    printf("%d %d\n", numPtr1[0], numPtr1[1]);
    free(numPtr1); // 동적 메모리 해제

    return 0;
}

// Hello, world!
// 10 20


  • 먼저 void *allocMemory()와 같이 함수의 반환값 자료형을 void 타입 포인터로 지정한다.
  • 그리고 함수 안에서는 void 타입 포인터에 malloc으로 100바이트만큼 메모리를 할당한 뒤 반환한다.


void *allocMemory() // void 타입 포인터를 반환하는 allocMemory 함수 정의
{
    void *ptr = malloc(100); // 100바이트만큼 동적 메모리 할당

    return ptr; // void 타입 포인터 반환
}


  • void 타입 포인터는 강제로 변환을 하지 않아도 다양한 형태의 포인터에 넣을 수 있다.


002


  • 먼저 allocMemory에서 반환된 void 타입 포인터를 문자열 포인터에 넣어서 문자열처럼 사용한다.


char *s1 = allocMemory();    // void 타입 포인터를 char 타입 포인터에 넣어서 문자열처럼 사용
strcpy(s1, "Hello, world!"); // s1에 "Hello, world!" 복사
printf("%s\n", s1);
free(s1); // 동적 메모리 해제


  • 다시 allocMemory에서 반환된 void 타입 포인터를 int 타입 포인터에 넣어서 정수 배열처럼 사용할 수도 있다.
  • 단, allocMemory에서 100바이트만큼 메모리를 할당했으므로 100바이트 이상 값을 넣으면 안 된다.


int *numPtr1 = allocMemory(); // void 타입 포인터를 int 타입 포인터에 넣어서 정수 배열처럼 사용
numPtr1[0] = 10;              // 첫 번째 요소에 10 저장
numPtr1[1] = 20;              // 두 번째 요소에 20 저장
printf("%d %d\n", numPtr1[0], numPtr1[1]);
free(numPtr1); // 동적 메모리 해제


  • allocMemory 안에서 malloc을 사용하고 있으므로 allocMemory에서 반환된 포인터를 사용할 때마다 반드시 free 함수로 메모리를 해제해 준다.


코드 줄이기

  • 코드를 줄이려면 ptr 변수를 선언하지 않고, malloc 함수를 호출하면서 바로 반환해도 된다.
  • 이렇게 하면 malloc 함수에서 반환된 값을 다시 반환한다.


void *allocMemory()
{
    return malloc(100); // malloc 함수를 호출하면서 바로 반환
}


6. 구조체와 구조체 포인터 반환하기

  • C 에서 함수는 값을 하나만 반환할 수 있다.
  • 그래서 인적 정보를 반환값으로 얻어오려면 다음과 같이 작성할 수 있다.


char *getName()
int getAge()
char *getAddress()


  • 하지만 이름, 나이, 주소를 각각 얻어오려면 항목마다 함수를 만들어야 해서 비효율적이다.
  • 이때는 함수에서 반환값으로 구조체를 활용하면 편리하다.
  • 함수에서 구조체를 반환하면 데이터를 묶어서 한 번에 가져올 수 있다.


struct 구조체이름 함수이름()
{
    return 구조체변수;
}
#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strcpy 함수가 선언된 헤더 파일

struct Person
{
    char name[20];
    int age;
    char address[100];
};

struct Person getPerson() // Person 구조체를 반환하는 getPerson 함수 정의
{
    struct Person p;

    strcpy(p.name, "홍길동");
    p.age = 30;
    strcpy(p.address, "서울시 용산구 한남동");

    return p; // 구조체 변수 반환
}

int main()
{
    struct Person p1;

    p1 = getPerson(); // 반환된 구조체 변수의 내용이 p1로 모두 복사됨

    // getPerson에서 저장한 값이 출력됨
    printf("이름: %s\n", p1.name);
    printf("나이: %d\n", p1.age);
    printf("주소: %s\n", p1.address);

    return 0;
}

// 이름: 홍길동
// 나이: 30
// 주소: 서울시 용산구 한남동


  • 구조체를 반환하는 함수를 정의하려면 struct Person getPerson()과 같이 struct 키워드와 구조체 이름을 함수 이름 앞에 붙여주면 된다.
  • 그리고 다음과 같이 함수 안에서 Person 구조체 변수를 선언하고 값을 저장한 뒤 반환한다.


struct Person getPerson() // Person 구조체를 반환하는 getPerson 함수 정의
{
    struct Person p;

    strcpy(p.name, "홍길동");
    p.age = 30;
    strcpy(p.address, "서울시 용산구 한남동");

    return p; // 구조체 변수 반환
}


  • main 함수에서 getPerson 함수를 호출하여 반환값을 변수 p1에 저장한 뒤 printfp1의 멤버를 출력해 보면 getPerson에서 저장한 값들이 출력된다.


int main()
{
    struct Person p1;

    p1 = getPerson(); // 반환된 구조체 변수의 내용이 p1로 모두 복사됨

    // getPerson에서 저장한 값이 출력됨
    printf("이름: %s\n", p1.name);
    printf("나이: %d\n", p1.age);
    printf("주소: %s\n", p1.address);

    return 0;
}


  • 구조체 변수를 반환한 뒤 다른 변수에 저장하면 반환된 구조체의 내용을 모두 복사하게 된다.
  • 여기서는 Person 구조체의 멤버가 세 개뿐이라 큰 문제가 없지만 구조체 크기가 커지면 복사할 공간이 그만큼 더 필요하게 되어 공간이 낭비되므로 비효율적이다.


구조체 변수의 메모리 주소 반환

  • 함수가 끝나면 구조체 변수도 사라진다.
  • 따라서 &(주소 연산자)로 구조체 변수의 메모리 주소를 반환하면 안 된다.


  • 구조체를 반환할 때는 구조체 복사가 일어나지 않도록 malloc 함수로 동적 메모리를 할당한 뒤 구조체 포인터를 반환하는 것이 좋다.
  • 먼저 구조체 포인터를 반환하는 함수는 구조체 이름과 함수 이름 사이에 *를 붙이면 된다.


struct 구조체이름 *함수이름()
{
    return 구조체포인터;
}
#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strcpy 함수가 선언된 헤더 파일
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일

struct Person
{
    char name[20];
    int age;
    char address[100];
};

struct Person *allocPerson() // Person 구조체 포인터를 반환하는 allocPerson 함수 정의
{
    struct Person *p = malloc(sizeof(struct Person)); // 구조체 포인터에 동적 메모리 할당

    strcpy(p->name, "홍길동");
    p->age = 30;
    strcpy(p->address, "서울시 용산구 한남동");

    return p; // 구조체 포인터 반환
}

int main()
{
    struct Person *p1;

    p1 = allocPerson(); // 포인터를 반환하여 p1에 메모리 주소 저장

    // allocPerson에서 저장한 값들이 출력됨
    printf("이름: %s\n", p1->name);
    printf("나이: %d\n", p1->age);
    printf("주소: %s\n", p1->address);

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

    return 0;
}

// 이름: 홍길동
// 나이: 30
// 주소: 서울시 용산구 한남동


  • 먼저 struct Person *allocPerson()과 같이 반환값 자료형을 구조체 포인터로 지정해 준다.
  • 그리고 함수 안에서 구조체 포인터에 메모리를 할당하고 값을 저장한 뒤 구조체 포인터를 반환한다.


struct Person *allocPerson() // Person 구조체 포인터를 반환하는 allocPerson 함수 정의
{
    struct Person *p = malloc(sizeof(struct Person)); // 구조체 포인터에 동적 메모리 할당

    strcpy(p->name, "홍길동");
    p->age = 30;
    strcpy(p->address, "서울시 용산구 한남동");

    return p; // 구조체 포인터 반환
}


  • 이제 main 함수에서 allocPerson 함수를 호출한 뒤 반환된 포인터를 p1에 저장한다.
  • 이렇게 하면 p1에서 ->(화살표 연산자)로 멤버에 접근할 수 있고, 값을 출력해 보면 앞에서 저장한 값들이 잘 출력된다.
  • 즉, allocPerson에서 메모리를 할당한 뒤 메모리 주소가 들어있는 포인터만 반환하여 사용하므로 구조체의 내용을 모두 복사하지 않아서 훨씬 효율적이다.


int main()
{
    struct Person *p1;

    p1 = allocPerson(); // 포인터를 반환하여 p1에 메모리 주소 저장

    // allocPerson에서 저장한 값들이 출력됨
    printf("이름: %s\n", p1->name);
    printf("나이: %d\n", p1->age);
    printf("주소: %s\n", p1->address);

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

    return 0;
}


  • 메모리 사용이 끝났으면 free 함수로 해제를 해준다.


구조체 별칭 사용하기

  • 구조체 포인터 별칭(구조체 별칭)을 정의했다면 함수의 반환값 자료형에 구조체 포인터 별칭을 그대로 지정해 주면 된다.


typedef struct _Person
{
    char name[20];
    int age;
    char address[100];
} Person, *PPerson; // 구조체 별칭 Person, 구조체 포인터 별칭 PPerson

PPerson allocPerson() // Person 구조체 포인터의 별칭을 반환값 자료형으로 지정
{
    PPerson p = malloc(sizeof(Person)); // 구조체 포인터에 동적 메모리 할당

    strcpy(p->name, "홍길동");
    p->age = 30;
    strcpy(p->address, "서울시 용산구 한남동");

    return p; // 구조체 포인터 반환
}


공용체와 열거형 반환하기

  • 다음과 같이 함수에서 공용체와 열거형도 반환할 수 있다.


#include <stdio.h>

union Box
{
    short candy;
    float snack;
    char doll[8];
};

enum BOX_TYPE
{
    BOX_PAPER = 0,
    BOX_WOOD,
    BOX_PLASTIC
};

union Box getBox() // Box 공용체를 반환하는 getBox 함수 정의
{
    union Box b; // 공용체 변수 선언

    b.candy = 10;

    return b; // 공용체 변수 반환
}

enum BOX_TYPE getBoxType() // BOX_TYPE 열거형을 반환하는 getBoxType 함수 정의
{
    return BOX_WOOD;
}

int main()
{
    union Box box;
    enum BOX_TYPE boxType;

    box = getBox();         // 반환된 공용체 변수의 내용이 box로 모두 복사됨
    boxType = getBoxType(); // 반환된 열거형 값이 boxType에 저장됨

    printf("%d\n", box.candy); // getBox에서 저장한 값
    printf("%d\n", boxType);   // getBoxType에서 반환한 BOX_WOOD

    return 0;
}

// 10
// 1

References