ft_printf

bahn·2021년 3월 18일
0

1-Circle

목록 보기
3/3

printf

#include <stdio.h>

int printf(const char * restrict format, ...);

코딩의 시작, TCP SCHOOL

C언어 표준 입출력 함수 중에서도 가장 많이 사용되는 대표적인 입출력 함수입니다.

printf() 함수의 f는 formatted의 약자이며, 서식화된 출력을 지원한다는 의미입니다.

이 함수는 출력할 데이터를 어떤 서식에 맞춰 출력할지 서식 지정자(format specifier)를 통해 직접 지정할 수 있습니다.

이스케이프 시퀀스는 프로그램의 결과가 화면에 출력될 때 사용하게 될 특수한 문자를 위해 만들어졌습니다.

가변 인자 (Value Argument)

C 언어 코딩도장

C 언어에서 함수를 사용하다 보면 printf, scanf같이 매개변수의 개수가 정해지지 않은 함수가 있습니다.
이렇게 매번 함수에 들어가는 인수(argument)의 개수가 변하는 것을 가변 인자(가변 인수, variable argument)라고 합니다.

함수에서 가변 인자를 정의할 때는 고정 매개변수가 한 개 이상 있어야 하며
고정 매개변수 뒤에 ...을 붙여 매개변수의 개수가 정해지지 않았다는 표시를 해줍니다.
단, ... 뒤에는 다른 매개변수를 지정할 수 없습니다.

#include <stdio.h>
#include <stdarg.h>    // va_list, va_start, va_arg, va_end가 정의된 헤더 파일

void printNumbers(int args, ...)    // 가변 인자의 개수를 받음, ...로 가변 인자 설정
{
    va_list ap;    // 가변 인자 목록 포인터

    va_start(ap, args);    // 가변 인자 목록 포인터 설정
    for (int i = 0; i < args; i++)    // 가변 인자 개수만큼 반복
    {
        int num = va_arg(ap, int);    // int 크기만큼 가변 인자 목록 포인터에서 값을 가져옴
                                    // ap를 int 크기만큼 순방향으로 이동
        printf("%d ", num);           // 가변 인자 값 출력
    }
    va_end(ap);    // 가변 인자 목록 포인터를 NULL로 초기화

    printf("\n");    // 줄바꿈
}

int main()
{
    printNumbers(1, 10);                // 인수 개수 1개
    printNumbers(2, 10, 20);            // 인수 개수 2개
    printNumbers(3, 10, 20, 30);        // 인수 개수 3개
    printNumbers(4, 10, 20, 30, 40);    // 인수 개수 4개

    return 0;
}   

...으로 들어온 가변 인자를 사용하려면 stdarg.h 헤더 파일에 정의된 매크로를 이용해야 합니다.

int num = va_arg(ap, int);를 실행하면 현재 ap에서 4바이트(int 크기)만큼 역참조하여 값을 가져온 뒤 ap를 4바이트만큼 순방향으로 이동시킵니다.

즉, 반복문에서 반복할 때마다 ap는 4바이트만큼 순방향으로 이동하므로 10, 20, 30, 40을 순서대로 가져올 수 있습니다.

va_arg

마지막으로 va_end 매크로를 사용하여 ap를 NULL로 초기화합니다.

인텔/AMD x86, x86-64 플랫폼에서는 va_end 매크로를 사용하지 않아도 동작에 지장이 없습니다.
하지만 다른 플랫폼에서는 문제가 생길 수도 있으므로 호환성을 위해서 va_end로 마무리를 해주는 것이 좋습니다.

image

printf의 리턴값: 출력되는 문자의 개수

참고블로그

제출 전 검토해야할 부분

  • 메모리 할당 오류 시 예외 처리

플래그 예외 조건

  • 문자열은 '0'으로 패딩 처리하지 않는다 ?
  • 데이터 타입이 정수형이고 좌측 정렬 플래그가 존재할 경우
    '0' 으로 패딩 처리하지 않는다.
  • precision에 음수가 할당되면 무시(없는 것으로 간주)
  • '0' 플래그가 존재하고 정밀도 너비가 양수이면 '0' 플래그 무시

Makefile

