뇌 마음 반반저장소

[42_ft_printf] printf 만들기1 (feat. 자세한 va 함수 설명) 본문

42/ft_printf

[42_ft_printf] printf 만들기1 (feat. 자세한 va 함수 설명)

맹진저 2022. 12. 17. 00:15
728x90

일단 사용 가능한 함수를 확인해보면 

malloc, free : 말록과 프리를을 사용해서 글을 쓸 공간을 할당하고 해제해 주고, 

write : 이 함수를 사용해서 하나씩 출력한다?

va_start, va_arg, va_copy, va_end : 가변 인자로 구성된 함수를 작성한다..

 

자 그러면 가변 인자는 무엇일까?

 가변 인자는 영어로 variable argument이다. variable은 변할 수 있는, 수식에 따라서 변하는 값을 의미하고 argument은 말 그대로 인수이다. 

*흥미로운 점 : 인자(parameter)와 인수(argument)는 다르지 말입니다?

int	ft_strlen(char *str) //인자 : 함수 원형에 들어가는 변수!
{
	int	i; //인수 : 함수 안에서 정의되고 사용되는 변수!
    
    i = 0;
    ...
}

printf처럼 여러 가지 타입의 정보를 받아서 차례로 처리해 출력해주는 함수 속에 va 함수들이 들어있다.

 

 

이 함수들의 헤더는 <stdarg.h> 이다. 먼저 이 함수들을 어떻게 사용하는지 알아보자. 일단 이 함수들은 스택에 저장된다는 것을 잊지 말 것! 일단 ft_printf의 구조를 알고 넘어가면 아래에 더욱 쉽게 적용할 수 있다.

printf의 구조

1. va_list

인수들의 리스트를 지정해 준다. 그냥 간단하게 이름만 정해주면 된다. 이 함수는 원래 구조체로 헤더에 심어져 있다! 아래의 구조체에 선언된 것처럼 char으로 1바이트씩 증감한다는 것을 의미한다.

//va_list의 본체!
typedef char	*va_list;

//아래처럼 사용한다.
int	ft_printf(const char *str, ...)
{
	va_list	prtlist;
	//구조체 선언과 리스트 이름 등록!
    ...
}

2. va_start

여기 다음부터 라고 초기화시켜준다. 그 부분을 포인터로 가리킨다.

두 개의 인자가 들어가는데 하나는 시작되는 리스트의 이름(위치), 그리고 함수에 가장 처음으로 쓰인 인자의 이름이 들어간다. 한마디로 말하면 "우리 이제 시작할 건데~ 이 리스트에서 첫 번째 인자 먼저 넣어주고 그다음부터 시작한다~"라는 뜻이다.

#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )

//*중요* 여기서 INTSIZEOF(v)는 4바이트 즉 4의 배수로 사이즈를 맞춰서 저장하겠다는 뜻이다!

void	va_start(va_list arg_ptr, prev_param); // (ANSI C89 and later)

//arg_ptr : 인수 목록의 포인터. 지금은 첫번째 자리를 가리키고 있을 것이다.
//prev_param : 다른 인수들 앞에 있어야 하는! 함수의 첫번째 매개변수.

	//아래처럼 사용한다.
	va_start(prtlist, str);
    	//리스트의 이름과, 첫번째 인자

va_start의 내장 구조

우리는 str이 const char *str로 들어왔다. 그래서 1바이트씩을 차지하지만, 내장 함수의 define을 보면 실제로는 4바이트씩 공간을 잡아주겠다고 선언한 것과 같다. 그래서 char도 4바이트씩 할당이 된다. 이 함수는 이후 va_arg가 나오지 않으면 의미가 없다.

va_start의 실제 시작 할당 셋팅

 

3. va_copy

이 함수는 strcpy함수처럼 src를 dest에 붙이는 함수이다!

void	va_copy(va_list dest, va_list src); // (ISO C99 and later)

4. va_arg

이 친구가 이제 인수들을 때려 넣어주는 친구인데, 하나하나씩 넣기 위해 루프를 사용한다. va 함수의 반환 값은 바로 va_arg의 인수들이다. 

#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

va_arg의 내장 구조

예시) 만약에 double사이즈를 추가하려면 먼저 8바이트만큼 옮겨서 자리를 잡아주고 다시 돌아와서 값을 입력한다.

 

특이하게도 va_arg함수는 먼저 다음 포인터를 계산하고 다시 돌아와서 값을 입력하는 형태이다. 여기서 중요한 것이 지정된 타입을 절대적으로 알아야 하고, 들어온 가변 인자의 정확한 수를 알아야 한다. printf는 % d나 % s처럼 포맷으로 넘어오는 타입을 먼저 파악하고 그 인자의 자릿수를 착착 계산해 내보내기 때문이다. 바이트 수를 예상하고 임의로 공간을 정하는 것보다, 이렇게 하면 낭비하는 바이트 없이 모든 인수가 착착착 담기게 되겠군!

type	va_arg(va_list arg_ptr, type);

//type : 인수의 유형

	//아래처럼 사용한다.
    int	args; //타입은 원하는 저장 형태로!
    int i;
    
    i = 0;
    args = 0;
    while (i < str)
    {
    	args += va_arg(prtlist, int); //계속 루프를 돌면서 args에 va_arg를 누적시킨다.
        i++;
    }

전체 문장 str에서 %가 나오면 변경시켜주는 if문으로 확장한다!

 

5. va_end

이 함수는 마지막에 리스트의 주소 값으로 가서 아래의 내장 함수 정의처럼 모두 0으로 초기화시켜준다!

#define va_end(ap)      ( ap = (va_list)0 )

void va_end(va_list arg_ptr);

 

자 그러면 이 함수들을 참고해서 printf의 본체를 만들어 보자!

 

 

도움을 주고 싶으신 내용이나

틀린 내용이 있다면 댓글로 남겨주시고,

도움이 되었다면 공감 한 번씩 눌러주세요👍❤️

728x90

'42 > ft_printf' 카테고리의 다른 글

[42_ft_printf] printf 만들기2 (정적 변수와 자료형)  (1) 2022.12.18
[42_ft_printf] 시작하며  (0) 2022.12.16
Comments