[42] printf 구현하기

KURTY·2022년 8월 29일
0

42_SEOUL

목록 보기
3/9

가변인자

printf("%d + $d = %d", 1, 1, 1);

위와 같이 우리가 printf를 사용하다 보면 자연스럽게 함수에 인자를 1개만 넣는 상황도 있고, 아니면 넣지 않거나 혹은 위와 같이 다수의 인자를 넣어도 문제없이 함수는 작동한다. 이것을 이해하기 위해 가변인자의 개념을 알아야 한다.

int ft_printf(const char *, ...)

위와 같이 과제의 프로토타입에서 보이는 …이 가변인자라고 할 수 있다. 파라미터로 아무것도 넘겨주지 않을 수도 있으며, 여러 개를 넘겨주는 것 또한 가능하다.

헤더파일 : <stdarg.h>

  • va_list 변수를 생성한다. 해당 변수는 각 가변인자의 시작 주소를 가리키는 포인터이다.
  • va_start 함수를 이용한다. 해당 함수는 가변인자를 가져올 수 있다록 포인터를 설정(초기화) 하는 함수이다.
    • va_start(ap, str) 에서 ap는 가변인자(va_list) 변수, 그 뒤 고정인자를 넣는다. 이 때 가변인자는 고정인자의 뒤에 위치하게 된다.
고정인자가변인자1가변인자2가변인자3
     👆🏻

   va_list
  • va_arg 함수를 이용하여 가변인자를 가져온다. va_arg(va_list, 자료형)은 va_list가 가리키는 값을 자료형의 크기 만큼 리턴한 후, va_list를 자료형 크기 만큼 뒤로 옮긴다.
  • va_end 함수를 이용하여 va_list를 초기화 한다.

va_list

각 가변 인자의 시작 주소를 가리킬 포인터이다.

va_start

I void va_start(va_list ap, var_name);

ap => va_list 로 만든 포인터가 담긴다.
var_name => 마지막 고정된 필수 인수가 담긴다.

va_start는 매크로 함수이고, va_list로 만들어진 포인터에게 가변 인자 중 첫 번째 인자의 주소를 가르쳐주는 매크로이다.

va_arg, va_copy, va_end에 대한 후속 호출에 대해 ap 포인터 값을 초기화한다.

va_arg

var_type va_arg(va_list ap, var_type);
ap => va_list 로 만든 포인터가 담긴다.
var_type => int or long과 같은 타입 이름이 담긴다.

va_arg를 호출하면 하나의 매개변수를 리턴하고, ap가 다음 매개변수를 가리키게 한다. va_list의 포인터를 다음 가변 인자로 이동시키는 매크로이다.

va_copy

void va_copy(va_list dest, va_list src);

va_start를 dest에 적용한 뒤 src

va_end

va_end(va_list arg_ptr);

va_end는 매크로 함수이고, 사용했던 가변 인자 목록을 비워준다. 함수가 리턴하기 전에 반드시 호출해야 한다.

형식 문자열(format)

표준 출력에 데이터들을 형식 문자열에 지정된 형태로 출력한다.

형식 문자열 다음에는 출력한 데이터를 나열한다. 형식 문자열 다음으로 오는 인자들의 개수는 반드시 형식 문자열 속의 형식 태그보다 같거나 많아야 한다.

서식 지정자 (format specifier)

  • ft_printf의 유일한 고정 매개변수 format은 서식문자열, 형식문자열, 포맷 이라는 이름으로 불린다. 이 포맷은 보통 우리가 출력하려고 하는 문자열이며, 이 문자열 내부에는 %문자로 시작하는 서식지정자가 존대한다.
  • 정수, 실수, 문자, 문자열, 포인터 주소 등을 출력하는 역할을 한다. 또한 이 서식지정자에 다양한 플래그, 폭, 정밀도, 길이 등의 다양한 옵션을 조합해서 사용할 수도 있다.

형식 태그 (format tag)

%[flag][width][precision]서식지정자

ft_printf의 형식 태그는 위와 같이 생겼다

  • 형식 태그의 첫 번째 문자는 반드시 %여야 하고, %를 출력하려면 두 번을 중복해 %%와 같이 활용한다.
  • 형식 태그는 출력할 값에 대하여 개별로 적용된다. 출력할 값이 3개라면 % 또한 세 개가 나와야 한다.
  • 형식 태그는 %와 서식지정자로 구성된다. []로 둘러쌓인 부분은 옵션이라 생략할 수 있다.

서식지정자에 옵션을 추가하려면 이미 규약된 형식태그를 지켜야 한다. ft_printf의 파라미터에 들어오는 값들을 형식 태그가 지정한 형태로 변환되어 출력하는 것이다. 그래서 위에서 말한 “인자의 개수” ≥ “형식 태그의 개수” 라는 조건이 성립되는 것이다.

플래그(flag)

  • 플래그의 경우 타입별로 플래그가 적용되지 않는 경우가 있다.