NAME            = libftprintf.a		// NAME : 최종 결과물 파일의 이름을 저장할 환경 변수
CC              = gcc			// CC : 컴파일러 이름 및 명령어를 저장할 환경 변수
CFLAGS          = -Wall -Wextra -Werror -g
                                        /* CFLAGS : 컴파일러 옵션을 저장할 변수
                                              -Wall : 모든 모호한 코딩에 대해서 경고를 보내는 옵션
                                              -Wextra : all 외의 나머지 경고도 함께 출력한다.
                                              -Werror : 모든 경고를 컴파일을 중단하는 오류로 취급한다.
                                              -g[level] : 디버깅 정보를 생성한다.
                                                      -level 0 : 디버깅 정보를 생성하지 않는다.
                                                      -level 1 : 최소한의 정보만 생성한다.
                                                      -level 3 : 추가적인 정보도 생성한다. 	*/
INCFLAGS        = -I./includes		/* INCFLAGS : 헤더파일의 기본 위치 경로를 설정하기 위한 옵션을 저장할 환경 변수 
					'I' 옵션 바로 옆에 헤더파일의 위치 경로를 붙여주면 헤더파일 인식이 가능해진다. */
AR              = ar crs
                                        /* c 옵션 : 아카이브 (라이브러리 파일) 생성.
                                           r 옵션 : 지정한 아카이브로 모듈(object 파일) 추가. 
                                                     새로운 object 파일이면 추가, 기존 파일이면 치환.
                                           s 옵션 : 아카이브 인덱스를 생성.    
                                                    아카이브 인덱스를 생성하지 않으면 링크 속도가 느려지고, 
                                                    시스템 환경에 따라서는 에러가 발생.     */
RM              = rm -rf		// RM : 삭제 명령어를 저장할 환경 변수

LIB_DIR         = ./libft		// LIB_DIR : Libft 의 위치 경로를 저장할 환경 변수

SRC_DIR         = ./srcs		// SRC_DIR : Source 파일(*.c)들의 위치 경로를 저장할 환경 변수
SRC_FILES       = ft_printf_utils.c \
                  ft_int_format.c \
                  ft_char_format.c \	// SRC_FILES : Source 파일(*.c)들의 이름 목록을 저장할 환경 변수
                  ft_tobase_n.c \
                  ft_strchr_set.c
					
SRCS            = $(addprefix $(SRC_DIR), $(SRC_FILES)) \
                ft_printf.c
                                        /* SRCS : 최종적으로 컴파일 과정에서 Source 파일들을 대입하기 위해 
                                                   Source 파일들의 경로와 이름을 조합하여 얻어낸 산출물을 저장할 환경 변수 
                                                 - "addprefix" 문법을 활용하여 어떠한 변수 또는 문자열에 접미사를 추가하여 
                                                 		해당 결과 산출물을 얻어낼 수 있도록 구현하였다.  	*/
        
OBJ_DIR         = ./					// OBJ_DIR : Object 파일들이 저장될 위치 경로를 저장할 환경 변수
OBJS            = $(SRCS:.c=.o)		/*      OBJS : 자동 매크로를 통해 SRCS 의 .c 확장자인 파일들을 
                					Object 파일들로 변환된 목록을 OBJS에 저장 */
.c.o            : $(SRCS)
        $(CC) $(CFLAGS) $(INCFLAGS) -c $< -o $@
/*      .c.o : 확장자 규칙에 의해서 make는 파일들간의 확장자를 자동으로 인식해서 필요한 작업을 수행한다.
        타켓 설정은 의존성 파일과 목적 파일을 실질적으로 만들기위한 명령어를 작성할 수 있다.
        .c.o 자동 확장자 규칙을 직접 구현하여 SRCS 환경 변수의 파일들을 의존하도록 설정하고 Source 파일들을 Object 파일로 생성하는 작업을 수행한다.
        $< : 현재의 목표 파일(Target)보다 더 최근에 갱신된 파일 이름
        $@ : 현재의 목표 파일(Target)		*/

$(NAME)         : $(OBJS)
        $(MAKE) all -C $(LIB_DIR)
        $(MAKE) -C $(LIB_DIR) bonus
        cp $(LIB_DIR)/libft.a $@
        $(AR) $@ $^
/*      $(NAME) : 최종 결과 라이브러리 파일의 타겟 설정
        .c.o 타겟에 의해 생성된 Object 파일들을 의존하도록 설정하고
        libft 파일들을 필요로 하기 때문에
        현재 라이브러리 파일을 만들기 전 
        libft 디렉토리 안에서의 make 명령을 실행하여 생성된 라이브러리를 
        현재 목표파일($(NAME) == libftprintf.a) 이름으로 복사한다.
        - $(MAKE) : make의 재귀적 사용으로써 Makefile 안에서 make 명령을 하나의 명령으로 인식하여 사용한다.
        - -C dir : 우선적으로 해당 경로로 이동하여 명령을 수행하는 make 옵션
        마지막으로 $(AR) 명령어를 통해 현재 목표 파일의 이름으로 라이브러리 파일 이름을 설정하고 의존성 파일을 자동 매크로를 통해 불러와 라이브러리 파일을 생성한다.
        $^ : 현재 Target이 의존하는 대상들의 전체 목록                          */       

