ft_printf

koeyhoyh·2022년 3월 9일
1

42Seoul

목록 보기
6/11

참고 :

마이크로소프트 docs : https://docs.microsoft.com/ko-kr/cpp/c-runtime-library/reference/va-arg-va-copy-va-end-va-start?view=msvc-170
모두의 코드 printf : https://modoocode.com/35
Apple open source printf : https://opensource.apple.com/source/xnu/xnu-201/osfmk/kern/printf.c.auto.html
42서울 카뎃님 블로그 : https://kirkim.github.io/42seoul/2021/03/03/a_formatted.html


4월 8일 :
main 문에서 test할 수 있도록 실행되는 프로그램 작성
d, i, s, c, '%' 서식 지정자 구현


Mendatory

사용 가능한 외부 함수

<stdarg.h>

va_list : 가변 인자 목록

  • va_start, va_arg, va_end 같은 매크로 함수를 사용할 때 필요한 정보가 포함되어 있습니다.

va_start : 가변인자 처음을 넘겨준다.
va_arg : 해당 가변인자를 반환하고, 가리키는 주소를 다음 가변인자로 넘겨준다.
va_copy : 가변인자로 선언된 변수를 복사한다.
va_end : 사용한 가변인자의 포인터를 다시 NULL 로 만들 때 주로 사용한다.


ft_printf(const char *, ...) 의 ... (가변 인자)

사용하려면, 반드시 "자료형과 이름이 고정된 매개 변수"가 하나 이상 필요합니다.

예)
int sum(int a, ...) // OK : D
int sum(...) // KO :(

여러 개의 가변 인자들을 받아올 수 있으며 가변 인자는 위의 매크로 함수로 많이 이용합니다.


printf 의 서식

%[플래그][너비][ .숫자 정밀도 | .문자열 최소/최대 출력 개수][길이][서식 지정자]

서식 지정자는 필수, 나머지 4개의 옵션은 선택입니다.

서식 지정자

서식지정자는 이런 것들이 있습니다.

구현해야할 옵션들은 앞으로 나와있습니다.

  • d, i = signed decimal