-0+공백#정밀도(.)
c공백을 뒤로UBxxxx
s공백을 뒤로UBxxx최대 문자 갯수
p공백을 뒤로공백을 0으로xxx (이미 적용)최소 문자 갯수
d공백을 뒤로공백을 0으로양수에 +양수에 공백x최소 문자 갯수
i공백을 뒤로공백을 0으로양수에 +양수에 공백x최소 문자 개수
u공백을 뒤로공백을 0으로xxx최소 문자 개수
x공백을 뒤로공백을 0으로xx숫자에 0x붙임최소 문자 개수
X공백을 뒤로공백을 0으로xx숫자에 0X붙임최소 문자 개수
%공백을 뒤로공백을 0으로xxxx

단 UB 의 경우, undefined behavior이지 결과값은 플래그가 적용된 상태

플래그가 두 개 동시에 나오는 경우, 0 플래그는 제외하고 -플래그만 사용한다.

또한, 플래그가 여러개가 나올 경우에도 작동한다.

#include <stdio.h>

int main()
{
	printf("%---0---0---s\n", "12345");
}
물론 위 경우처럼 '-''0'이 동시에 나올 경우, undefined behavior이다.

하나만 여러개 나올 경우 경고발생도 없다.

플래그는 어느 위치에서나 적용될 수 있지만, 경고 메시지가 뜬다.
(%---10.3--s의 경우, invalid conversion specifier '-')

따라서, ft_printf는 undefined behavior에 관하여 출력내용을 따라하기도,
에러로 판단하고 출력하지 않기도 한다.

길이(폭, width)

  • %에서부터 type까지의 부분에 대하여 출력될 총 문자열의 길이를 뜻한다.
  • 출력될 데이터의 길이(가변인자로 받았고, 가공되어 출력 전의 상태의 데이터) 보다 값이 크다면 width만큼, 아니면 데이터의 길이 만큼 출력이 된다.
  • 파싱 중 *이 나오면 가변인자에서, 나오지 않았다면 숫자가 있을 경우 파싱하여 값을 저장한다.
  • *과 숫자는 같이 나올 수 없다.
  • 값이 음수일 경우 -플래그 + 숫자로 간주한다.

정밀도(precision)

  • 길이와 구분하기 위해 .을 사용.
  • width와 마찬가지로 *을 이용하여 가변인자에서 불러올 수 있다.
  • 정수의 경우 (d, i) 출력될 데이터 길이보다 짧으면 무시된다.
  • . 뒤에 숫자 없이 바로 type 이 올 수 있다. (이때, 0으로 간주 ex)%.d)

반환값 (return)

  • 출력된 길이를 반환한다.
  • 오류 발생 시 -1을 리턴한다.

%c (char)

  • data의 크기를 int로 받아온다.
  • width의 길이가 1보다 길면, 출력할 길이를 width만큼 확보한다.
  • 0플래그가 적용될 사항일 경우 0, 아닐 경우 빈칸을, 확보한 string에 넣는다
  • -플래그 유무에 따라 문자를 앞 혹은 뒤에 넣고 출력한다.

%s (string, char *)

  • 가변인자를 char *자료형으로 받아온다. 저장되는 데이터는 문자열의 주소값이다.
  • datanull(0)일 경우 문자열은 (null)이 된다. 이때, 괄호가 포함된다.
  • 정밀도가 문자열의 길이보다 길 경우 정밀도는 문자열의 길이이며, 양수이면서 문자열의 길이보다 짧을경우 문자열을 정밀도의 길이만큼만 출력한다.
  • 정밀도가 없거나, 음수의 정밀도거나 data의 길이보다 작다면 정밀도 = data의 길이다.
  • width이 현재 가공된 데이터 보다 길경우 width만큼, 짧을 경우 가공된 데이터 길이만큼 확보한다.
  • 0플래그가 적용될 사항일 경우 0, 아닐 경우 빈칸을, 확보한 string에 넣는다.
  • -플래그 유무에 따라 문자열을 앞 혹은 뒤에 넣고 출력한다.