all             : $(NAME)			/* all : Target의 이름을 지정하여,
                        				타겟절이 여러 개일 경우 그것들을 순서대로 한 번에 실행할 수 있다. */
clean           :
        $(MAKE) -C $(LIB_DIR) clean
        $(RM) $(OBJS)
/*      clean : -C dir 매크로 옵션을 사용하여 libft 디렉토리 위치에서 
                make clean 명령을 실행하여 libft의 Object 파일들을 제거해준다.
                현재 위치에서의 Object 파일들도 RM 환경 변수를 이용하여 제거해준다.		*/
fclean          :
        $(MAKE) -C $(LIB_DIR) fclean
        $(RM) $(OBJS)
        $(RM) $(NAME)
//      fclean : clean 매크로에서 수행하지 않은 라이브러리 파일까지 지워주는 작업을 수행
re              : fclean all
//      re : 라이브러리 파일과 Object 파일들을 모두 지워 초기화시킨 후 make 명령을 다시 실행
.PHONY          : all clean fclean re
//      .PHONY : PHONY 타겟은 해당 이름의 파일이 존재할 경우 충돌 방지를 위해 사용

ft_printf

int	ft_printf(const char *str, ...)
//  ft_printf 함수는 하나의 고정인자와 가변인자를 매개변수로 받는다. \
    가변인자는 존재하지 않을 수도 있다.
{
	int			print_len;  // 각 가변인자에 대한 출력 길이를 임시 저장할 변수
	int			rtn;    //  최종적으로 리턴 값을 저장할 변수
	va_list		ap;
    //  가변인자의 데이터에 접근하기 위해 va_list 타입의 가변 인자 목록 포인터를 선언 

	rtn = 0;
	va_start(ap, str);  //  va_start 매크로 함수를 이용하여 초기화
	while (*str != '\0')
    //  고정 인자의 문자열의 문자들이 널 종료 문자를 만날 때까지 순차적으로 탐색
	{
		if (*str != '%')// '%' 문자를 발견하지 못할 경우 해당 문자를 순수 출력 
		{
			rtn += ft_putchar_fd(*str, 1);
			str++;
		}
		else    // '%' 문자를 만났을 경우 서식 지정자에 대한 처리를 수행
		{
			print_len = find_format((char *)++str, ap);
            /*  find_format 함수를 호출, '%' 다음 인덱스 문자의 주소와 가변 인자 목록 포인터를 매개변수로 넘겨준다. */
			if (print_len >= 0) // 올바른 서식일 경우
			{
				rtn += print_len;
				str = ft_strchr_set((char *)str, DTYPE) + 1;
                // 고정 인자 문자열의 주소를 해당 서식의 다음 인덱스 주소로 변경
                //  ex) ft_printf("%04d next", 12) => "%04d" 서식에 대한 처리를 마친 후
                //  해당 서식의 다음 인덱스인 ' ' 를 가리키도록 변경 
			}
		}
	}
	va_end(ap);
	return (rtn);
}

