int ft_printf(const char *, ...);
요구 사항:
printf()의 버퍼 관리 기능을 구현하지 마십시오.c, s, p, d, i, u, x, X, %.printf()와 비교될 것입니다.ar 명령어를 사용하여 라이브러리를 생성해야 합니다.libtool 명령어 사용은 금지됩니다.libftprintf.a 라이브러리는 저장소의 루트에 생성되어야 합니다.구현해야할 변환:
%c : 단일 문자를 출력합니다.
%s : 문자열을 출력합니다 (일반적인 C 규칙에 따름).
%p : void * 포인터 인수를 16진수 형식으로 출력합니다.
%d : 십진수(기본 10) 숫자를 출력합니다.
%i : 0진수(기본 10) 정수를 출력합니다.
%u : 부호 없는 십진수(기본 10) 숫자를 출력합니다.
%x : 숫자를 소문자 형식의 16진수(기본 16)로 출력합니다.
%X : 숫자를 대문자 형식의 16진수(기본 16)로 출력합니다.
%% : 퍼센트 기호를 출력합니다
%d와%i는 출력에서는 동일한 기능을 수행하지만scanf에서의 차이를 가집니다.
%d는scanf에서 10진 정수로 해석합니다.
%i는scanf에서 자동으로 진수를 인식합니다 (0으로 시작하면 8진수, 0x로 시작하면 16진수, 그 외엔 10진수)
가변 인수 는 함수가 받을 인수의 개수가 고정되어 있지 않고, 호출 시에 필요한 만큼의 인수를 전달할 수 있는 함수 인수를 의미합니다. C 언어에서는 printf나 scanf같은 함수가 대표적인 가변 인수 함수를 사용한 예입니다.
printf("Hello, World!\n"); // 인수 0개
printf("Number: %d\n", 10); // 인수 1개
printf("Two numbers: %d, %d\n", 10, 20); // 인수 2개
printf("Three numbers: %d, %d, %d\n", 10, 20, 30); // 인수 3개
//이것만 봐도 인수의 개수가 고정되어있지 않다는것을 알 수 있다.
가변 인수는 정해지지 않은 수의 인수를 받을 수 있는 함수를 작성할 때 유용합니다. 이를 위해 C에서는 stdarg.h 헤더 파일에서 제공하는 va_list, va_start, va_arg, va_end 등의 매크로를 사용합니다.
이러한 가변 인수를 처리하기 위해 마찬가지로 stdarg.h 헤더 파일에 정의되어 있는 va_list를 사용합니다.
이 타입은 가변 인수 리스트를 관리하기 위한 구조체 또는 포인터 타입으로, 컴파일러에 따라 구현이 달라질 수 있습니다.
printf("Number: %d, Character: %c, String: %s", 42, 'A', "Hello");
위와 같은 호출에서, 인수들은 메모리에 순서대로 저장됩니다. 즉, 42, 'A', "Hello"가 메모리에 차례대로 위치합니다.
형식 지정자(%d, %c, %s)는 저장되지 않음: printf나 ft_printf는 형식 지정자 문자열을 보고 가변 인수를 해석할 뿐, 이 형식 지정자는 가변 인수 리스트에 포함되지 않습니다. 형식 지정자는 함수 내부에서 format 문자열을 순차적으로 읽으면서 다음 인수를 어떻게 해석할지 결정하는 역할만 합니다.
그럼 가변 인수 리스트가 어떻게 처리되는지 단계별로 살펴보겠습니다.
format 문자열에서 %d를 만나면, va_arg(args, int)로 첫 번째 인수 42를 int로 해석하여 가져옵니다.
format 문자열에서 %c를 만나면, va_arg(args, int)로 두 번째 인수 'A'를 가져옵니다 (char도 자동으로 int로 승격되어 저장됨).
format 문자열에서 %s를 만나면, va_arg(args, char *)로 세 번째 인수 "Hello"를 char *로 해석하여 가져옵니다.
1. 정수형 자동 승격:
char 및 short 타입의 정수는 자동으로 int로 승격됩니다.
unsigned char및 unsigned short도 자동으로 unsigned int로 승격됩니다.
승격 이유: int는 대부분의 시스템에서 기본 연산 자료형으로, 메모리 접근과 연산이 최적화되기 때문입니다.
2. 부동 소수점 자동 승격:
float 타입의 인수는 항상 double로 자동 승격됩니다.
이유: float은 연산 시 double로 변환되는 것이 일반적이며, 메모리 연산에서도 double이 효율적입니다.
3. 포인터와 기타 자료형:
char *(문자열)이나 void *(일반 포인터) 같은 포인터 타입은 승격되지 않습니다. 따라서 가변 인수에서 char *로 선언된 문자열은 그대로 char *로 전달됩니다.
이 외에 long, long long 등의 타입은 승격되지 않고, 그 자체로 전달됩니다.
사용 가능한 외부 함수:
malloc, free, write, va_start, va_arg, va_copy, va_end
1. va_start :
역할 : 가변 인수 리스트를 시작합니다.
구문: void va_start(va_list ap, last);
ap : 가변 인수 리스트의 시작 지점으로, 리스트의 첫 인수를 가리킨다 (argument point의 약자!)last : 고정 인수 중 마지막 인수(변동 인수 바로 전에 위치).설명: 함수의 고정 인수 뒤에 오는 가변 인수를 ap에 설정해 사용할 수 있도록 초기화합니다. va_start는 반드시 가변 인수 함수 내에서 최초로 호출되어야 합니다.
2. va_arg :
역할 : 다음 인수를 가져옵니다.
구문: type va_arg(va_list ap, type);
ap : 가변 인수 리스트를 가리키는 포인터입니다.type : 인수의 데이터 타입입니다(예: int, double).설명: va_arg 는 ap가 가리키는 현재 위치의 인수를 읽어오고, 다음 인수로 이동시킵니다.
3. va_copy :
역할 : 가변 인수 리스트를 복사합니다.
구문: void va_copy(va_list dest, va_list src);
dest: 복사할 가변 인수 리스트.src : 원본 가변 인수 리스트.설명 : va_copy는 ap가 가리키는 위치를 다른 va_list 변수로 복사해 별도의 가변 인수 리스트로 사용할 수 있도록 합니다.
4. va_end :
역할 : 가변 인수 처리가 끝났음을 알리고 메모리를 해제합니다.
구문: void va_end(va_list ap);
설명 : va_end 는 가변 인수 리스트를 해제하고, 자원을 정리합니다. 반드시 va_start 이후 가변 인수 처리가 끝나면 호출해야 합니다.
#include <stdarg.h>
#include <stdio.h>
int sum(int count, ...) { // count: 고정 인수 (총 가변 인수 개수)
va_list args; // 가변 인수 리스트를 선언
int total = 0;
va_start(args, count); // args를 가변 인수 리스트의 시작 지점으로 초기화
// 가변 인수 리스트에서 count만큼의 정수를 읽어서 합산
for (int i = 0; i < count; i++) {
total += va_arg(args, int); // 각 인수를 순서대로 가져와 total에 더함
}
va_end(args); // 가변 인수 리스트 종료
return total;
}
int main() {
printf("Sum of 1, 2, 3: %d\n", sum(3, 1, 2, 3)); // count = 3, 1 + 2 + 3 = 6
printf("Sum of 10, 20: %d\n", sum(2, 10, 20)); // count = 2, 10 + 20 = 30
return 0;
}

