56. 함수 포인터 매개변수
1. 키워드
- 포인터 매개변수
2. 함수에서 포인터 매개변수 사용하기
- C에서 값을 여러 개 반환하는 함수를 만들 때는 주로 포인터 매개변수를 사용한다.
- 지금까지 매개변수는 함수에 값을 전달할 때 사용했고, 함수 바깥에서 값을 가져오는 용도로는 사용할 수 없었다.
- 즉, 다음과 같이 두 값을 더하는
add
함수는 반환값이1
개이고, 매개변수a
와b
는 함수 안에서만 사용할 수 있었다.
- 하지만 함수에서 포인터 매개변수를 사용하면 함수 안에서 바뀐 내용을 함수 바깥에서도 알 수 있다.
- 따라서 함수 바깥으로 값을 전달할 수 있다.
- 다음은 함수에서 포인터 매개변수를 사용하여 두 변수의 값을 서로 바꾸는
swapNumber
함수이다.
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
를 이용하여first
와second
에 저장된 값을 서로 바꾸었다.
void swapNumber(int first, int second) // 반환값 없음, int 타입 매개변수 두 개 지정
{
int temp; // 임시 보관 변수
temp = first;
first = second;
second = temp;
}
swapNumber
를 호출할 때10
이 들어있는num1
과20
이 들어있는num2
를 넣었다.- 그 뒤
printf
로 값을 출력해 보면num1
과num2
의 값이 서로 바뀌지 않고10
과20
이 그대로 출력되는 것을 볼 수 있다. - 즉, 매개변수는 값을 전달하는 역할만 할 뿐 함수 바깥의 변수와는 상관이 없다.
int num1 = 10;
int num2 = 20;
swapNumber(num1, num2); // 변수 num1과 num2를 넣어줌
printf("%d %d\n", num1, num2); // swapNumber 함수와는 상관없이 처음 저장한 10과 20이 출력됨
- 아무리
swapNumber
를 호출해 봐야num1
과num2
의 값은 바뀌지 않는다.
- 이번에는 매개변수를 포인터로 사용해 보자.
- 다음과 같이
()
(괄호) 안에서 자료형 뒤에*
(애스터리스크)를 붙여서 매개변수를 포인터 형태로 만든다.
#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
와 같이 포인터 형식으로 지정해 준다. - 그리고 함수 안에서 매개변수를 역참조하여
first
와second
의 값을 바꾼다.
void swapNumber(int *first, int *second) // 반환값 없음, int 타입 포인터 매개변수 두 개 지정
{
int temp; // 임시 보관 변수
// 역참조로 값을 가져오고, 값을 저장함
temp = *first;
*first = *second;
*second = temp;
}
num1
에는10
,num2
에는20
이 들어있다.- 이 상태에서
&
(주소 연산자)를 사용하여swapNumber
에num1
과num2
의 메모리 주소를 넣어준다.
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
에 의해서num1
과num2
의 값이 서로 바뀐 것을 알 수 있다.- 즉, 변수의 메모리 주소를 함수에 전달하면 함수 안에서는 메모리 주소에 접근하여 값을 저장할 수 있다.
- 함수의 반환값은 값을 하나만 반환할 수 있는데, 이처럼 매개변수를 포인터로 사용하면 함수 바깥으로 여러 개의 값을 전달할 수 있다.
- 지금까지
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
은 다음과 같이 정의되어 있으며, 컴파일할 때는 아무런 영향을 주지 않는다.
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
에 메모리를 할당하게 된다.
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!"
와 같이 문자열을 넣어주면 문자열의 주소가 매개변수에 전달된다.
- 다음과 같이 배열 형태의 문자열도 문자열 포인터 매개변수에 전달할 수 있다.
- 이때도 매개변수는
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[]
과 같이 지정하더라도 배열뿐만 아니라 문자열이나 메모리가 할당된 포인터도 전달할 수 있다.