static	int	find_format(char *fmt, va_list ap)
{
	int		print_len;
	t_opt	*opt;   // 서식의 플래그 옵션을 저장할 구조체 t_opt 포인터 변수를 선언

	if (!(opt = init_opt()))    // 구조체의 변수들을 초기화시키기 위해 init_opt 함수 호출
		return (-1);
    /*
    typedef struct	s_opt
    {
        size_t		minus;  // 0
        size_t		zero;   // 0
        int			width;  // 0
        int			prec;   // -1 ('.' 플래그만 존재하고 너비에 대한 값이 존재하지 않을 수 있는 경우를 대비하여 초기값을 -1로 설정)
        char		type;   // 0 ('\0')
    }				t_opt;
    */
	while (!ft_strchr(DTYPE, *fmt) && *fmt != '\0')
    //  매개변수로 넘어온 서식에서 데이터 타입을 만나기 전까지 반복
	{
		if (*fmt == '-')
			opt->minus = 1;
		else if (*fmt == '0' && opt->width == 0 &&
				opt->prec == -1 && opt->minus == 0)
			opt->zero = 1;
        /* '0' 플래그가 존재할 경우 동시에 출력 너비, 정밀도, 정렬 플래그 옵션이
            이미 설정되어있는지 검사를 해주어야한다.
            '0' 플래그의 위치가 아닌 다른 플래그에서 '0' 문자가 존재할 수 있는 경우를 방지하기 위함      
            minus 플래그도 검사하는 이유는 while 문 수행 후 
            '0' 플래그 무시 조건 if 문에서 '%' 타입에 대해서는 처리를 하지 않기 때문에
            여기서 '%' 타입 처리를 대비하기 위함    
            %-05.%  %^^^^
            */
		else if (*fmt == '.')
			opt->prec = 0;
		else if (ft_isdigit(*fmt) || *fmt == '*')
			set_width_or_prec(ap, opt, *fmt);
		fmt++;
	}
	opt->type = *fmt;   
    // while 문을 수행 후 서식에 존재하는 데이터 타입 문자를 구조체 type 변수에 대입
    if ((opt->prec >= 0 || opt->minus > 0) && opt->type != '%')
		opt->zero = 0;
    //  '.' 플래그가 존재하거나 '-' 플래그가 존재하고 '%' 데이터 타입이 아닐 경우
    //  '0' 플래그를 0으로 설정하여 무시
    //  ft_printf("|05.4d|", 12) => | 0012| 정밀도와 '0' 패딩이 겹치면 안되기 때문
    //  ft_printf("|-04d|", 12) => |12  |   
    //     좌측정렬일 경우 '0' 패딩으로 인한 정수 데이터의 혼동을 방지하기 위해 '0' 플래그 무시
	print_len = data_type(ap, opt);
    // 데이터 타입에 따른 출력 처리를 하기 위해 data_type 함수 호출;
	free(opt);
    // 모든 출력 처리를 마친 후 구조체 포인터 변수 메모리 해제
	return (print_len);
}

static	void	set_width_or_prec(va_list ap, t_opt *opt, char ch)
//  플래그 옵션 위치에 존재하는 너비 값을 구조체 변수에 저장하기 위한 함수
{
	if (ft_isdigit(ch)) // 너비 값이 고정 인자 문자열에 명시적으로 존재할 경우
	{
		if (opt->prec == -1)    // '.' 플래그가 발견되지 않은 상태일 경우
			opt->width = opt->width * 10 + ch - 48;
            //  고정 인자 문자열은 문자로 이루어져 있기 때문에
            //  ASCII 코드를 활용하여 문자를 정수형으로 변환하기 위한 과정
		else    // '.' 플래그 이후에 너비 값이 존재할 경우
			opt->prec = opt->prec * 10 + ch - 48;
	}
	else if (ch == '*') // 숫자 와일드카드, 가변 인자로 값이 들어올 경우
	{
		if (opt->prec == -1)
		{
			if ((opt->width = va_arg(ap, int)) < 0)
			{
				opt->minus = 1;
                /* 만약 너비에 대한 가변 인자 값이 음수일 경우
                    '-' 부호를 플래그로 인식하도록
                    minus 변수를 1로 설정       */
				opt->width *= -1;   //  너비는 양수로 변경
				opt->zero = 0;  //  zero 변수는 0으로 설정하여 무시 ('%' 타입 대비)
			}
		}
		else
		{
			if ((opt->prec = va_arg(ap, int)) < 0)
				opt->prec = -1;
            //   가변 인자로 받아온 정밀도 너비가 음수일 경우 \
                    prec 변수를 -1로 설정하여 정밀도에 대한 처리를 무시
		}
	}
}

