[C] ft_printf - printf 구현하기

Austin·2024년 11월 23일

42-Cursus

목록 보기
4/24

printf란?

printf는 C 언어에서 출력 형식을 지정하여 데이터를 출력하는 데 사용되는 함수입니다.
기본적인 사용법은 문자열에 포함된 형식 지정자를 통해 다양한 데이터 타입을 출력할 수 있는 기능을 제공한다.
헤더는 stdio.h 사용하고 함수 호출은 기본형태가 이렇게 int printf(const char *format, ...); 되어 있다.
자세한내용은 man printf 메뉴얼을 참고하면 된다.

ft_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에 더해져 출력된 정확한 문자수를 확인한다.

%check

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));
}
  • 문자 출력 함수 : write함수의 반환값은 printf와 같이 출력한 문자수를 반환한다.
    char 문자 1개를 출력하는 함수를 구현하여 return 값으로 직접 넣어 write를 출력하고 반환값을 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);
}
  • 문자열 출력 함수 : 문자열의 끝을 나타내는 널문자(\0)을 찾을 때 까지 ft_print_char 함수를 이용하여 출력하고 인덱스i를 반환한다. NULL이 들어올 시 printf 함수에서 (null)을 출력하고 6을 반환한 것 처럼 s 문자열에 (null)을 넣어 함수의 출력값을 동일하게 하였다.
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 함수의 기본적인 기능을 재현할 수 있다.

0개의 댓글