%d, %i (integer)

  • data를 int형으로 받아온다.
  • data를 litoa함수를 사용하여 문자열로 바꾼다. 이 때 음수는 고려하지 않는다.
  • litoa함수를 구성하여 사용하는 이유는 기존 itoa함수를 사용한다면 INT_MIN값에대한 자동적인 타입캐스팅으로 오버플로가 일어나 원하는 값을 얻지 못하기 때문이다.
  • 정밀도가 문자열의 길이보다 길다면 그 차이만큼 앞에 0을 추가한다.
  • 정밀도가 없거나, 음수의 정밀도거나 data의 길이보다 작다면 정밀도 = data의 길이다.
  • data가 음수면 앞에 -을 추가한다.
  • width값이 데이터 문자열의 길이보다 길다면 width, 아니라면 데이터 문자열 길이만큼 출력할 문자열의 길이를 확보한다.
  • 0플래그가 적용될 사항일 경우 0, 아닐경우 빈칸을, 확보한 string에 넣는다
  • '0'플래그와 ('+'플래그 혹은 음수)일 때, 정밀도가 없거나, 음수라면 0이 출력되기 전에 부호를 출력한다. 공백플래그도 같다.
  • 정밀도가 존재하고, 음수가 아닐 경우 0플래그는 무시한다.
  • -플래그 유무에 따라 문자열을 앞 혹은 뒤에 넣는다.
  • data가 음수라면 +플래그, 공백플래그에 대하여 '-'부호를 출력한다.
  • 공백플래그가 존재할 경우 음수가 아니고, 플러스 플래그가 없다면 원래의 width에서 1만큼 감소한 값을 출력한다.
  • '0'플래그가 없거나, 음수가 아닌 정밀도가 존재한다면, width가 data의 길이보다 길 때 공백을 출력한 뒤 '+'플래그 or 음수에 대한 부호 or '공백'플래그에 대한 값을 출력한다.
  • litoa함수를 통한 동적할당이 있었으므로 free처리 해준다.
  • '+'플래그와 '공백'플래그가 동시에 존재할 경우 '공백'플래그는 무시된다.

%u (unsigned integer)

  • %d or %i부분에서 음수, '공백'플래그, '+'플래그를 배제하고 생각.
  • unsigned int형으로 가변인자에서 data를 가져온다.
  • uitoa함수를 통해서 음수 부분은 배제하고 문자열로 변환한다.
  • 나머지 부분은 위와 같다. (작동하는 함수 같음)

%p (pointer, 주소값 출력)

주소값 출력이지만 가변인자로 일반변수를 넣으면, 값을 16진수로 변환하여 출력한다.

  • datavoid * 형으로 받아오고 unsigned long으로 캐스팅한다.
  • 정밀도가 없거나, 음수의 정밀도거나 data의 길이보다 작다면 정밀도 = data의 길이다.
  • precision이 문자열의 길이보다 길다면 그 차이만큼 앞에 0을 추가한다.
  • data가 null값이라면 precision길이만큼 '0'을 출력한다.
  • 문자열의 길이 + 2만큼 출력할 문자열의 길이를 확보한다. 문자열의 길이 + 2보다 width길이가 길다면 그만큼 더 확보한다.
  • 0플래그가 적용될 사항일 경우 0, 아닐 경우 빈칸을, 확보한 string에 넣는다.
  • -플래그 유무에 따라 문자열을 앞 혹은 뒤에 넣는다.
  • '0'플래그가 존재한다면 '0x'출력 후 0을 출력 후 data를 출력한다.
  • '0' or '-' 플래그가 존재하지 않는다면 width가 data의 길이보다 길 때 공백을 출력 후 0x를 출력한다. '-'플래그가 존재한다면 바로 0x를 출력함

%x, %X (16진수)

  • unsigned int형으로 가변인자에서 받아온다.
  • #플래그가 있을 떄, %x라면 0x를 출력, %X라면 0X를 출력한다.
  • 정밀도가 없거나, 음수의 정밀도거나 data의 길이보다 작다면 정밀도 = data의 길이다.
  • data가 null값이 아니고 '#' 플래그가 존재한다면 원래의 width에서 -2한다.
  • '#' 플래그가 존재할 때 0플래그가 존재하고 정밀도가 없다면 공백 혹은 '0'이 출력되 전에 제일 앞에 0x or 0X를 출력한다.
  • 그 외의 경우에는 '0'혹은 공백이 출력된 뒤 출력한다.
  • 정밀도가 data의 길이보다 길다면 앞에 '0'으로 채운다.
  • '0'플래그가 존재할 떄 양수인 정밀도가 존재한다면 '0'은 출력되지 않는다.
  • 재귀함수를 통해서 16진수를 출력하는데 iteration의 존재이유는 data가 null값일때, (%p가 이고 정밀도가 있는) 경우를 제외하면 재귀가 돌아가야 하기 때문에 존재한다. -> %p일때 data가 0이면 널 포인터를 의미하지만 그 외에 x or X에선 0을 의미하기 때문, %p이고 정밀도가 없으면 0을 출력함

%% (%출력)

  • %c 에서 가변인자를 받아오는 부분과 문자를 출력할 문자열에 삽입하는 과정을 %로 바꾸면 된다.

출력 예시

// 정수
printf("%d\n", -20); // 부호 있는 10진 정수
printf("%i\n", -20); // 부호 있는 10진 정수
printf("%u\n", 10);  // 부호 없는 10진 정수
printf("%x\n", 0xF1); // 부호 없는 16진 정수(소문자)
printf("%X\n", 0xF1); // 부호 없는 16진 정수(대문자)

// 문자, 문자열
printf("%c\n", 'a'); // 문자
printf("%s\n", "Hello world!"); // 문자열

// 포인터
int num 1;
void *ptr = &num1;
printf("%p\n", ptr) // 앞에 0x가 붙고, A~F는 소문자로 출력, 높은 자릿수의 0은 생략

// %기호
printf("%%\n"); // % 기호 출력