static	int	data_type(va_list ap, t_opt *opt)
{
    /* 가변 인자 목록 포인터와 플래그가 저장된 구조체를 매개변수로 받아 \
        구조체 type 변수를 검사하여 
        va_arg 매크로 함수를 통해 가변 인자 데이터를 추출하여   
        type 에 따른 함수를 호출.       */   
	if (opt->type == 'c')
		return (char_format(va_arg(ap, int), opt));
	else if (opt->type == 's')
		return (str_format(va_arg(ap, char*), opt));
	else if (opt->type == 'i' || opt->type == 'd')
		return (int_format(va_arg(ap, int), opt));
	else if (opt->type == 'p')
		return (pointer_format(va_arg(ap, long long), opt));
	else if (opt->type == 'u' || opt->type == 'x' || opt->type == 'X')
		return (uint_format(va_arg(ap, unsigned int), opt));
	else if (opt->type == '%')
		return (char_format(opt->type, opt));
	else
		return (-1);
    /*  데이터 타입이 올바르지 않을 경우 서식 출력에 대한 처리를 수행하지 않고
        -1 을 리턴하여 find_format(), ft_printf() 의 print_len 변수에 -1이 저장되어진다.
        ex) ft_printf("%05.5m", 12) => "05.5m" */
}
size_t	char_format(int ch, t_opt *opt)	// 데이터 타입이 'c' || '%' 일 경우 처리하는 함수
{
	size_t	print_len;	//	플래그 옵션을 적용하여 총 출력되는 길이를 저장할 함수
	char	*padding;	//	패딩 관련 플래그가 존재할 경우 너비에 맞게 패딩된 문자열만 저장

	if (opt->width > 0)
		padding = set_padding(opt->zero, (size_t)opt->width - sizeof(char));
	//	전체 출력 너비가 0 보다 큰 값이 설정되어있다면 \
		해당 값에서 1 byte를 뺀 길이만큼 패딩 문자열을 생성
	else
		padding = ft_strdup("");
	//	전체 출력 너비가 0 보다 작거나 같다면 \
		빈 문자열('\0')을 padding 에 저장
	if (opt->minus == 0)
	{
		print_len = ft_putstr_fd(padding, 1);
		print_len += ft_putchar_fd((char)ch, 1);
	/*	'-' 플래그가 존재하지 않는다면	
		우측 정렬로 출력해야하기 때문에
		padding 된 문자열을 먼저 출력하고,
		그 다음 가변 인자 문자 값을 출력한다. 	*/
	}
	else
	{
		print_len = ft_putchar_fd((char)ch, 1);
		print_len += ft_putstr_fd(padding, 1);
	/*	'-' 플래그가 존재한다면	
		좌측 정렬로 출력해야하기 때문에
		가변 인자 문자 값을 먼저 출력하고,
		그 다음 padding된 문자열을 출력한다. 	*/
	}
	free(padding);
	return (print_len);
}

size_t	str_format(char *str, t_opt *opt)//	s 데이터 타입일 경우 처리하는 함수
{
	size_t	print_len;
	char	*arg;	// 가변인자 문자열을 저장할 변수
	char	*padding;	//	패딩 문자열을 저장할 변수

	if (str == NULL)
		str = "(null)";	
	//	만약 가변인자 문자열이 NULL 일 경우 가변인자 문자열을 "(null)"로 대체
	if (opt->prec > -1)
		arg = ft_substr(str, 0, opt->prec);
	// 만약 '.' 플래그가 존재한다면 정밀도 너비만큼 가변인자 문자열을 잘라내어 메모리에 저장. 
	// 모든 서식문자에서 ‘-0.*’ 플래그와 최소 필드 너비의 조합을 어떤 조합도 처리할 것이기 때문이다.
	else
		arg = ft_strdup(str);
	//	'.' 플래그가 존재하지 않는다면 가변인자 문자열을 그대로 복제하여 메모리에 저장
	if (opt->width > 0 && (size_t)opt->width > ft_strlen(arg))
	{
		padding = set_padding(opt->zero, opt->width - ft_strlen(arg));
		arg = set_sorting(opt->minus, arg, padding);
		/*	전체 출력 너비가 0보다 크고 arg 길이보다 크다면
			플래그에 따른 패딩 문자열을 생성하고
			arg 에 정렬 플래그에 따라 arg 와 padding을 조합한다.	*/
	}
	print_len = ft_putstr_fd(arg, 1);
	free(arg);
	return (print_len);
}
// ft_printf_utils.c
char	*set_padding(size_t zr_flg, size_t size)
//	padding 문자열을 만들 메모리 공간을 확보하고 \
	'0' 플래그 존재 유무와 너비 값에 따라 패딩 문자열을 만든다.
{
	char	*padding;

	padding = ft_calloc(sizeof(char), size + 1);
	//	ft_calloc 함수를 통해 size 와 '\0' 문자 1 byte 를 더한 만큼 메모리 공간을 할당.  
	if (zr_flg == 0)
		ft_memset(padding, ' ', size);
	//	'0' 플래그가 존재하지 않았다면 ft_memset 함수를 통해 \
		' ' 문자를 할당된 메모리 공간에 채운다.
	else
		ft_memset(padding, '0', size);
	//	'0' 플래그가 존재한다면 ft_memset 함수를 통해 \
		'0' 문자를 할당된 메모리 공간에 채운다.
	return (padding);
}

