printf 함수를 직접 구현하면서 C언어 가변인자에 대해 공부해 보았다.
가변인자는 함수를 정의할 때, 그 함수가 받는 인자의 개수를 고정하지 않고 유동적으로 처리할 수 있게 해준다. 이는 함수를 더욱 유연하게 만들어주며, 특히 많은 수의 인자를 처리해야 하는 함수를 작성할 때 유용하다.
#include <stdarg.h>
#include <stdio.h>
// 가변인자를 이용하여 정수들의 합을 계산하는 함수
int sum(int count, ...) {
va_list ptr;
va_start(ptr, count);
int total = 0;
for (int i = 0; i < count; ++i) {
int num = va_arg(ptr, int); // 가변인자로부터 정수를 가져옴
total += num;
}
va_end(ptr);
return total;
}
int main() {
// 다양한 개수의 정수를 전달하여 합을 계산
printf("Sum of 3, 5, 7, 9, 11: %d\n", sum(5, 3, 5, 7, 9, 11));
printf("Sum of 2, 4, 6, 8: %d\n", sum(4, 2, 4, 6, 8));
printf("Sum of 1, 2, 3, 4, 5, 6, 7, 8, 9, 10: %d\n", sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
return 0;
}
위와 같이 sum
함수는 처음 인자로 정수의 개수를 받고, 이후에 나오는 인자들을 가변인자로 처리하여 합을 계산한다. 이렇게 가변인자를 사용한 함수는 임의의 개수의 인자를 전달받아 처리할 수 있다.
가변인자를 사용하기 위해서는 몇 가지 필수 요소가 있다.
stdarg.h
헤더 파일에 정의된va_start
,va_arg
,va_end
와 같은 매크로들이 이에 해당한다. 이러한 매크로들을 이용하여 가변인자를 초기화하고, 각 인자에 접근하여 처리할 수 있다.
stdarg.h 헤더 파일: stdarg.h
헤더 파일에는 가변인자를 다루기 위한 매크로와 함수들이 정의되어 있어 꼭 포함해줘야한다.
va_list: va_list
타입은 가변인자 목록을 가리키는 일종의 포인터이다.
va_start 매크로: 가변인자 목록을 초기화하는 역할을 한다. 가변인자 목록을 순회하기 전에 va_start
매크로를 사용하여 초기화해야 한다.
int sum(int count, ...) {
va_list ptr;
va_start(ptr, count);
위와 같이 마지막 고정인자를 인자로 가지는데 va_start 내부에서 마지막 고정인자의 끝 다음부분으로 주소를 이동시켜 가변인자의 시작주소를 ptr에 저장한다.
va_arg 매크로: 가변인자 목록에서 다음 가변인자의 값을 가져오는 역할을 한다. 이 매크로는 가져올 인자의 타입과 가변인자 목록을 가리키는 포인터를 인자로 받는다. 가져온 값은 해당 타입으로 반환된다.
va_arg(가변인자포인터, 가져올 타입)
//정수
int num;
num = va_arg(ptr, int);
//단어
char c;
c = (char)va_arg(ptr, int);
//문자열
char *s;
s = va_arg(ptr, char *);
이때 char 타입의 인자를 받을 때에도 va_arg(ptr, int)
와 같이 int 타입으로 값을 가져와야 한다는 점을 ⚠️주의하자.
그 이유는 C 언어의 가변인자 처리 규칙에 따라, 작은 크기의 정수 타입들은 자동으로 큰 크기의 정수 타입으로 확장되므로 char 타입은 int 타입으로 자동 확장되어 전달되기 때문이라고 한다.
가변인자 함수의 대표적인 예시로는 C 언어의 printf
함수가 있다. printf
함수는 서식 지정자를 이용하여 출력 형식을 지정하고, 이후에 오는 가변인자들을 출력한다. 예를 들어, "Hello, %s!"
와 같은 형식 문자열에서 %s
는 문자열을 출력하기 위한 서식 지정자이며, 이에 대응하는 인자가 출력되게 된다.
#include <stdarg.h>
#include <unistd.h>
#include <stdio.h>
//인자의 포맷 체크
void check_format(const char *format, va_list ptr)
{
if (*format == 'c')
{
char c = (char)va_arg(ptr, int);
write(1,&c,1);
}
else if (*format == 's')
{
char *c = va_arg(ptr, char *);
while(*c)
{
write(1,c,1);
c++;
}
}
}
//%c, %s만 처리하는 간단한 printf구현
int ft_printf(const char *format,...)
{
va_list ptr;
va_start(ptr,format);
while (*format)
{
if (*format == '%')
{
format++;
check_format(format, ptr, &len);
format++;
}
else
{
write(1,format,1);
format++;
}
}
va_end(ptr);
return (1);
}
int main(void)
{
ft_printf("Hello, %c bye",'a');
ft_printf("Hello, %s bye","kim");
}
매번 사용하던 printf 함수가 어떤 원리로 다양한 개수의 인자를 처리할 수 있는지 알 수 있었다.
또한 가변인자는 함수의 유연성을 높여주지만 잘못 사용할 경우 버그의 원인이 될 수 있으므로 조심스럽게 다뤄야 하며, 특히 타입 일치와 같은 주의사항을 유의해야겠다.