// 정수 서식 지정자 %d에 폭 지정
printf("%6d\n", 20); // %d의 출력 폭을 6칸으로 지정
//    20    4칸 공백
printf("%6d\n", 2000); // %d의 출력 폭을 6칸으로 지정
//  2000    2칸 공백

// 정수 서식 지정자 %d에 폭과 0플래그 사용
printf("%06d\n", 20);      // 출력 폭을 6칸으로 지정, 남는 공간은 0으로 채움
//000020    0이 4개 채워짐
printf("%06d\n", 2000);    // 출력 폭을 6칸으로 지정, 남는 공간은 0으로 채움
//002000    0이 2개 채워짐

// 정수 서식 지정자 %d에 폭과 ' ' 플래그, '+'플래그 사용
printf("% d\n", 10);  // 양수일 떄는 부호를 출력하지 않고 공백으로 표시
// 10
printf("% d\n", -10); // 음수일 때는 - 부호를 출력
//-10
printf("%+d\n", 200); // 양수일 떄는 + 부호를 출력
//+200
printf("%+d\n", -200); // 음수일 떄는 - 부호를 출력
//-200

// 16진수를 출력시 '#' 플래그 사용
printf("%#x\n", 0xf1);  // 16진수 소문자 출력이면 앞에 0x를 붙임
//0xf1
printf("%#X\n", 0xf1);  // 16진수 대문자 출력이면 앞에 0X를 붙임
//0XF1

ft_printf.h

#ifndef FT_PRINTF_H
# define FT_PRINTF_H

# include "./libft/libft.h"
# include <stdarg.h>
# include <unistd.h>

# define SPECIFIERS "cspdiuxX%"
# define HEXLOW "0123456789abcdef"
# define HEXUP "0123456789ABCDEF"

typedef struct s_format
{
	int		minus;
	int		plus;
	int		width;
	int		precision;
	int		neg_prec;
	int		zero;
	int		dot;
	int		space;
	int		sharp;
	int		neg;
	char	specifier;
}t_format;

int			ft_nbrlen(long n, int base);
int			ft_printnstr(char *str, int n);
int			ft_printnchar(int c, int n);
int			ft_printchar(int c);
int			ft_printf(const char *str, ...);
t_format	ft_newformat(void);
int			ft_print_format(t_format f, va_list ap);
int			ft_print_c_pct(t_format f, va_list ap);
int			ft_print_s(t_format f, va_list ap);
int			ft_print_u(t_format f, va_list ap);
int			ft_print_d_i(t_format f, va_list ap);
int			ft_print_x(t_format f, va_list ap);
int			ft_print_p(t_format f, va_list ap);
int			ft_parse(char *str, va_list ap);
int			ft_recursive_hex(t_format f, size_t n, size_t iteration);
char		*ft_allocate(char *str, int len, unsigned int n);
char		*ft_uitoa(unsigned int n);
char		*ft_litoa(long long n);

#endif

ft_printf.c

#include "ft_printf.h"

int	ft_printf(const char *str, ...)
{
	int		print_len;
	va_list	ap;

	print_len = 0;
	va_start(ap, str); // str을 ap에 저장하면서 시작
	while (*str)
	{
		if (*str == '%') // %발견 시
		{
			if (*(++str)) // 다음 문자가 존재 시
				print_len += ft_parse((char *)str, ap); // parsing
			while (*str && !ft_strchr(SPECIFIERS, *str)) // 구분자가 나올 때 까지 str++
				str++;
		}
		else
			print_len += ft_printchar(*str); // 아니라면 그냥 바로 출력
		if (*str)
			str++;
	}
	va_end(ap); // ap를 끝냄
	return (print_len); // 최종 출력한 길이 반환
}

int	ft_print_format(t_format f, va_list ap)
{
	int	print_len;

	print_len = 0;
	if (f.specifier == 'c' || f.specifier == '%')
		print_len = ft_print_c_pct(f, ap);
	if (f.specifier == 's')
		print_len = ft_print_s(f, ap);
	if (f.specifier == 'u')
		print_len = ft_print_u(f, ap);
	if (f.specifier == 'd' || f.specifier == 'i')
		print_len = ft_print_d_i(f, ap);
	if (f.specifier == 'X' || f.specifier == 'x')
		print_len = ft_print_x(f, ap);
	if (f.specifier == 'p')
		print_len = ft_print_p(f, ap);
	return (print_len);
}

ft_format.c

#include "ft_printf.h"

t_format	ft_newformat(void)
{ // flag 초기화
	t_format	newformat;

	newformat.minus = 0;
	newformat.plus = 0;
	newformat.width = 0;
	newformat.precision = 0;
	newformat.specifier = 0;
	newformat.zero = 0;
	newformat.dot = 0;
	newformat.space = 0;
	newformat.sharp = 0;
	newformat.neg_prec = 0;
	newformat.neg = 0;
	return (newformat);
}

ft_parse.c

#include "ft_printf.h"