char	*set_sorting(size_t mns_flg, char *arg, char *padding)
//	가변 인자 문자열과 패딩 처리된 문자열을 '-' 플래그 존재 유무에 따라 \
	좌측 또는 우측 정렬을 적용한 최종 문자열을 만든다.
{
	char	*result;

	if (mns_flg == 0)
	{
		result = ft_strjoin(padding, arg);
		//	'-' 플래그가 존재하지 않는다면 ft_strjoin 함수를 통해 \
			 padding 문자열을 기준으로 가변 인자 문자열을 합쳐 우측 정렬을 적용시킨다.
		return (result);
	}
	else
	{
		result = ft_strjoin(arg, padding);
		//	'-' 플래그가 존재한다면 ft_strjoin 함수를 통해 \
			 가변 인자 문자열을 기준으로 padding 문자열을 합쳐 좌측 정렬을 적용시킨다.
		return (result);
	}
}
size_t			int_format(int n, t_opt *opt)
//	정수형 데이터 타입 'i', 'd'에 대한 출력 처리를 하는 함수
{
	size_t	print_len;
	char	*sign;		// 가변인자 정수가 음수일 때 부호를 저장할 변수
	char	*arg;		// 가변인자 정수의 절댓값을 저장할 변수

	sign = ft_strdup("");
	//	가변인자 값이 양수일 경우 '+' 부호를 출력할 필요가 없기 때문에 \
		sign에 빈 문자열을 저장해놓는다.
	if (opt->prec == 0 && n == 0)
		arg = ft_strdup("");
		//	'.' 플래그가 존재하면서 정밀도 너비가 0, 가변인자 정수값이 0일 경우 \
			arg를 빈 문자열로 저장한다.
	else if (n < 0)
	{
		sign = ft_strjoin(sign, ft_strdup("-"));
		//	가변인자 정수값이 음수일 경우 sign에 음수 부호를 저장한다. \
			이미 sign에 메모리 공간에 할당이되고 빈 문자열이 저장되어있기 때문에 \
			매개변수 두 문자열의 메모리 해제 과정이 포함된 ft_strjoin 함수를 호출하여 \
			"-" 문자열로 대체한다.
		arg = ft_itoa((long long)n * -1);
		//	음수인 가변인자 정수 값의 절댓값을 문자열로 변환하여 arg에 저장한다.
	}
	else
		arg = ft_itoa(n);
		//	가변인자 정수 값이 양수일 경우 문자열로만 변환하여 arg에 저장한다.
	arg = applies_to_prec(opt->prec, arg);
	//	정밀도 너비에 따라 가변인자 정수값이 저장되어있는 arg에 정밀도를 적용시킨다.
    /*  정밀도는 사실상 실수 데이터 타입의 정확도를 높이기 위해 
        자릿수를 설정하는 용도로 사용되어지고 있다. 
        하지만 정수형에도 적용이 가능하기 때문에
        정수형일 경우 최대 자릿수를 맞추기 위해 '0'을 추가한다.
    */
	arg = applies_to_width(opt, sign, arg);
	//	전체 출력 너비에 따라 패딩 처리 및 부호 위치 조정하는 과정을 수행한다.
	print_len = ft_putstr_fd(arg, 1);
	free(arg);
	return (print_len);
}

static	char	*applies_to_prec(int prec, char *arg)
{
	char	*padding;

	if (prec > -1 && (size_t)prec > ft_strlen(arg))
	//	'.' 플래그가 존재하고 정밀도의 너비가 가변인자 정수 값의 길이보다 클 경우 정밀도를 적용시킨다.
	{
		padding = set_padding(1, prec - ft_strlen(arg));
		//	첫번째 매개변수 '0' 플래그 값을 1로 넘겨주어 무조건 '0'으로 패딩 처리하도록 set_padding 함수를 호출
		arg = set_sorting(0, arg, padding);
		//	첫번째 매개변수 '-' 플래그 값을 0으로 넘겨주어 무조건 우측 정렬을 기준으로 처리하도록 set_sorting 함수를 호출
	}
	return (arg);
}