va_list는 한 방향으로만 순회할 수 있기 때문에, va_arg로 한 번 인수를 읽으면 ap가 다음 인수로 이동합니다. 다시 처음 인수부터 읽고 싶다면 초기 위치로 되돌리는 방법이 필요한데, 이때 va_copy를 사용하여 va_list를 복사해 두면 같은 인수 리스트를 처음부터 다시 읽을 수 있습니다.
예를 들어, 함수에서 가변 인수 리스트를 한 번은 출력 용도로, 또 한 번은 다른 계산을 위해 반복해서 사용해야 하는 경우가 있을 수 있습니다.
그리고 함수를 호출할 때 가변 인수 리스트를 넘겨야 하는 경우, 원본 va_list를 그대로 넘기면, 원본 리스트가 그 함수 내에서 소진될 수 있습니다. 이럴 때 va_copy를 사용해 리스트를 복사하여 넘기면, 원본 va_list는 유지되고 복사된 리스트는 전달된 함수 내에서 사용될 수 있습니다.
#include <stdarg.h>
#include <stdio.h>
void sum_and_average(int count, ...) {
va_list ap, ap_copy;
int sum = 0;
va_start(ap, count);
// 원본 va_list를 복사
va_copy(ap_copy, ap);
// 첫 번째 순회: 합을 구함
for (int i = 0; i < count; i++) {
sum += va_arg(ap, int);
}
// va_list를 끝냈으니 해제
va_end(ap);
// 두 번째 순회: 평균을 구함
printf("Sum: %d\n", sum);
printf("Average: %.2f\n", sum / (float)count);
// 복사된 va_list도 사용 후 반드시 해제해야 함
va_end(ap_copy);
}
int main() {
sum_and_average(4, 10, 20, 30, 40);
return 0;
}