t_format	ft_parse_bonus(char *str, t_format f)
{
	while (*str != '.' && !ft_strchr(SPECIFIERS, *str))
	{
		if (*str == '+') // '+' 발견시
			f.plus = 1; // '+' 플래그 설정
		if (*str == ' ') // 공백 발견시
			f.space = 1; // 공백 플래그 설정
		if (*str == '#') // '#' 발견시
			f.sharp = 1; // sharp 플래그 설정
		str++;
	}
	return (f);
}

t_format	ft_parse_width(char *str, va_list ap, t_format f)
{ // width 설정
	int	specified;

	specified = 0;
	while (*str != '.' && !ft_strchr(SPECIFIERS, *str)) // .이 아니고, 구분자가 없을 때
	{
		if (*str == '-') // '-'이 발견되었다면
			f.minus = 1; // '-'플래그 설정
		if (*str == '0' && !ft_isdigit(*(str - 1))) // '0'이 발견되고 그 앞에 정수가 없다면
			f.zero = 1; // '0' 플래그 설정
		else if (((*str > '0' && *str <= '9') || *str == '*') && !specified)
		{ // 정수에 포함되는 값이거나 *이 등장하고 아직 width이 식별되지 않았을 떄
			if (*str == '*') // '*'이 있다면
				f.width = va_arg(ap, int); // 가변인자에서 width를 받아옴
			else
				f.width = ft_atoi(str); // width를 atoi로 정수화
			specified = 1; // width가 식별됨을 의미
		}
		str++;
	}
	return (f);
}

t_format	ft_parse_precision(char *str, va_list ap, t_format f)
{ // precision을 설정
	int	specified;

	specified = 0;
	while (!ft_strchr(SPECIFIERS, *str)) // 구분자가 발견되기 전까지
	{
		if ((ft_isdigit(*str) || *str == '*') && !specified) // 정수거나 '*'이라면
		{
			if (*str == '*') // '*'이 발견되었다면
				f.precision = va_arg(ap, int); // 가변인자에서 precision을 받아옴
			else
				f.precision = ft_atoi(str); // precision을 atoi로 정수화
			specified = 1; // precision이 발견되었음을 의미
		}
		str++;
	}
	return (f);
}

int	ft_parse(char *str, va_list ap)
{
	t_format	new_format;

	new_format = ft_parse_width(str, ap, ft_newformat()); // width를 parse
	new_format = ft_parse_bonus(str, new_format); // bonus에 대한 플래그 설정
	while (!ft_strchr(SPECIFIERS, *str) && *str != '.') // width와 precision을 구분하는 .이 없고, 구분자가 없다면
		str++; // 문자 이동
	if (*str == '.' && !new_format.specifier) // .이 발견되었고, 구분자가 없다면
	{
		new_format.dot = 1; // '.'의 유무 표시
		new_format = ft_parse_precision(++str, ap, new_format); // precision을 지정
		while (!ft_strchr(SPECIFIERS, *str)) // 구분자가 나올때 까지 문자 이동
			str++;
	}
	if (new_format.width < 0) // width이 음수라면
	{
		new_format.minus = 1; // '-'플래그 설정
		new_format.width *= -1; // width 양수화
	}
	new_format.specifier = *str; // 구분자 지정
	new_format.neg_prec = new_format.precision < 0; // precision이 음수라면 neg_prec에 표시
	return (ft_print_format(new_format, ap)); // 구분자에 따라서 출력
}

ft_print_chars.c

#include "ft_printf.h"

int	ft_printchar(int c)
{
	write(1, &c, 1); // 문자 1개를 출력 후 1 반환
	return (1);
}

int	ft_printnchar(int c, int n)
{
	int	print_len;

	print_len = 0;
	while (n-- > 0) // 문자 n개를 출력 후 출력된 문자의 개수 반환
		print_len += (int)write(1, &c, 1);
	return (print_len);
}

int	ft_printnstr(char *str, int n)
{
	if (str != NULL)
		return ((int)write(1, str, n)); // 문자열 출력 후 문자열의 길이 반환
	return (0);
}

int	ft_print_c_pct(t_format f, va_list ap)
{
	char	c;
	int		print_len;

	print_len = 0;
	if (f.specifier == 'c') // 식별된 구분자가 'c'라면
		c = va_arg(ap, int); // 가변인자에서 int형으로 받아옴
	else // 아니라면
		c = '%'; // %를 출력한다는 의미
	f.precision = 1; // precision은 1로 고정
	if (!f.minus && f.zero && f.width > f.precision) // -플래그가 없고 0플래그가 있다면
		print_len += ft_printnchar('0', f.width - f.precision); // width - precision만큼 0출력
	else if (!f.minus && f.width > f.precision) // -플래그와 0플래그가 없다면
		print_len += ft_printnchar(' ', f.width - f.precision); // width - precision만큼 공백 출력
	print_len += ft_printchar(c); // 문자를 출력
	if (f.minus && f.width > f.precision) // -플래그가 있다면
		print_len += ft_printnchar(' ', f.width - f.precision); // width - precision만큼 공백 출력
	return (print_len); // 출력한 길이 반환
}