static	char	*applies_to_width(t_opt *opt, char *sign, char *arg)
{
	char	*padding;

	if (opt->width > 0 &&
			(size_t)opt->width > (ft_strlen(sign) + ft_strlen(arg)) &&
			opt->width > opt->prec)
	//	전체 출력 너비값이 arg 길이와 부호 문자 1byte 길이를 더한 값보다 크면서 정밀도 너비보다도 커야한다.
	{
		padding = set_padding(opt->zero,
				opt->width - (ft_strlen(sign) + ft_strlen(arg)));
		//	 전체 출력 너비 값에서 부호 문자와 가변인자 문자열 길이를 합한 값을 뺀 만큼 패딩 문자열을 만든다.
		if (opt->zero > 0)
			padding = ft_strjoin(sign, padding);
		//	'0' 플래그가 존재한다면 부호 문자를 기준으로 패딩 문자열을 합쳐서 부호 문자를 가장 먼저 출력하도록 한다.
		//	|-000015|
		else
			arg = ft_strjoin(sign, arg);
		//	'0' 플래그가 존재하지 않는다면 절댓값 가변 인자 정수 값에 다시 부호를 붙인다.
		//	|    -15|
		arg = set_sorting(opt->minus, arg, padding);
		//	'0' 플래그에 따른 부호 위치 조정 및 패딩 문자열을 붙이고나서 \
		 '-' 플래그에 따라 좌측 또는 우측 정렬을 적용시킨다.
	}
	else
		arg = ft_strjoin(sign, arg);
	//	if문에서 전체 출력 너비 값 조건에 부합하지 않는다면 \
		단순히 부호와 가변 인자 문자열을 조합한다. 
	return (arg);
}

size_t			uint_format(unsigned int n, t_opt *opt)
//	부호 없는 정수형 타입을 가변인자로 받는 'u', 'x', 'X' 에 대한 출력 처리를 하는 함수
{
	size_t	print_len;
	char	*arg;

	if (opt->prec == 0 && n == 0)
		arg = ft_strdup("");
	// 정밀도 너비가 0으로 설정되어있고 가변 인자 정수 값이 0일 경우 arg를 빈 문자열로 저장하여 출력한다.
	else if (opt->type == 'u')
		arg = ft_itoa(n);
	// 데이터 타입이 'u'일 경우 단순히 문자열로 변환하여 arg에 저장;
	else if (opt->type == 'x')
		arg = ft_tobase_n(n, "0123456789abcdef");
	else if (opt->type == 'X')
		arg = ft_tobase_n(n, "0123456789ABCDEF");
	// 데이터 타입이 'x' 또는 'X'일 경우 포인터 주소를 부호 없는 정수형으로 가변인자 값을 받아오기 때문에 \
		부호 없는 정수형 가변인자 값을 16진주로 변환하는 ft_tobase_n 함수를 호출한다.
	else
		return (0);
	// 'X'일 경우의 else if문을 else로 설계하여도 되지만 \
		본인은 명시적이고 가독성이 좋은 코드를 선호하기 때문에 번거롭고 코드가 길어지는 것을 감수하고도 \
		5개의 if 문을 만들었다.
	arg = applies_to_prec(opt->prec, arg);
	//	정밀도 너비에 따라 가변인자 정수값이 저장되어있는 arg에 정밀도를 적용시킨다.
	arg = applies_to_width(opt, ft_strdup(""), arg);
	//	전체 출력 너비에 따라 패딩 처리하는 과정을 수행한다. \
		부호 없는 정수형을 출력하기 때문에 부호 문자를 처리할 필요가 없어 부호 문자열 대신 빈 문자열을 넘겨주었다.
	print_len = ft_putstr_fd(arg, 1);
	free(arg);
	return (print_len);
}

