마이크로소프트 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, '%' 서식 지정자 구현
<stdarg.h>
va_list : 가변 인자 목록
va_start : 가변인자 처음을 넘겨준다.
va_arg : 해당 가변인자를 반환하고, 가리키는 주소를 다음 가변인자로 넘겨준다.
va_copy : 가변인자로 선언된 변수를 복사한다.
va_end : 사용한 가변인자의 포인터를 다시 NULL 로 만들 때 주로 사용한다.
사용하려면, 반드시 "자료형과 이름이 고정된 매개 변수"가 하나 이상 필요합니다.
예)
int sum(int a, ...) // OK : D
int sum(...) // KO :(
여러 개의 가변 인자들을 받아올 수 있으며 가변 인자는 위의 매크로 함수로 많이 이용합니다.
서식 지정자는 필수, 나머지 4개의 옵션은 선택입니다.
서식지정자는 이런 것들이 있습니다.
구현해야할 옵션들은 앞으로 나와있습니다.
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를 붙여줍니다.
' ' = 한 칸 띄어서 출력합니다. (앞에 '+' 같은 플래그 들이 있다면 무시된다.)
'+' = 양수라면 + 기호를 붙여줍니다.
'-' = 왼쪽으로 정렬시켜 줍니다.
'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
%[플래그][너비][ .숫자 정밀도 | .문자열 최소/최대 출력 개수][길이][서식 지정자]
%를 만나기 전 까지는, 일반 문자열이므로 출력
'%' 를 만나면, 여러 가지 조건을 검사.
먼저, 만만한 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 형태로 바꾸어주니 잘 나왔다 : )