int	ft_print_s(t_format f, va_list ap)
{
	char	*string;
	int		print_len;

	print_len = 0;
	string = va_arg(ap, char *); // 문자열을 char *형태로 가변인자에서 받아옴
	if (!string) // string == 0일 경우
		string = "(null)"; // "(null)"을 저장함
	if (!f.dot || f.precision > (int)ft_strlen(string) || f.neg_prec) // 정밀도가 없거나, 정밀도가 음수거나, 문자열의 길이보다 작다면
		f.precision = ft_strlen(string); // precision = 문자열의 길이
	if (!f.minus && f.zero && f.width > f.precision) // -플래그가 없고, 0플래그가 있다면
		print_len += ft_printnchar('0', f.width - f.precision); // width - precision만큼 0출력
	else if (!f.minus && f.width > f.precision) // -플래그와 0플래그가 없다면
		print_len += ft_printnchar(' ', f.width - f.precision); // width - precision만큼 공백 출력
	print_len += ft_printnstr(string, f.precision); // 문자열 출력
	if (f.minus && f.width > f.precision) // -플래그가 있다면
		print_len += ft_printnchar(' ', f.width - f.precision); // width -precision만큼 공백 출력
	return (print_len); // 출력한 길이 반환
}

ft_print_hex.c

#include "ft_printf.h"

char	*ft_sharp(t_format f) // sharp에 관한 함수
{
	if (f.specifier == 'X')
		return ("0X");
	return ("0x");
}

int	ft_recursive_hex(t_format f, size_t n, size_t iteration)
{
	int		print_len;
	int		remainder;
	char	character;

	print_len = 0;
	if (n > 0 || (!iteration && !(f.specifier == 'p' && f.dot))) // n이 0일때, %p이고 정밀도가 0이라면 16진수 출력 x
	{
		remainder = n % 16; // 마지막에 출력할 문자 저장
		if (f.specifier == 'X') // X라면 대문자 16진수 출력
			character = HEXUP[remainder];
		else // x or p라면 소문자 16진수 출력
			character = HEXLOW[remainder];
		n /= 16; // n /= 16
		iteration = 1; // 넘어온 iteration이 0일 때, %p가 아니거나 정밀도가 0이 아니라면 이후에도 출력해야 하므로 iteration = 1로 둔다.
		print_len += ft_recursive_hex(f, n, iteration); // 재귀를 통해 앞에서 부터 16진수 출력
		print_len += ft_printchar(character); // 16진수 출력
	}
	return (print_len);
}


// 0플래그가 존재하고 정밀도가 존재할 때(0 포함) 공백 출력 후 0x출력
// 0플래그가 존재하고 정밀도가 존재하지 않을 경우 0x출력 후 0출력
// 0플래그가 존재하지 않고, -플래그가 존재하지 않을 때 공백 출력 후 0x출력
// -플래그가 존재할 때 0x출력 후 16진수 출력 후 공백 출력
int	ft_print_hex(t_format f, unsigned long n, int len)
{
	int	print_len;

	print_len = 0;
	if (f.zero) // 0플래그 존재하고 정밀도가 존재하지 않을 떄 #플래그 일 경우 0x 먼저 출력
		print_len += ft_printnstr(ft_sharp(f), 2 * (f.sharp && !f.dot && n));
	if (!f.minus && f.zero && f.width > f.precision && (!f.dot || f.neg_prec))
		print_len += ft_printnchar('0', f.width - f.precision);
	else if (!f.minus && f.width > f.precision) // -플래그, 0플래그가 없을 떄 
		print_len += ft_printnchar(' ', f.width - f.precision); // width - precision만큼 공백 출력
	print_len += ft_printnstr(ft_sharp(f), 2 * (f.sharp && f.dot && n)); // 0플래그가 존재하고 정밀도가 존재할 때 공백 출력 후 0x출력
	if (!f.zero) // 0플래그가 존재하지 않고, 정밀도가 존재하지 않을 때 공백 출력 후 0x출력
		print_len += ft_printnstr(ft_sharp(f), 2 * (f.sharp && n && !f.dot)); 
	print_len += ft_printnchar('0', f.precision - len); // precision - len만큼 0출력 precision > len 일 경우
	if (len) // len이 존재한다면
		print_len += ft_recursive_hex(f, n, n); // 재귀를 통한 16진수 출력
	if (f.minus && f.width > f.precision) // -플래그가 존재할 경우
		print_len += ft_printnchar(' ', f.width - f.precision); // 16진수 출력 후 공백 출력
	return (print_len); // 출력한 길이 반환
}