size_t			pointer_format(long long n, t_opt *opt)
//	포인터 주소 데이터 타입 'p'를 출력 처리하기 위한 함수. \
	포인터 주소를 long long 형으로 받아오게 된다.
{
	size_t	print_len;
	char	*addr;

	if (n == 0 && opt->prec == 0)
		addr = ft_strdup("");
	// 정밀도 너비가 0이고 가변인자 값이 0일 경우 addr에 빈 문자열을 저장
	else if (n == 0 && opt->prec == -1)
		addr = ft_strdup("0");
	//	'.' 플래그가 존재하지 않고 가변인자 값이 0일 경우 addr에 "0"을 저장
	else
		addr = ft_tobase_n(n, "0123456789abcdef");
	//	포인터 주소 출력에 대한 예외 조건에 부합하지 않는다면 long long 형 가변인자 값을 \
		16진수로 변환하여 addr에 저장한다. 
	addr = applies_to_prec(opt->prec, addr);
	//	정밀도 너비에 따라 가변인자 정수값이 저장되어있는 arg에 정밀도를 적용시킨다.
	addr = ft_strjoin(ft_strdup("0x"), addr);
	//	포인터 주소를 출력할 때 해당 주소는 16진수로 이루어져있다는 것을 나타내기 위해	\
		"0x"를 16진수로 변환된 문자열 addr 앞에 붙여준다.
	addr = applies_to_width(opt, ft_strdup(""), addr);
	//	전체 출력 너비에 따라 패딩 처리하는 과정을 수행한다. \
		포인터 주소도 마찬가지로 부호 문자를 처리할 필요가 없어 부호 문자열 대신 빈 문자열을 넘겨주었다.
	print_len = ft_putstr_fd(addr, 1);
	free(addr);
	return (print_len);
}
//	ft_tobase_n.c	(La Piscine 과정에서 작업한 ft_putnbr_base.c 파일을 일부 수정하여 만듦)
static	int		ft_dupl_check(char *str, int length)
//	진수 표현 문자들중 중복된 문자들의 유무를 체크해주는 함수
{
	int i;
	int j;

	i = 0;
	j = i + 1;	// 없어도 될 것 같다.
	/* 첫 번째 인덱스 문자를 기준으로 나머지 문자들과의 중복 체크를 하고
		기준이 되는 인덱스를 1씩 증가시키며 반복 비교 	*/
	while (i < length - 1)
	{
		j = i + 1;
		while (j < length)
		{
			if (str[i] == str[j])
				return (1);
			//	만약 중복되는 문자들이 존재한다면 1을 리턴하여 함수 종료
			j++;
		}
		i++;
	}
	return (0);
}

static	char	*ft_putnbr(long long nbr, char *base,
		int base_count, char *result)
//	nbr : 변환될 대상 값
//	base : 진수 표현 문자열
//	base_count : 진수 표현 문자 수
//	result : 최종 변환된 결과를 문자열로 저장할 변수
{
	char	*hex;

	if (nbr >= 0)	// ft_printf 에서는 절댓값만을 nbr로 넘겨주기 때문에 없어도 되는 문장
	//	음수에 대한 처리는 구현하지 않음
	{
		if (nbr / base_count < base_count)
		//	나눈 값이 base_count 보다 작으면 바로 진법 표현이 가능하다.
		{
			if (nbr / base_count != 0)
			//	nbr이 base_count 보다 작은 값일 경우 \
				여기서 문자 추출 및 병합 과정을 수행하지 않고 \
				밑에 if, else 문 다음 과정으로 넘긴다.
			{
				hex = ft_substr(base, nbr / base_count, 1);
				//	ft_substr 함수를 통해 base 문자열에서 \
					나눈 값의 인덱스 문자를 추출하여 hex에 저장
				result = ft_strjoin(result, hex);
				//	추출한 문자를 result 와 병합
			}
		}
		else
			result = ft_putnbr(nbr / base_count, base, base_count, result);
		// 나눈 값이 base_count보다 크거나 같다면 \
			 나눈 값을 다시 변환 대상 값으로 재귀 함수 호출

		hex = ft_substr(base, nbr % base_count, 1);
		// base 문자열에서 나머지 값의 인덱스 문자를 추출하여 hex에 저장
		result = ft_strjoin(result, hex);
		// 나눈 값의 인덱스 문자와 나머지 값의 인덱스 문자를 병합
	}
	return (result);
}

char			*ft_tobase_n(long long n, char *base)
{
	char	*result;
	int		base_count;

	base_count = 0;
	while (base[base_count] != '\0')
	{
		if (base[base_count] == '-' || base[base_count] == '+')
		{
			ft_putchar_fd('\0', 1);
			return (NULL);
		}
		base_count++;
		//	진수 표현 문자들 중 '-', '+' 문자들이 포함되어있다면 NULL 리턴 함수 종료
	}
	if (base_count < 2)	// 이진법보다 진수 표현 수가 작으면 NULL 리턴 함수 종료
		return (NULL);
	else if (ft_dupl_check(base, base_count))	// 진수 표현 문자 중복 체크
		return (NULL);
	else
	{
		result = ft_calloc(sizeof(char), 1); 
		// result 1byte 만큼 메모리 공간 할당 및 '\0'(ft_bzero) 초기화
		result = ft_putnbr(n, base, base_count, result);
		// 진수 변환 시작
	}
	return (result);
}

형식 태그 & 서식지정자 & 플래그 옵션

profile
42Seoul-bahn

0개의 댓글