o = signed octal

  • u = unsigned decimal

  • X, x = unsigned hexadecimal (0x, 0X : # 플래그를 사용할 때, 대문자, 소문자 차이)

  • c = first byte of argument (character)

  • s = bytes from the string (정밀도에 영향을 받는다. 정밀도 옵션이 없다면, 끝까지 출력한다.)

  • p = The void * pointer argument is printed in hexadecimal (as if by ‘%#x’ or ‘%#lx’).

  • % = print a '%'

플래그

"# +" 플래그를 구현하시오

  • # = 부호 없는 8, 16진수의 앞에 0, 0x를 붙여줍니다.

  • ' ' = 한 칸 띄어서 출력합니다. (앞에 '+' 같은 플래그 들이 있다면 무시된다.)

  • '+' = 양수라면 + 기호를 붙여줍니다.

다음 플래그들의 조합 (any combination) 을 구현하세요 : '-0.', 그리고 각 서식 지정자별 최소 폭

  • '-' = 왼쪽으로 정렬시켜 줍니다.

  • '0' = 왼쪽 빈 공간을 0으로 채워줍니다.

' ', '0' 등의 플래그는 '-' 플래그를 만나면 무시됩니다.

  • '.' = 정밀도 옵션, 부동 소수점을 가진 옵션들과 주로 사용되며, 너비를 결정해줍니다.

너비가 더 크다면, 왼쪽에 그 만큼 공백이 생기고,
원래 숫자의 소숫점 밑의 수보다 크면, 오른 쪽을 0으로 채웁니다.

출처 : https://kirkim.github.io/42seoul/2021/03/03/a_formatted.html

우리는 f를 구현하지 않으므로, 서식 지정자 s와 같이 사용합니다.

"[최소 너비].[최대 너비]" 로 사용하며 최소 너비보다 문자열이 작다면 왼 쪽에 공백을 추가하고, 최대 너비보다 문자열이 크다면 문자열이 잘립니다.

출처 : https://kirkim.github.io/42seoul/2021/03/03/a_formatted.html


설계

%[플래그][너비][ .숫자 정밀도 | .문자열 최소/최대 출력 개수][길이][서식 지정자]

%를 만나기 전 까지는, 일반 문자열이므로 출력
'%' 를 만나면, 여러 가지 조건을 검사.

format : d, i

먼저, 만만한 d, i 서식 지정자를 구현하기로 마음먹었다.
printf를 가지고 놀고 있는데 나온 경고들이다.

각각 ' '은 '+'를 가지고 있을 때 무시되고, '0'는 '-'를 가지고 있을 때 무시된다고 한다.

크게 분기를 나눈다면,

'-' flag 의 유무.

%X.Yd : X(총 너비), Y(기호, 공백이 아닌 실제 출력 너비)

Y : 가변인자의 출력 길이가 Y를 넘지 않는다면, 0으로 나머지를 다 채운다.

X : 0으로 다 채우고, '+', '-', ' ' 등의 플래그나 기호가 있으면 Y+1
X > Y + (1)(flag)이라면, X - Y + (1) 개만큼의 공백으로 앞, (-)뒤를 채운다.

padding_left, padding_right의 수정 필요하다.

/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   ft_decimal.c                                       :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: hyojeong <hyojeong@student.42seoul.kr>     +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2022/04/01 13:12:35 by hyojeong          #+#    #+#             */
/*   Updated: 2022/04/08 13:56:19 by hyojeong         ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

#include "ft_printf.h"

#include <unistd.h>
#include <stdlib.h>

size_t	get_numlen(long num, int flag)
{
	size_t	len;
	int		mod;

	len = 0;
	mod = 10;
	if (flag)
		mod = 16;
	if (num < 0)
		len++;
	if (num == 0)
		len = 1;
	while (!(num == 0))
	{
		num = num / mod;
		len++;
	}
	return (len);
}

void	ft_putnbr(int num, int hexa)
{
	const char	*hexanum = "0123456789abcdef";
	long		nb;
	int			mod;

	nb = num;
	mod = 10;
	if (hexa)
		mod = 16;
	if (nb < 0)
		nb *= -1;
	if (nb >= mod)
		ft_putnbr(nb / mod, hexa);
	write(1, &hexanum[(nb % mod)], 1);
}

void	print_decimal(va_list ap, t_flag *flag)
{
	long	num;
	size_t	len;
	size_t	idx;

	idx = 0;
	num = va_arg(ap, int);
	if (num < 0)
		flag->plus = '-';
	len = get_numlen(num, flag->hexa);
	if (flag->padding_left < len)		// 최소, 최대 넓이보다 실제 출력할 숫자가 더 길때 
		flag->padding_left = 0;
	if (flag->padding_right < len)
		flag->padding_right = 0;
	if (flag->plus || num < 0)		//  +, -, ' ' 기호가 있다면 padding 개수를 줄여주어야 함.
		flag->padding_left--;
	if (!flag->left)		// 왼쪽 정렬 ('-') 플래그가 아닐 때
	{
		if (flag->padding_right)	// 최소  넓넓이이가  존존재재할할때때
		{
			while (flag->padding_left > flag->padding_right + idx)
			{
				write(1, " ", 1);
				idx++;
			}
			if (flag->plus)
				write(1, &flag->plus, 1);
			idx = 0;
			while (flag->padding_right > len + idx)
			{
				write(1, "0", 1);
				idx++;
			}
			ft_putnbr(num, flag->hexa);
		}
		else
		{
			if (flag->zero && flag->plus)
				write(1, &flag->plus, 1);
			while (flag->zero && flag->padding_left > len + idx)
			{
				write(1, "0", 1);
				idx++;
			}
			while (flag->padding_left > len + idx)
			{
				write(1, " ", 1);
				idx++;
			}
			if (!(flag->zero) && flag->plus)
				write(1, &flag->plus, 1);
			ft_putnbr(num, flag->hexa);
		}
	}
	else	// '-' flag 로 왼쪽 정렬일때
	{
		if (flag->padding_right)
		{
			if (flag->plus)
				write(1, &flag->plus, 1);
			while (flag->padding_right > len + idx)
			{
				write(1, "0", 1);
				idx++;
			}
			ft_putnbr(num, flag->hexa);
			while (flag->padding_left > flag->padding_right + idx)
			{
				write(1, " ", 1);
				idx++;
			}
		}
		else
		{
			if (flag->plus)
				write(1, &flag->plus, 1);
			ft_putnbr(num, flag->hexa);
			while (flag->padding_left > len + idx)
			{
				write(1, " ", 1);
				idx++;
			}
		}
	}
}

다듬지 않고 d 플래그 해결!


고민 1

현재, 플래그, 너비 등을 검사해서 구조체를 이용해 기억해두려고 하는데 discpxX% 등의 플래그가 있을 때 까지만 검사하고 싶다.
-> 먼저 서식지정자가 있을 때 까지만 문자열의 길이를 재서 그 곳 까지만 검사하게 하거나 임시로 문자열을 나누어서 검사하면 될 것 같은데??

해결 1

잴 필요도 없다. 전 과제에서 구현한 strchr 로 플래그들만 검사, width 도 strchr 이용해 검사하니 끝남.

고민 2

개행문자를 왜 인식하지 못하는가?

해결 2

// 메인 함수
int	ft_printf(const char *str, ...)
{
	va_list	ap;
	int		printf_len;
	
	printf_len = 0;
	if (str == 0)
		return (-1);
	va_start(ap, str);
	{
		while (*str)
		{
			if (!(*str == '%'))
			{
				write(1, str, 1);
				str++;
				printf_len++;
			}
			else
			{
				str++;
				branch(ap, &str, &printf_len);
			}
		}
	}
	va_end(ap);
	return (printf_len);
}

현재 나는 이런 식으로 여러 함수들에 집어넣고 있었는데, branch 그리고 함수 연결 부분에 문제가 있었다.

개행문자를 인식 못하는 것이 아니라, 서식 지정자 다음의 문자들을 전부 다 인식하지 못하고 있었다.

branch 함수를 살펴보니, 내가 했던 실수가 나왔다.

void	branch(va_list ap, const char **str, int *printf_len)
{
	t_flag	flag;

	(void)printf_len;
    while (**str)
    {
		flag = make_flag((char **)str);
		if (**str == 'c' || **str == 's')
			print_str(ap, &flag);
		if (**str == 'd' || **str == 'i')
			print_decimal(ap, &flag);
		if (**str == '%')
		{
			write(1, "%", 1);
			(*printf_len)++;
		}
		(*str)++;
    }
}

자세히 살펴보니 왜 while 문을 넣었지? 하는 의문이 들었다.
단순히 while 문을 제거해줌으로써, 뒤에 있던 문자들도 잘 출력되고, 서식 지정자가 여러 개 나오더라도 잘 출력되는 모습을 볼 수 있었다.
'+' 플래그가 2번 출력되던 현상도 자연스럽게 수정되었다...??

void	branch(va_list ap, const char **str, int *printf_len)
{
	t_flag	flag;

	(void)printf_len;
	flag = make_flag((char **)str);
	if (**str == 'c' || **str == 's')
		print_str(ap, &flag);
	if (**str == 'd' || **str == 'i')
		print_decimal(ap, &flag);
	if (**str == '%')
	{
		write(1, "%", 1);
		(*printf_len)++;
	}
	(*str)++;
    
}

고민 3 : 왜 %p 옵션이 잘 작동하지 않을까?

다른 printf와 비교해봤을 때, 정상적으로 작동하지 않는 모습을 확인했다.

8번이 내가 만든 함수, 9번이 실제 printf 함수이다.

va_arg(ap, unsigned char *); 

이렇게 받고, unsigned char 1바이트씩을 16진법으로 바꾸어 계산했는데 왜 이런 결과가 나오지?? 라고 생각했었다.

잘못 생각하고 있었다.

이렇게 받아오는 것이 아니라, void * 형태로 받아와 unsigned char, unsigned long 형태로 캐스팅해야 가능했다.

그리고, ft_putnbr 의 반환 형태가 int 형으로 되어있었다. long 형태로 바꾸어주니 잘 나왔다 : )

profile
내가 만들어낸 것들로 세계에 많은 가치를 창출해내고 싶어요.

0개의 댓글