0. 과제 설명

printf 함수를 구현한다.



1. 가변 인자(variable argument)

가변 인자는 말 그대로 개수가 변할 수 있는 인자이다.
함수 printf를 사용하는 경우를 생각해보자.

#include <stdio.h>

int main()
{
	printf("%s %d\n", "this is an example.", 123);
	printf ("%d\n", 456);
}

위 코드에서 첫 번째 printf에 "%s %s\n", "this is an example", 123 3개의 인자가 들어갔다. 하지만 두 번째 printf에는 "%d\n", 456 2개의 인자가 들어갔다. printf 함수는 첫 번째 인자 문자열에 주어지는 %의 수에 따라 추가적인 인자가 필요하므로, printf 함수를 만들 때 사용자가 몇 개의 인자를 넣을 것인지 미리 알 수 없다. 이렇게 상황에 따라 함수가 받는 인자의 수를 조절하고 싶을 때 가변 인자를 사용할 수 있다.

1-1. 가변 인자 사용법

#include <stdarg.h>


가변 인자에 관한 함수와 구조체는 stdarg.h 헤더에 들어 있다.
가변 인자를 갖는 함수의 프로토 타입은 다음과 같다.

int func(int num_args, ...);


가변 인자는 몇 개가 될지 모르므로 ...으로 표시한다.
최소 1개의 고정 인수(위의 경우 num_args)가 필요하다는 점에 유의하자. 이 고정 인수는 뒤에 올 가변 인자의 개수를 알려주는 역할을 한다.

1-2. 매크로

stdarg.h에는 가변 인수를 사용하기 위한 다양한 매크로가 존재한다.

1-2-1. va_list

va_list는 가변 인수들의 정보를 담아 놓기 위한 타입이다. 길이가 변할 수 있는 인수들을 저장해야 하기 때문에 va_list 또한 저장 공간의 크기를 유동적으로 늘리고 줄일 수 있는 가변 공간이다.

⚠️ 클러스터 Mac(Intel Mac)에서는 va_list 자료형 자체로 포인터지만, M1 Mac에서는 포인터가 아닌 일반 자료형이다. 따라서 va_list를 통해 가변 인자 값에 접근하는 방식에 있어서 차이가 발생한다.

1-2-2. va_start

va_start는 va_list를 사용하기 전에 초기화하는 역할을 한다. 왜 va_list를 초기화해야 할까?
va_list가 가리키고 있는 주소를 마지막 고정 인자 다음의 첫 번째 가변 인자의 주소로 설정해야 하기 때문이다(va_list는 포인터 임을 잊지 말자).
따라서 va_start는 va_list, 마지막 고정인자 2개의 인자를 필요로 한다.

void func(int n, ...)
{
	va_list ap;

	va_start(ap, n); // ap의 주소를 고정 인자 n 다음, 즉 첫 번째 가변 인자의 주소로 옮긴다.
}


1-2-3. va_arg

va_list가 현재 가리키고 있는 인자를 반환하고, va_list의 주소를 다음 인자로 이동시킨다. 첫 번째 인자로 va_list, 두 번째 인자로 현재 인자의 type을 입력받는다.

void func(int n, ...)
{
	int ret;
	va_list ap;

	va_start(ap, n);
	ret = va_arg(ap, int); //ap에서 int 타입의 인자를 반환한다.
}


🔔 va_arg로 char타입 인자를 반환받는 경우, 아래와 같이 char형이 아닌 int 자료형을 두 번째 인자로 넣어줘야 한다. CPU는 메모리를 4bytes씩 접근하는데, char는 1byte만을 차지하기 때문에, 3bytes의 남는 공간이 생긴다. 하지만 1byte만 char형을 넣어놓고 바로 이어서 4bytes인 int형을 넣어 놓는다면, 메모리가 char형을 읽고 다음 4bytes 위치의 메모리에 접근했을 때 int 변수의 시작 주소가 아닌 중간 주소에 접근하는 꼴이 되므로 효율적인 메모리 접근이 불가능하다. 따라서 CPU의 메모리 접근 크기보다 작은 char형은 실질적인 내용은 1byte 밖에 없지만 4bytes의 공간을 차지하도록 할당한다.

char empty empty empty int int int int
메모리 접근 ———— ———— ———— 메모리 접근 ———— ———— ————
char c;

c = va_arg(ap, int);


1-2-4. va_end

가변 인자를 모두 사용하고 난 후 실행하는 함수로, ap의 값을 NULL로 변경한다.

va_end(ap);


2. 예시 코드

#include "ft_printf.h"

int ft_printf(const char *format, ...)
{
	va_list ap;
	size_t bytes;

	va_start(ap, format);
	bytes = read_format(format, ap); 
	// read_format : format을 해석하고, 이에 맞게 출력 후 출력한 글자 byte 수를 리턴해주는 함수
	va_end(ap);
	return (bytes);
}

Leave a comment