int	ft_print_x(t_format f, va_list ap)
{
	int				print_len;
	unsigned int	n;
	int				len;

	print_len = 0;
	n = va_arg(ap, unsigned int); // unsigned int 형으로 가변인자에서 받아옴
	len = ft_nbrlen(n, 16); // len의 길이 지정
	if (!n && (!f.precision && f.dot)) // n이 0이거나 정밀도가 0이라면
		len = 0; // len = 0
	if (f.neg_prec || f.precision < len || !f.dot) // 정밀도가 없거나, 정밀도가 음수거나, 정밀도가 len보다 작다면
		f.precision = len; // precision = len
	if (f.sharp && n) // n이 존재하고 sharp플래그가 있다면
		f.width -= 2; // 0x or 0X의 공간만큼 width에서 뺌
	print_len += ft_print_hex(f, n, len); // 16진수 출력
	return (print_len); // 출력한 길이 반환
}

ft_print_nbrs.c

#include "ft_printf.h"

char	oper(t_format f) // 음수라면 -를 반환, +플래그 존재시 +반환
{
	if (f.plus)
		return ('+');
	return ('-');
}

int	ft_print_nbr(t_format f, char *nbr, int len)
{
	int	print_len;

	print_len = 0;
	f.width -= (f.space && !f.neg && !f.plus && f.width);
    // space플래그가 있을때, 음수가 아니고, +플래그가 없다면 width보다 1만큼 작게 나옴
	if (f.neg || f.plus) // 음수거나 +플래그가 있는데 0플래그가 있고, 정밀도가 없거나 음수라면 바로 부호 출력
		print_len += ft_printnchar(oper(f), f.zero && (!f.dot || f.neg_prec));
	else if (f.space) // 공백 플래그가 있고, 정밀도가 없으며 0플래그가 존재한다면 공백 출력 -> 양수에 대해 + 플래그가 없을 떄
		print_len += ft_printnchar(' ', f.zero && !f.dot);
	if (!f.minus && f.zero && f.width > f.precision && (!f.dot || f.neg_prec)) // 0플래그가 존재하고 정밀도가 없거나 음수라면 0출력
		print_len += ft_printnchar('0', f.width - f.precision - f.neg - f.plus);
	else if (!f.minus && f.width > f.precision) // 0플래그, -플래그가 없다면 공백 출력
		print_len += ft_printnchar(' ', f.width - f.precision - f.neg - f.plus);
	if (f.neg || f.plus) // 음수거나 +플래그가 있는데 0플래그가 없거나 양수인 정밀도가 존재한다면 0이나 공백 후 부호 출력
		print_len += ft_printnchar(oper(f), !f.zero || (f.dot && !f.neg_prec));
	else if (f.space) // 공백 플래그가 존재하고 0플래그가 없거나 정밀도가 존재한다면 공백 출력
		print_len += ft_printnchar(' ', !f.zero || f.dot);
	print_len += ft_printnchar('0', f.precision - len); // precision - len만큼 0출력
	print_len += write(1, nbr, len); // nbr출력
	if (f.minus && f.width > f.precision) // -플래그가 존재한다면 숫자 출력 후 공백 출력
		print_len += ft_printnchar(' ', f.width - f.precision - f.neg - f.plus);
	return (print_len); // 출력한 길이 반환
}

int	ft_print_u(t_format f, va_list ap)
{
	char			*nbr;
	unsigned int	n;
	int				print_len;
	int				len;

	print_len = 0;
	n = va_arg(ap, unsigned int); // unsigned int형을 가변인자로 받아옴
	f.plus = 0; // %u는 plus플래그가 없다
	nbr = ft_uitoa(n); // uitoa함수를 통해서 nbr에 문자화
	len = ft_strlen(nbr); // nbr의 길이를 구함
	if (*nbr == '0' && (!f.precision && f.dot)) // 정밀도가 0이고, nbr이 '0'일 경우
		len = 0; // len = 0
	if (f.precision < len || !f.dot) // 정밀도가 존재하지 않거나 정밀도 < len일 경우
		f.precision = len; // 정밀도 = len
	print_len += ft_print_nbr(f, nbr, len); // 플래그에 따라 출력
	free(nbr); // itoa를 통한 동적할당이 있었으므로 해제
	return (print_len); // 출력한 길이 반환
}

int	ft_print_d_i(t_format f, va_list ap)
{
	char			*nbr;
	long long		n;
	int				print_len;
	int				len;

	print_len = 0;
	n = va_arg(ap, int); // int형을 가변인자로 받아옴
	if (n < 0) // n이 음수일 경우
	{
		f.neg = 1; // 음수 플래그에 표시
		f.plus = 0; // 음수 이므로 plus플래그는 존재하지 않음
		n *= -1; // 음수 부호는 따로 찍을 예정이므로 양수화
	}
	nbr = ft_litoa(n); // litoa함수를 통해서 longlong 형태로 itoa사용할 수 있도록 함
	len = ft_strlen(nbr); // nbr의 길이 반환
	if (*nbr == '0' && (!f.precision && f.dot)) // 정밀도가 0이고, nbr이 '0'일 경우
		len = 0; // len = 0
	if (f.precision < len || !f.dot) // 정밀도가 없거나 len보다 작을 경우
		f.precision = len; // 정밀도 = len
	print_len += ft_print_nbr(f, nbr, len); // 플래그에 따라서 출력
	free(nbr); // itoa를 통한 동적할당이 있었으므로 해제
	return (print_len); // 출력한 길이 반환
}

