개요
프엪은 가이드를 찾아봐야할 정도로 난이도가 좀 있는 편이라고 생각해서 글을 쓰기 시작한다.
사실은 과제를 완성하고 몇달만에 냈는데, 온라인 평가가 싫다고 계속 미루다가 더 미루기가 힘들어서 평가를 받았다. 정작 세 번 다 온라인 평가여서 뭔가 싱숭생숭했음
코드는... 맘에 안들어서 보여주고 싶진 않지만 [깃헙 주소]를 참고. 글에서 설명하기 위해서 필요한 만큼은 스크린샷으로 첨부할 것이고, 하위 함수들은 보는거보다 직접 만드는 걸 추천한다.
서브젝트 및 평가표 정리
첫페이지에 과제에 대한 설명이 다 담겨있다.
요약 : 이 프로젝트는 꽤 단순합니다. 여러분은 printf 함수를 직접 구현하시면 됩니다.
그냥 printf 를 만들면 된다. 그중에서 서식지정자 cspdiuxX% 를, 보너스에서는 -0.# +(공백 하나 있음)를 구현하면 된다.
추가적으로 '성공적인 ft_printf의 핵심은 체계적이고 확장성 있는 코드입니다. '보너스를 구현하실 예정이라면, 단순하게 접근하지 않기 위하여 처음부터 어떻게 구현을 해야 할지 고민해 보셔야 할 겁니다.'라는 내용처럼 미리 구조를 잘 따져보고 구현하는 게 많은 도움이 된다.
구현 시작!... 그 전에 뭘 만들어야 하는지 공부하기
printf 를 그냥 써보기만 하고, 폭이나 플래그에 대해서 모르던 입장에서 뭘 구현해야 하는지가 좀 막막했다.
그래서 먼저 기본적인 서식지정자의 내용과 플래그의 종류들을 이해하는데 신경을 좀 썼다.
코딩도장의 글을 읽으면서 어느 정도는 알겠는데 구현할 수 있을 정도로 이해가 되진 않았다. man 을 읽어봐도 큰 도움이 되진 않았다. 그래서 그냥 무식하게 다 찍어봤다.
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <limits.h>
int main(void)
{
int i;
int j;
printf("%% test");
printf("f %%%%, [%%]\n");
printf("f %%5%%, [%5%]\n");
printf("f %%-5%%, [%-5%]\n");
printf("f %%05%%, [%05%]\n");
printf("f %%-05%%, [%-05%]\n");
printf("id basic test n width test \n");
printf("f %%i = 2147483647, [%i]\n", 2147483647);
printf("f %%d = -2147483648, [%d]\n", (int)(-2147483678));
printf("f %%d = 0, [%d]\n", 0);
printf("f %%7i = 33, [%7i]\n", 33);
printf("f %%7d = -14, [%7d]\n", -14);
printf("f %%3i = 0, [%3i]\n", 0);
printf("f %%5d = 52625, [%5d]\n", 52625);
printf("f %%5i = -2562, [%5i]\n", -2562);
printf("f %%4d = 94827, [%4d]\n", 94827);
printf("f %%4i = -2464, [%4i]\n", -2464);
printf("f %%-7d = 33, [%-7d]\n", 33);
printf("f %%-7i = -14, [%-7i]\n", -14);
printf("f %%-5i = 52625, [%-5i]\n", 52625);
printf("f %%-5d = -2562, [%-5d]\n", -2562);
printf("f %%-4d = 94827, [%-4d]\n", 94827);
printf("f %%-4i = -2464, [%-4i]\n\n", -2464);
printf("id precision test \n");
printf("f %%.i = 2, [%.i]\n", 2);
printf("f %%.d = -3, [%.d]\n", -3);
printf("f %%.5i = 2, [%.5i]\n", 2);
printf("f %%.6d = -3, [%.6d]\n", -3);
printf("f %%.3i = 0, [%.3i]\n", 0);
printf("f %%.5d = 5263, [%.5d]\n", 5263);
printf("f %%.5d = -2372, [%.5d]\n", -2372);
printf("f %%.7i = 13862, [%.7i]\n", 13862);
printf("f %%.7i = -23646, [%.7i]\n\n",-23646);
printf("id zero field width padding test \n");
printf("f %%05i = 43, [%05i]\n", 43);
printf("f %%07d = -54, [%07d]\n", -54);
printf("f %%03i = 0, [%03i]\n", 0);
printf("f %%04d = 634, [%04d]\n", 634);
printf("f %%04i = -532, [%04i]\n", -532);
printf("f %%04d = -4825, [%04d]\n\n", -4825);
printf("id width and precision test \n");
printf("f %%8.5i = 34, [%8.5i]\n", 34);
printf("f %%8.5i = 0, [%8.5i]\n", 0);
printf("f %%8.3d = 8375, [%8.3d]\n", 8375);
printf("f %%8.3i = -8473, [%8.3i]\n", -8473);
printf("f %%3.7d = 3267, [%3.7d]\n", 3267);
printf("f %%3.7i = -2375, [%3.7i]\n", -2375);
printf("f %%3.3d = 6983, [%3.3d]\n", 6983);
printf("f %%3.3i = -8462, [%3.3i]\n", -8462);
printf("f %%-8.5i = 34, [%-8.5i]\n", 34);
printf("f %%-8.5i = 0, [%-8.5i]\n", 0);
printf("f %%-3.8d = 8375, [%-3.8d]\n", 8375);
printf("f %%-3.8i = -8473, [%-3.8i]\n", -8473);
printf("f %%-3.7d = 3267, [%-3.7d]\n", 3267);
printf("f %%-3.7i = -2375, [%-3.7i]\n", -2375);
printf("f %%-3.3d = 6983, [%-3.3d]\n", 6983);
printf("f %%-3.3i = -8462, [%-3.3i]\n\n", -8462);
printf("id width and precision with zeropadding test \n");
printf("f %%08.5i = 34, [%08.5i]\n", 34);
printf("f %%010.5d = -216, [%010.5d]\n", -216);
printf("f %%08.5i = 0, [%08.5i]\n", 0);
printf("f %%08.3d = 8375, [%08.3d]\n", 8375);
printf("f %%08.3i = -8473, [%08.3i]\n", -8473);
printf("f %%03.7d = 3267, [%03.7d]\n", 3267);
printf("f %%03.7i = -2375, [%03.7i]\n", -2375);
printf("f %%03.3d = 6983, [%03.3d]\n", 6983);
printf("f %%03.3i = -8462, [%03.3i]\n\n", -8462);
printf("id width and precision, left-justified with zeropadding test \n");
printf("f %%0-8.5i = 34, [%0-8.5i]\n", 34);
printf("f %%0-10.5d = -216, [%0-10.5d]\n", -216);
printf("f %%0-8.5i = 0, [%0-8.5i]\n", 0);
printf("f %%0-8.3d = 8375, [%0-8.3d]\n", 8375);
printf("f %%0-8.3i = -8473, [%0-8.3i]\n", -8473);
printf("f %%0-3.7d = 3267, [%0-3.7d]\n", 3267);
printf("f %%0-3.7i = -2375, [%0-3.7i]\n", -2375);
printf("f %%0-3.3d = 6983, [%0-3.3d]\n", 6983);
printf("f %%0-3.3i = -8462, [%0-3.3i]\n\n", -8462);
printf("id zero test \n");
printf("f %%i = 0, [%i]\n", 0);
printf("f %%5i = 0, [%5i]\n", 0);
printf("f %%.0i = 0, [%.0i]\n", 0);
printf("f %%.i = 0, [%.i]\n", 0);
printf("f %%5.0i = 0, [%5.0i]\n", 0);
printf("f %%5.i = 0, [%5.i]\n", 0);
printf("f %%-5.0i = 0, [%-5.0i]\n", 0);
printf("f %%-5.i = 0, [%-5.i]\n\n", 0);
printf("id space test \n");
printf("f %% i = 34, [% i]\n", 34);
printf("f %% d = -216, [% d]\n", -216);
printf("f %% 3.3d = 8375, [% 3.3d]\n", 8375);
printf("f %% 3.3i = -8473, [% 3.3i]\n", -8473);
printf("f %% 8.3d = 8375, [% 8.3d]\n", 8375);
printf("f %% 8.3i = -8473, [% 8.3i]\n", -8473);
printf("f %% 3.7d = 3267, [% 3.7d]\n", 3267);
printf("f %% 3.7i = -2375, [% 3.7i]\n\n", -2375);
printf("id plus sign test \n");
printf("f %%+i = 34, [%+i]\n", 34);
printf("f %%+d = -216, [%+d]\n", -216);
printf("f %%+3.3d = 8375, [%+3.3d]\n", 8375);
printf("f %%+3.3i = -8473, [%+3.3i]\n", -8473);
printf("f %%+8.3d = 8375, [%+8.3d]\n", 8375);
printf("f %%+8.3i = -8473, [%+8.3i]\n", -8473);
printf("f %%+3.7d = 3267, [%+3.7d]\n", 3267);
printf("f %%+3.7i = -2375, [%+3.7i]\n\n", -2375);
printf("id space with plus sign test \n");
printf("f %%+ i = 34, [%+ i]\n", 34);
printf("f %%+ d = -216, [%+ d]\n", -216);
printf("f %%+ 3.3d = 8375, [%+ 3.3d]\n", 8375);
printf("f %%+ 3.3i = -8473, [%+ 3.3i]\n", -8473);
printf("f %%+ 8.3d = 8375, [%+ 8.3d]\n", 8375);
printf("f %%+ 8.3i = -8473, [%+ 8.3i]\n", -8473);
printf("f %%+ 3.7d = 3267, [%+ 3.7d]\n", 3267);
printf("f %%+ 3.7i = -2375, [%+ 3.7i]\n\n", -2375);
printf("u - unsigned int test \n");
printf("f %u\n", 4294967295u);
printf("%u\n", 42);
printf("Kashim a %u histoires à raconter\n", 1001);
printf("Il fait au moins %u\n", -8000);
printf("%u\n", -0);
printf("%u\n", 0);
printf("%u\n", INT_MAX);
printf("%u\n", INT_MIN);
printf("%u\n", INT_MIN - 1);
printf("%u\n", INT_MAX + 1);
printf("%%u 0000042 == |%u|\n", 0000042);
printf("%%u \t == |%u|\n", '\t');
printf("%%u Lydie == |%u|\n\n", 'L'+'y'+'d'+'i'+'e');
printf("X - unsigned hexadecimal test \n");
printf("f %%X = [%X]\n", 4294967295u);
printf("f %%-X = [%-X]\n", 42);
printf("f Kashim a [%X] histoires à raconter\n", 1001);
printf("f Il fait au moins [%X]\n", -8000);
printf("f %%08X = [%08X]\n", -0);
printf("f %%.8X = [%.8X]\n", 0);
printf("f %%#X = [%#X]\n", INT_MAX);
printf("f %%8.0X = [%8.0X]\n", INT_MIN);
printf("f %%0.8X = [%0.8X]\n", INT_MIN - 1);
printf("f %%8X = [%8X]\n", INT_MAX + 1);
printf("f %%8X 0000042 == |%8X|\n", 0000042);
printf("f %%-8X \t == |%-8X|\n", '\t');
printf("f %%-8.0X Lydie == |%-8.0X|\n\n", 'L'+'y'+'d'+'i'+'e');
printf("x - unsigned hexadecimal test \n");
printf("f %%x = [%x]\n", 4294967295u);
printf("f %%-x = [%-x]\n", 42);
printf("f Kashim a [%x] histoires à raconter\n", 1001);
printf("f Il fait au moins [%x]\n", -8000);
printf("f %%08x = [%08x]\n", -0);
printf("f %%.8x = [%.8x]\n", 0);
printf("f %%#x = [%#x]\n", INT_MAX);
printf("f %%8.0x = [%8.0x]\n", INT_MIN);
printf("f %%0.8x = [%0.8x]\n", INT_MIN - 1);
printf("f %%8x = [%8x]\n", INT_MAX + 1);
printf("f %%8x 0000042 == |%8x|\n", 0000042);
printf("f %%-8x \t == |%-8x|\n", '\t');
printf("f %%-8.0x Lydie == |%-8.0x|\n\n", 'L'+'y'+'d'+'i'+'e');
printf("p - pointer test \n");
printf("%p\n", NULL);
printf("%5p\n", NULL);
printf("%2p\n", NULL);
printf("%.p\n", NULL);
printf("%5.p\n", NULL);
printf("%2.p\n", NULL);;
printf("%9.2p\n", 1234);
printf("%2.9p\n", 1234);
printf("%.5p\n", 0);
printf("%.0p\n", 0);
printf("%5p\n\n", 0);
printf("c - char test \n");
printf("[%c]\n", 42);
printf("[Kashim a [%c] histoires à raconter\n", 1001);
printf("[Il fait au moins [%c]\n", -8000);
printf("[%c]\n", -0);
printf("[%c]\n", 0);
printf("[%c]\n", INT_MAX);
printf("[%4c]\n", 'c');
printf("[%-2c]\n", '\n');
printf("[%04c]\n", 'l');
printf("[% c]\n", 'y');
printf("[%c]\n", ' ');
printf("[%#c]\n", 'e');
printf("[%+c]\n", 's');
printf("[%.2c]\n", 't');
printf("[%c]\n", ' ');
printf("[%c]\n", 'f');
printf("[%c]\n", '\r');
printf("[%c]\n\n", '\t');
printf("s - string test \n");
printf("hello, %8s.\n", "gavin");
printf("%-8s\n", "testing testing");
printf("%08s%8.3s\n", "hello", "world");
printf("..%#8s stuff % 8s\n", "a", "b");
printf("this %s is empty\n", "");
printf("%s !", "Ceci n'est pas un \0 exercice !\n\n");
char *null_str = NULL;
printf("%s everywhere\n", null_str);
system("leaks test > leaks_result; cat leaks_result | grep leaked && rm -rf leaks_result");
}
다른 테스터기들을 참고하면서 일일이 찍어보니까 좀 감이 왔다. 구조를 어떻게 해야 할지도 조금 감이 왔음.
% | di | u | xX | p | c | s | |
정밀도(.) | X | 최소 문자 갯수 | 최소 문자 갯수 | 최소 문자 갯수 | 최소 문자 갯수 | X | 최대 문자 갯수 |
- | 공백을 뒤로 | 공백을 뒤로 | 공백을 뒤로 | 공백을 뒤로 | 공백을 뒤로 | 공백을 뒤로 | 공백을 뒤로 |
0 | 공백을 0으로 | 공백을 0으로 | 공백을 0으로 | 공백을 0으로 | 공백을 0으로 | UB | UB |
+ | X | 양수에 + | X | X | X | X | X |
# | X | X | X | 숫자에 0x 0X붙임 | X(이미 적용) | X | X |
(공백) | X | 양수에 ' '(공백) | X | X | X | X | X |
구현 시작
ft_printf 함수 자체에는 가변 인자를 가져와서 다른 함수에 넘겨주는 동작만을 수행했다.
printf에 문자열이 들어오면 % 이 나올때까지 쭉 출력을 했다. 그 다음에 %이 나오면 구조체를 하나 만들어서 거기에 플래그와 서식지정자를 담고, 서식지정자가 나오면 그거에 맞춰서 분기해서 출력을 해줬다.
가장 먼저 %를 구현하고 그다음 di, u, xX, p, c, s 순서로 구현하였음.
%를 가장 먼저 구현하는게 좋을 것 같다고 생각했다. % 는 printf("%%);의 형태로 쓰여서 %를 출력하는 서식지정자이다. 이걸 만들면 다른 서식지정자에 대해서도 구현에 대한 방향을 잡을 수가 있다.
우선 %와 결합된 서식지정자들이 어떻게 쓰이는지를 직접 확인해보자.
그러면 폭이 있을 때 폭만큼 공백을 앞에 출력해주고, 폭과 -가 있으면 공백이 뒤에 출력되고, 폭과 0이 같이 있으면 폭만큼 앞에 0이 출력되는 걸 확인할 수 있다. 나머지는 특별한 동작이 없다.
여기서 한가지 중요한 부분은 % 앞에 나오는 플래그에 0이 나오는 경우가 두가지라는 점이다. %10% 로 0이 나오면 이건 폭을 의미하고 %05% 로 0이 나오면 이건 플래그 0을 의미한다. 그래서 이 부분을 주의해서 처리해야한다. 폭이 비어있을 때 0이 나오면 0을 플래그로 처리하고 폭이 비어있지 않을 때 0이 나오면 기존 폭에 10을 곱해주는 식으로 처리하면 된다.
요약하면 -가 있으면 공백이 뒤에, 없으면 공백 혹은 0이 앞에 온다는 걸 알 수 있으니 -의 유무를 조건으로 걸어서 공백 혹은 0을 출력하는 함수를 % 의 출력보다 먼저 실행하거나 나중에 실행한다.
대부분의 서식지정자가 플래그에 따라서 동작이 다르지만 -에 따라 분기되는 건 비슷하니까 이걸 기본 틀로 잡고 di까지 진행해보자
di는 printf(%d, tmp);의 형태로 쓰여서 10진수 정수를 출력하는 서식지정자이다. % 뒤에 d나 i가 바로 나오면 다음 가변 인자를 정수로 가져와서 출력해주고 출력하면서 size를 가져와서 반환값으로 사용했다.
di는 폭과 정밀도, - 0 공백 +를 구현해야 한다. 더 많은 플래그를 구현해야 하지만 본 함수의 코드는 -를 기준으로 분기한 % 와 크게 다르지 않다.
다만 하위 함수들에서 처리해야 하는 예외들이 많다 보니 printf를 찍어볼 때 명확히 이해를 안 하고 지나가면 코드가 엄청 지저분해진다. 여러분은 꼭 다 이해하고 시작하길 바란다...
di를 마무리하고 나면 u는 가변인자를 unsigned int로 가져와서 di와 동일한 로직을 공백과 + 플래그가 동작하지 않게 만들어서 사용하면 되고, 사실 di까지 하고나면 나머지는 쉽게 할 수 있을 거라고 생각한다.
잘못한 점과 후기
처음에 실수했던 부분이, '출력해야 하는 숫자가 0이면서 정밀도가 0이거나, .(정밀도 플래그) 이후에 비어있으면 폭만큼의 공백을 출력한다. 폭이 없으면 아무것도 출력하지 않는다'를 인지하지 못하고 넘어가서 나중에 테스터기에서 발견하고 예외 처리하느라 엄청 고생했다.
그리고 di에서 u를 쉽게 분기해서 만들고 나니 자신감이 생겨서 xX 이후에 p를 같은 함수를 사용하려 했는데, 모양만 비슷하지 완전 새로 짜야하는 함수들이었다. 이걸 다 만들고 깨달은 덕분에 굉장히 이상한 함수가 만들어졌다. 이게 가장 후회스러움.
s에서 가변 인자에 null이 들어오면 (null)을 출력한다. 이 부분도 처음에 모르고 지나가서 좀 헤맴.
구조를 잘못 이해하고 나중에 예외처리로 때운 부분들을 처음부터 다시 짜면 훨씬 잘 짤 수 있을 거란 생각이 들긴 했는데, 평가받기 전에 진지하게 고민하다 결국 그냥 냈다. 그러다 보니 평가받을때 가독성이 안좋은 부분에서 민망함을 참아가면서 평가받음.
그러다보니 다음부턴 가독성을 더 신경 쓰고, 예외처리를 덕지덕지 발라서 복잡해진 함수들은 그냥 새로 짜기로 마음먹었다. 다음부턴 더 잘해야지.
'프로그래밍' 카테고리의 다른 글
[42 서울] push_swap을 케이크처럼 쉽게 먹는 법 - 2 (0) | 2022.01.18 |
---|---|
[42 서울] push_swap을 케이크처럼 쉽게 먹는 법 - 1 (0) | 2022.01.16 |
[42서울] Born2beroot 설치 및 세팅만 정리 (0) | 2021.11.28 |
새로 들어오는 카뎃들을 위한 libft 가이드 (0) | 2021.10.30 |
자주 쓰게되는 vim 명령어 (0) | 2021.02.20 |