54. 함수 반환값
1. 키워드
- 반환값(Return Value)
2. 함수에서 반환값 사용하기
- 함수에서 값을 꺼내올 때는 반환값을 이용한다.
- 함수 입장에서 보자면 반환값은 함수를 호출해 준 바깥에 결괏값을 알려주기 위해 사용한다.
3. 정수, 실수, 불 반환값 사용하기
- 반환값은 함수를 정의할 때 반환값의 자료형을 지정해 주고, 함수 안에서
return
키워드로 값을 반환하면 된다.
- 한 가지 중요한 점은 반환값과 반환값의 자료형이 일치해야 한다는 점이다.
- 즉, 반환값의 자료형을 반환값의 형태와 크기에 따라 결정한다.
- 이제 반환값을 가지는 함수를 호출해 보자.
#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
을 반환했다.
- 반환값을 저장하려면 반환값을 저장할 변수에
=
(할당 연산자)를 사용한 뒤 함수를 호출해 주면 된다.
- 반환값이 변수에 저장되는 과정을 그림으로 표현하면 다음과 같은 모양이 된다.
- 만약 반환값을 변수에 저장하지 않고 바로 사용하고 싶다면 다음과 같이
printf
함수 안에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
으로 불값을 반환하면 된다.
반환값의 자료형과 자료형 변환
- 만약 반환값의 자료형과 반환하는 값의 자료형이 다르다면 형 변환을 하면서 반환하면 된다.
#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
- 함수 반환값의 자료형과 반환값을 저장할 변수의 자료형이 다를 때도 있다.
- 이때도 다음과 같이 자료형을
()
(괄호)로 묶은 뒤 함수 앞에 붙여서 반환값의 자료형을 강제로 변환한다.
4. 포인터 반환하기
- 포인터를 반환하려면 반환값 자료형과 함수 이름 사이에
*
(애스터리스크)를 붙여주면 된다.
#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
타입 포인터는 강제로 변환을 하지 않아도 다양한 형태의 포인터에 넣을 수 있다.
- 먼저
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
함수에서 반환된 값을 다시 반환한다.
6. 구조체와 구조체 포인터 반환하기
- C 에서 함수는 값을 하나만 반환할 수 있다.
- 그래서 인적 정보를 반환값으로 얻어오려면 다음과 같이 작성할 수 있다.
- 하지만 이름, 나이, 주소를 각각 얻어오려면 항목마다 함수를 만들어야 해서 비효율적이다.
- 이때는 함수에서 반환값으로 구조체를 활용하면 편리하다.
- 함수에서 구조체를 반환하면 데이터를 묶어서 한 번에 가져올 수 있다.
#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
에 저장한 뒤printf
로p1
의 멤버를 출력해 보면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
함수로 동적 메모리를 할당한 뒤 구조체 포인터를 반환하는 것이 좋다. - 먼저 구조체 포인터를 반환하는 함수는 구조체 이름과 함수 이름 사이에
*
를 붙이면 된다.
#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