42Seoul - ft_printf

devicii·2022년 3월 1일
0

42

목록 보기
4/8
post-thumbnail

선행지식

1. 가변인자 (Variadic Arguments)

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

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

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

예제

#include <stdio.h>
#include <stdarg.h> // va_list, va_start, va_arg, va_end가 정의된 헤더 파일 int total(int args, ...) // 가변인자의 개수를 받는다. ...로 가변인자를 설정
    {
        int num = 0;
        int i;
        va_list ap; //가변인자의 포인터 리스트 설정
    
        va_start(ap, args); //가변인자 포인터 설정
        
        
        for(i=0; i<args; i++) // 가변인자 갯수만큼 반복
            num += va_arg(ap, int); // int 크기만큼 ap에서 값을 가져온다. ap는 int 크기만큼 순방향으로 이동한다.va_end(ap); /// ap를 null로 초기화한다.return num;
    }int main()
    {
        printf("%d\n", total(1,2,3);return 0;
    }
    ​
    결과 => 6

va_list

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

va_start

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

2. 형식문자열(format)

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

서식지정자 (foramt specifier)

  • ft_printf의 유일한 고정 매개변수 format은 서식문자열, 형식문자열, 포맷 이라는 이름우로 불린다. 이 포맷은 보통 우리가 출력하려고 하는 문자열이며, 이 문자열 내부에는 %문자로 시작하는 서식지정자가 존재한다.

  • 정수, 실수, 문자, 문자열, 포인터 주소 등을 출력하는 역할을 한다. 또한 이 서식지정자에 다양한 플래그, 폭, 정밀도, 길이 등의 다양한 옵션을 조합해서 사용할 수도 있다.

형식태그(format tag)

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

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

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

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

활용함수


void	ft_putnbr_base(unsigned int n, char *base)
{
	int	i;
	int	base_length;
	int	nbr_box[100];

	i = 0;
	base_length = 0;
	if (n < 0)
	{
		n = -n;
		ft_putchar('-');
	}
	while (base[base_length] != 0)
	{
		base_length++; //문자열인 base를 활용해 base의 크기를 구한다.
 	}
	while (n != 0)
	{
		nbr_box[i] = n % base_length;
		n = n / base_length;  
        // 	예를 들어 2015를 n으로 가정한다면 아래와 같은 연산이 이루어질 것이다.
        //	nbr_box[0] = 2015 % 16 = 16 
        //  n = 2015 / 16 = 125
        // 	nbr_box[1] = 125 % 16 = 13 
        // 	n = 125 / 16 = 7   
        // 	nbr_box[2] = 7 % 16 = 7
   		// 	n = 7 / 16 = 0       
		i++;
	}  
    // nbr_box[0] = 16
    // nbr_box[1] = 13
    // nbr_box[2] = 7 
    // 그리고 배열에 위와 같은 값이 담기게 된다.
    while (--i >= 0)
	{
		ft_putchar(base[nbr_box[i]]);
        // 10진수를 16진수로 변환할 때 n이 0이 될때까지 16으로 나누고 동시에 나오는 나머지 값을 역순으로	
        // 16진수 위치에 대입하면 된다. 현재 7 13 16이라는 값을 123456789abcdef의 위치에 대입한다.
        // 그러면 7df가 10진수 2015의 16진수이다.
	}
}

+++ -1을 넣으면 FFFFFFFF 나오는 이유는 들어오는 값이 unsigned int라서 -1을 하게 된다면 unsigned int의 최대값인 4294967295가 나오고 이 값을 16진수로 변환하면 FFFFFFFF가 나오게 된다.

생각해볼 만한 점

1. 왜 char형을 int로 캐스팅 하는가?

바이트 패딩에 관하여

2. 왜 const를 유지해야 하는가? 또한 서식지정자를 판별하는 함수에서 더블 포인터를 쓰는 이유

  • 2.1 str_reader함수에서 ft_str_reader(const char **str, ap_list ap) 를 만들면서 str을 또 더블 포인터로 받아주는 이유는 처음에 ft_printf(const char *str, ....)함수에서 받은 문자열에서 %의 여부를
    두 번째 함수인 ft_str_reader(const char **str, ap_list ap)에서 확인해야한다.
    해당 함수가 실행되면서 str의 값을 제어해야하기 때문에 당연히 포인터의 포인터인 더블 포인터를 활용해 str의 값을 제어할 수 있다.

3. 왜 메모리 주소값은 0x...으로 시작할까 ?

만일 2바이트를 표현한다면 0x0000부터 0xFFFF로 표현하면 되고, 바이트 수가 늘어나도 가장 큰 주소값은 0xFF...FF처럼 나타날 것이기 때문에 일관성이 있다.

이러한 주소를 10진수로 표현하면, 1바이트는 0 ~ 256이고, 2바이트는 0~65535, 3바이트는 16777216이다. 메모리 주소에 일관성이 없어진다. 그렇기 때문에 16진수로 메모리 표기를 하는 것이 관용적으로 굳어지게 된 것이다.

메모리 주소를 16진법으로 표기하는 이유

4. %d 와 % 의 차이점은 ?

둘 사이의 출력에서는 전혀 문제가 없지만 scanf같은 input 형식 지정자에서의 특징이 다르기 때문에 알고 있는 것이 좋다.
%d는 signed 10진수 정수를 입력받는다.

%i는 10진수/8진수/16진수를 입력받는다.

5. %s 지정자에서 NULL이 들어온다면 ?

원래 printf에서의 출력값으로 (null)이라고 나온다.
%s태그를 만났을 때 출력을 write로 "(null)"을 해주어 해결할 수 있는 문제이다.

profile
Life is a long journey. But code Should be short :)

0개의 댓글