printf는 C 언어에서 출력 형식을 지정하여 데이터를 출력하는 데 사용되는 함수입니다.
기본적인 사용법은 문자열에 포함된 형식 지정자를 통해 다양한 데이터 타입을 출력할 수 있는 기능을 제공한다.
헤더는 stdio.h 사용하고 함수 호출은 기본형태가 이렇게 int printf(const char *format, ...); 되어 있다.
자세한내용은 man printf 메뉴얼을 참고하면 된다.
ft_printf 함수의 프로토타입은 아래와 같다.
int ft_printf(const char *, ...);
ft_printf함수에서 구현 요구를하는 형식 지정자는 다음과 같다.
%c: 단일 문자를 출력합니다.
%s: 문자열을 출력합니다 (일반적인 C 관례에 따라 정의됨).
%p: void * 포인터 인자는 16진수 형식으로 출력해야 합니다.
%d: 10진수(base 10) 숫자를 출력합니다.
%i: 10진수로 integer를 출력합니다.
%u: 부호 없는 10진수 숫자를 출력합니다.
%x: 16진수(base 16) 소문자 형식으로 숫자를 출력합니다.
%X: 16진수(base 16) 대문자 형식으로 숫자를 출력합니다.
%%: 퍼센트 기호를 출력합니다.
코드 설명에 앞서 printf의 반환값(return)을 알아 보았다.
printf 함수는 출력한 문자의 개수를 반환한다
printf("%s", "abcde");
이 경우 출력된 문자는 abcde, printf의 반환값은 5이다.
아래 코드는 함수를 구성하는 메인 코드이다.
int ft_printf(const char *format, ...) { va_list args; va_start(args, format); auto int i = 0; auto int len = 0; while (format[i] != '\0') { if (format[i] != '%') len += ft_print_char(format[i]); else if (format[i++] == '%') { auto int len_buffer = ft_vaprint(format[i], args); if (len_buffer < 0) { va_end(args); return (-1); } len += len_buffer; } i++; } va_end(args); return (len); }
입력 처리 : 가장 먼저, ft_printf 함수가 호출되면 가변 인자를 처리하기 위해 va_list 변수를 초기화한다. va_list에 대해 알고 싶은 내용은 여기를 참고하면 된다!
문자열 분석 : 입력받은 문자열을 한 글자씩 순회하며, 일반 문자와 형식 지정자를 구분한다. 일반 문자는 그대로 출력하고, 형식 지정자를 만나면 해당하는 처리 함수로 분기한다.
반환값 : len 변수에 문자열을 읽을 때 마다 +하여 읽은 문자수만큼 카운팅 되어 최종 반환값에 len이 들어간다. 형식지정자 처리하는 함수ft_vapirnt에서 len_buffer로 읽은 문자수를 반환하여 len에 더해져 출력된 정확한 문자수를 확인한다.
int ft_vaprint(const char c, va_list args) { int len; len = 0; if (c == 'c') len = ft_print_char((unsigned char)va_arg(args, int)); else if (c == 's') len = ft_print_str(va_arg(args, char *)); else if (c == 'p') len = ft_print_ptraddr((uintptr_t)va_arg(args, void *)); else if (c == 'd' || c == 'i') len = ft_print_num(va_arg(args, int)); else if (c == 'u') len = ft_print_unum(va_arg(args, unsigned int)); else if (c == 'x' || c == 'X') len = ft_print_hex(va_arg(args, unsigned int), c); else if (c == '%') len = ft_print_char('%'); return (len); }
변환 형식 확인 함수 : ft_vaprint 함수에서는 % 문자 다음 나오는 형식 지정자를 확인 하여 각 형식에 맞는 함수를 호출하여 출력한다. len에 출력 문자수를 저장하여 반환해준다.
각 데이터 타입에 대한 변환은 각 타입에 맞는 함수에서 이루어진다.
아래 함수들은 다양한 데이터 타입을 처리하는 기능을 가지며, 각 형식 지정자에 맞는 변환 로직을 담당한다.
int ft_print_char(unsigned char c) { return (write(1, &c, 1)); }
int ft_print_str(char *s) { int i; i = 0; if (s == NULL) s = "(null)"; while (s[i] != '\0') { ft_print_char((unsigned char)s[i]); i++; } return (i); }
int ft_print_num(int i) { char *num; int len; num = ft_itoa(i); len = ft_print_str(num); free(num); if (len == -1) return (-1); return (len); }
숫자(10진수) 출력 함수 : ft_itoa 함수를 이용하여 int형 숫자를 문자열로 변환하여 저장하였다.
이 후 ft_print_str 출력함수를 이용하여 출력하고 문자수를 반환하였다.
int ft_print_unum(unsigned int i) { unsigned int len; unsigned int n; char *unum; len = 1; n = i; while (n > 9) { len++; n /= 10; } unum = (char *)malloc(sizeof(char) * (len + 1)); if (unum == NULL) return (-1); unum[len--] = '\0'; while (len > 0 && i > 9) { unum[len] = i % 10 + '0'; i /= 10; len--; } unum[len] = i % 10 + '0'; len = ft_print_str(unum); free(unum); return (len); }
부호없는 숫자(10진수) 출력 함수 : n을 10으로 계속 나눠 주어진 숫자의 자리수를 구한다.
malloc으로 메모리 할당을 하고 unum을 10으로 나눠 나눈 나머지에 '0'을 더하여 저장한다.
이 이유는 char타입에서는 0을 표시하려면 ascii code인 48을 넣어야 하기에 그렇다. unum 문자열이 모두 채워지면 ft_print_str 함수를 통해 출력하고 반환값을 len에 담아 반환해준다.
int ft_putptr(uintptr_t i, int len) { if (i >= 16) len = ft_putptr(i / 16, len); len += ft_print_char(HEX_BASE[i % 16]); return (len); } int ft_print_ptraddr(uintptr_t ptr) { int len; len = 0; if (ptr == 0) { len = ft_print_str("(nil)"); return (len); } len += ft_print_char('0'); len += ft_print_char('x'); len = ft_putptr(ptr, len); return (len); }
포인터 주소 출력 함수 : uintptr_t 포인터 값을 저장하고 조작하는 데 사용된다. <stdint.h> 헤더를 사용하면 된다. 이 타입을 사용하면 void* 값을 모두 저장하여 사용 가능하기에 이 타입을 사용 하였다. 포인터가 0을 가리키면 NULL포인터를 가리키므로 printf가 반환 하는 것 처럼 "(nil)"을 출력하였다. 이후엔 16진수라는 0x를 출력하고 ft_putptr로 16진수로 바꾸는 주소값을 출력하게 하였다.
int ft_puthex(unsigned int i, char *base, int len) { if (i >= 16) { len = ft_puthex(i / 16, base, len); len = ft_puthex(i % 16, base, len); } else { ft_print_char(base[i % 16]); len++; } return (len); } int ft_print_hex(unsigned int n, char c) { int len; char *base; if (c == 'x') base = HEX_BASE; else base = HEX_UPBASE; len = 0; len = ft_puthex(n, base, len); return (len); }16진수 출력 함수 :
unsgined int n으로 받은 숫자를 16진수로 변경하여 출력하는 함수이다.char c가 소문자인지 대문자인지 확인하여 ft_puthex로 전달하는 base를 값을 다르게 하였다.HEX_BASE, HEX_UPBASE는 헤더파일에 매크로로 처리하였다.
이러한 구현을 통해 printf 함수의 기본적인 기능을 재현할 수 있다.