42서울 1써클의 첫 과제로 결정한 printf는, 말그대로 c의 표준출력함수인 printf를 write, malloc, free만을 사용하여 새로 작성하는 것이 목표다. 먼저 이미 존재하는 함수의 기능을 구현하는 것이라 앞서 했던 과제에서처럼 입출력 테스트를 통해 주요 입출력을 확인하는 것으로 시작했다.
1.접근 방법
가장 먼저 떠오르는 방법은 printf의 format string 부분을 순회하며 조건문과 write 함수를 통해 출력과 반환값 계산을 동시에 하는 것이다. 이 방법의 장점은 mandatory part를 빠르게 끝낼 수 있고 코드가 짧아 시간을 아낄 수 있다. 단점은 bonus part의 경우 width, precision을 모두 매개변수로 넘겨주며 조건문으로 판단하면서 출력과 반환값을 동시에 처리하는 것이 불편하다는 점이 있다.
두번째로 생각한 방법은 printf 함수란 결국 format string에서 출력하는 string으로의 일대일 대응이기에 출력할 string을 malloc을 통해 작성한 후 마지막 반환값은 strlen으로 계산해주는 방법이다. 이 방법의 장점은 내부함수들에서 출력값이 char* str으로 통일성이 있고 bonus part도 한결 수월하게 작성할 수 있다 생각했다. 단점으로는 malloc 사용에 따른 할당이 되었는지 체크, 중간중간 free하는 것등을 포함하게 되어 코드가 상대적으로 복잡하다.
마지막으로 떠오른 방법은 연결리스트를 활용하는 방법이다. 위의 두번째 방법을 구현할 때 strjoin으로 앞에서부터 출력 문자열을 완성하고 다시 다음 문자열을 뒤에 붙여넣는 과정에서 결국 이전 출력 문자열을 반복적으로 읽고 복사하는 불필요한 과정이 반복된다. 이러한 동적할당된 배열 활용의 한계점을 처리하기 위해 literal에 대응하는 문자열을 저장한 node, %i %p 등의 태그에 따라 출력한 문자열을 저장한 node를 순서대로 연결리스트로 저장하고 마지막에는 리스트를 순회하며 프린트하는 방법이다. 장점으로는 bonus part확장이 가장 용이할 것이고 두번째 방법에 비해 불필요한 연산이 줄어든다. 단점으로는 세가지 방법중 가장 코드가 상대적으로 복잡하다.
위의 세가지 방법을 놓고 고민하다가 연결 리스트를 연습할 겸 세번째 방법으로 과제를 시작했다.
2.variadic function
printf는 매개변수의 개수가 고정되지 않기 때문에 이를 처리하기 위해 variadic function을 찾아 공부하고 구현했다.
int ft_printf(const char *str, ...)
{
va_list va_ptr;
va_start(va_ptr, str);
ft_chr_to_field((char) va_arg(va_ptr, int))
va_end(va_ptr);
}
위와 같이 고정적인 매개변수 뒤에 가변적으로 입력될 매개변수를 ... 으로 선언하고 이를 va_list로 선언하여 초기화하고 필요할 때마다 va_arg를 통해 뒤의 가변적으로 입력된 매개변수를 하나씩 지정된 형태로 꺼내어 사용할 수 있다. (운영체계에 따라 작동방식의 다소 차이가 있을 수 있지만) va_list에는 입력된 매개변수의 값을 배열처럼 연속된 메모리에 저장하고 va_arg가 호출될 때마다 지정된 type size만큼 해당 값을 반환해준다. 이때 va_ptr의 주소값이 type size씩 이동하는 경우가 있고 클러스터 mac환경에서는 va_ptr의 주소값은 고정되지만 내부에서 포인터를 움직여가며 값을 반환해주는데 이는 운영체제나 개발환경에 따라 발생하는 차이다.
3.problem & solving
연결리스트로 printf를 접근하면서 아래와 같은 구조를 사용하면서 문제를 발견하게 되었다.
typedef struct s_field
{
char *str;
struct s_field *next;
} t_field;
%c에 \0문자가 입력된 경우 위의 출력할 str에 "\0\0"를 저장하고 후에 '\0'을 출력해야 하는데 빈문자열인 "\0"와 구분할 수 없었다. 그렇다고 무조건 다음 값에 접근하여 읽다보면 후자의 경우에 segmentation fault가 발생한다. c에서 null-terminated string을 사용하기 때문에 발생하는 문제였다. 고민끝에 위의 구조체에 출력할 문자열의 길이를 같이 저장하는 방법을 사용하여 %c에 \0문자가 입력된 "\0\0"은 len=1, %s에 ""가 입력된 "\0"는 len=0을 지정해주어 segmentation fault를 방지하며 위의 문제를 처리할 수 있었다.
문제 외적인 42seoul의 과정 상에서의 문제점으로 빠른시간내에 printf를 작성해야하기 때문에 결국 첫번째 방법을 사용하여 printf를 또다시 작성했다. 어떻게 보면 시간을 낭비한 것처럼 보이지만 이때 연습한 연결리스트 덕분에 뒤의 과제들에서 수월하게 리스트와 구조체를 사용할 수 있었다.