ft_print_point.c

#include "ft_printf.h"

int	count_pnt(t_format f, size_t n, size_t iteration)
{
	int	pnt_len;

	pnt_len = 1;
	if (n > 0 || !(iteration || f.dot)) // &p일 때 n이 0이고 정밀도가 있다면 출력x임
	{
		if (n < 16) //n이 16보다 작다면
			return (1); // 길이 1
		n /= 16;
		iteration = 1;
		pnt_len += count_pnt(f, n, iteration); // 재귀를 통한 길이 구하기
	}
	return (pnt_len);
}

int	ft_print_p(t_format f, va_list ap)
{
	int		print_len;
	size_t	n;
	int		len;

	print_len = 0;
	n = (size_t)va_arg(ap, void *); // void * 형으로 가변인자에서 가져온 뒤 size_t(unsigned long)으로 캐스팅
	len = count_pnt(f, n, n); // 포인터의 길이 구하기
	if (!n && (!f.precision  && f.dot)) // n이 0이고, 정밀도가 0이라면
		len = 0; // len - 0
	if (f.precision < len || !f.dot) // 정밀도가 없거나 len보다 작다면
		f.precision = len; // 정밀도 = len
	f.width -= 2; // 0x출력할 만큼 width 감소
	print_len += write(1, "0x", 2 * f.zero); // 0플래그가 있는 경우 0x먼저 출력
	if (!f.minus && f.width > f.precision && !f.dot && f.zero)
		print_len += ft_printnchar('0', (f.width - f.precision));
	else if (!f.minus && f.width > f.precision)
		print_len += ft_printnchar(' ', (f.width - f.precision));
	print_len += write(1, "0x", 2 * !f.zero); // 0플래그가 없을경우 공백 이후 출력
	print_len += ft_printnchar('0', (f.precision - len) * (n != 0)); // n이 0이 아닐 때 precision - len 만큼 0출력
	print_len += ft_printnchar('0', f.precision * (f.dot && !n));
	if (len) // len이 존재한다면
		print_len += ft_recursive_hex(f, n, n); // 16진수 출력
	if (f.minus && f.width > f.precision)
		print_len += ft_printnchar(' ', f.width - f.precision); // '-'플래그 존재시 16진수 출력 이후 공백 출력
	return (print_len);
}

ft_print_utils.c

#include "ft_printf.h"

int	ft_nbrlen(long n, int base)
{
	int	len;

	len = 0;
	if (!base)
		base = 10;
	if (n == 0)
		return (1);
	while (n != 0)
	{
		len++;
		n /= base;
	}
	return (len);
}

char	*ft_uitoa(unsigned int n)
{
	char	*str;
	int		len;

	len = ft_nbrlen(n, 10);
	str = (char *)malloc(sizeof(char) * (len + 1));
	if (!str)
		return (0);
	str[len] = '\0';
	while (len-- > 0)
	{
		str[len] = (n % 10) + '0';
		n /= 10;
	}
	return (str);
}

int	count_ll(long long n)
{
	int	cnt_nbr;

	if (n == 0)
		return (1);
	cnt_nbr = 0;
	if (n < 0)
		cnt_nbr = 1;
	while (n)
	{
		n /= 10;
		cnt_nbr++;
	}
	return (cnt_nbr);
}

char	*ft_litoa(long long n)
{
	long long	nbr;
	char		*res;
	int			len;

	len = count_ll((long long)n);
	nbr = n;
	if (n < 0)
		nbr *= -1;
	res = (char *)malloc(sizeof(char) * (len + 1));
	if (!res)
		return (NULL);
	res[len--] = '\0';
	while (len >= 0)
	{
		res[len] = nbr % 10 + '0';
		nbr /= 10;
		len--;
	}
	if (n < 0)
		res[0] = '-';
	return (res);
}

Makefile

NAME = libftprintf.a
LIBFT = libft
LIBFT_LIB = libft.a

SRCS = ./ft_printf.c ./ft_format.c ./ft_parse.c ./ft_print_chars.c ./ft_print_hex.c ./ft_print_nbrs.c
OBJS = $(SRCS:.c=.o)
INCS = .
AR = ar rcs
CC = cc
CFLAGS = -Wall -Wextra -Werror
RM = rm -rf

.c.o:
	$(CC) $(CFLAGS) -c $< -o $(<:.c=.o) -I$(INCS)

$(NAME): $(OBJS)
	make all -C $(LIBFT)/
	cp $(LIBFT)/$(LIBFT_LIB) $(NAME)
	$(AR) $(NAME) $(OBJS)

all: $(NAME)

fclean: clean
	$(RM) $(NAME)
	make fclean -C $(LIBFT)

clean:
	$(RM) $(OBJS)
	make clean -C $(LIBFT)

re: fclean all

.PHONY: all clean fclean re
profile
진짜 공부하자

0개의 댓글