[ft_printf](4)서식지정자 출력


이번 포스트는 (ft_printf)서식지정자 출력에 관한 내용입니다.


1️⃣ ft_printf에 사용할 서식지정자

서식지정자간단한 설명자료형
%c문자char
%s문장char*
%d, %i부호있는 정수int
%p주소(16진수)void*
%o부호없는 8진수정수unsigned int
%x부호없는 16진수정수unsigned int
%u부호없는 십진법 정수unsigned int
%f, %g, %부동소수점float, double
%n지금까지 출력한 문자 개수int*
%%%그 자체x
  • 이번 포스트에서는 실수형(g ,f, e)를 제외한 서식지정자의 출력여러진수(정수형)을 문자형으로 변환하는 함수(ft_ulliatoi함수)를 구현해볼 예정입니다.


2️⃣ 운영체제별 매개변수 포인터 동작방식

  • 서식자지정자를 출력하는 코드는 ap(매개변수 포인터)를 사용해야 합니다.
  • 하지만 운영체제별로 ap(매개변수 포인터)가 동작하는 방식이 달랐습니다.

< 매개변수 포인터 사용 코드(샘플) >

#include <stdarg.h>
#include <stdio.h>

void print_temp(va_list ap) //ap를 사용하는 함수
{
    char *te;
    char c;
        
    te = va_arg(ap, char*);
    printf("%s", te);

    c = va_arg(ap, int);
    printf("%c", c);
}

void temp(char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    {
        print_temp(ap);   
        print_temp(ap);   //함수 종료 후 재 호출 
    }
    va_end(ap);
}

int main(void)
{
    temp("temp"," sssss ", 't'," 13 ",'2');
}
/*---윈도우 출력---*/
sssss t sssss t
/*---리눅스 출력---*/
sssss t 13 2
  • 위의 출력 결과와 같이 윈도우에서는 ap(매개변수포인터)를 사용하는 함수를 종료하면 호출시의 위치로 재지정되었습니다.
  • 어떤식으로 작동하는지는 자세히 알 수 없지만 운영체제별로 매개변수포인터가 작동하는 방식이 다름을 알 수 있습니다.
  • 이번 ft_printf과제는 리눅스기반으로 작성할 계획입니다.




3️⃣ set_form함수[ 서식지정자 조건만 추가 ]

  • 본 함수인ft_printf에서 '%'기호를 만나면 set_form함수를 호출하게 됩니다.
  • set_form함수에서 지정서식자를 만나면 form_spec함수를 호출하게 됩니다.

< set_form(임시) >

int set_form(va_list ap, const char **format)
{
    int cnt = 0;
    if (ft_strchr("cspdiuxX%onfge", **format) == TRUE)
        cnt = form_spec(ap, *format);
    (*format)++;
    return (cnt);
}

< form_spec함수 기본틀 >

int form_spec(va_list ap, const char *c)
{
    int cnt = 0;

    if (*c == 'c')
    {/* 코드 생략 */}
    if (*c == 's')
    {/* 코드 생략 */}
    if (*c == 'd' || *c == 'i' || *c == 'u' || *c == 'x' || *c == 'X' || *c == 'o')
    {/* 코드 생략 */}
    if (*c == 'p')
    {/* 코드 생략 */}
    if (*c == 'n')
    {/* 코드 생략 */}
    if (*c == 'f' || *c == 'g' || *c == 'e')
    {/* 코드 생략 */}

    return (cnt);
}




4️⃣ c(문자), s(문자열) , %(%%)출력코드 [ 플래그 옵션을 제외한 임시 코드 ]

< %c(문자) >

if (*c == 'c')
{
    char temp;
    temp = va_arg(ap, int);
    write(1, &temp, 1);
    cnt++;
}

< %s(문자열) >

if (*c == 's')
{
    char *temp =  va_arg(ap, char*);
    while (*temp != '\0')
    {
        write(1, temp, 1);
        temp++;
        cnt++;
    }
}

< %% >

if (*c == '%')
{
	write(1, "%", 1);
	cnt++;
}




5️⃣ 여러진수를 문자로 변환시켜주는 함수(ft_ullitoa_malloc)

  • printf함수다양한 자료형문자형으로 변환해서 출력해 줍니다.
  • 그렇기 때문에 몇가지 자료형은 문자형으로 변환시켜주는 과정이 필요합니다.
  • 이번 과제에서 다룰 서식자는 10진수말고도 8진수, 16진수가 있기 때문에 각각의 진수에 맞춰 변환해주는 함수를 구현할 예정입니다.
  • %d,%i를 제외함면 대부분의 서식지정자가 부호가 없는 정수를 다루기 때문에 unsigned형(음이아닌 정수) 자료형만을 받아 처리하도록 구현했습니다.
  • 또한 구현할 함수의 인자자료형을 정수중에서 큰 자료형인 unsigned long long int자료형으로 정한 이유는 다음과 같습니다.
    1. unsigned long long int자료형의 크기는 현재 윈도우, 리눅스 운영체제 모두 8byte의 크기를 가지고 있습니다.
    2. int자료형으로 할경우 함수 구현에 있어서 int형의 최소값을 처리하는 코드가 추가적으로 필요합니다.
    3. 결정적으로 추후에 플래그옵션으로 사용하게될 ll의 경우 %lld가 될경우 long long int를 뜻하기 때문에 추후의 사용될 이러한 옵션들도 호환이 가능한 함수가 됩니다.

< ft_ullitoa_malloc함수 (unsigned long long int » char) >

char *ft_ullitoa_malloc(unsigned long long int num, char *num_version)
{
    int num_len;
    int cnt;
    int nb;
    char *result;

    num_len = ft_strlen(num_version);
    nb = num;
    cnt = 1;
    while ((nb /= num_len) > 0)
        cnt++;
    if(!(result = (char*)malloc(sizeof(char) * (cnt + 1))))
		return (NULL);

    result[cnt] = '\0';
    while (cnt > 0)
    {
        result[cnt - 1] = num_version[num % num_len];
        cnt--;
        num /= num_len;
    }
    
    return (result);    
}




6️⃣ 정수형( d, i, u, x, X, o ) 출력코드 [ 플래그 옵션을 제외한 임시 코드 ]

< %d, %i, %u, %x, %X, %o >

if (*c == 'd' || *c == 'i' || *c == 'u' || *c == 'x' || *c == 'X' || *c == 'o')
{
	long long int temp;
	char *result;
	temp = va_arg(ap, int);
	if (*c == 'u')
	{
		if(!(result = ft_ullitoa_malloc(temp,DIGITS)))
			return (-1);
	}
	else if (*c == 'i' || *c == 'd')
	{
		if (temp < 0)
		{
			write(1, "-", 1);
			cnt++;
			temp *= -1;
		}
		if(!(result = ft_ullitoa_malloc(temp,DIGITS)))
			return (-1);		
	}
	else if (*c == 'x')
	{
		if(!(result = ft_ullitoa_malloc(temp, LOWHEXA)))
			return (-1);
	}
	else if (*c == 'X')
	{
		if(!(result = ft_ullitoa_malloc(temp, HIGHHEXA)))
			return (-1);
	}
	else if (*c == 'o')
	{
		if(!(result = ft_ullitoa_malloc(temp, OCTAL)))
			return (-1);
	}
	cnt += ft_print_str(result);
		free(result);
}




6️⃣ 주소값( p ) 출력코드 [ 플래그 옵션을 제외한 임시 코드 ]

  • %p변수의 주소를 16진수형으로 출력해주는 서식지정자입니다.
  • 16진수형으로 출력하므로 unsigned형입니다. 하지만 아직 주소의 크기가 얼마인지 알지 못합니다.
  • 적절한 자료형을 찾기위한 임시코드를 만들어서 실험해 봤습니다.

< 윈도우,리눅스에서 주소값 크기 비교 >

int main(void)
{
    char arr[] = "hello";   //임시 변수

    printf("%ld\n", sizeof(&arr));   //주소값의 크기
    printf("%ld\n", sizeof(long int));
    printf("%ld\n", sizeof(long long int));
}
/*---윈도우출력---*/
4    //address_size
4    //long int
8    //long long int
/*---리눅스출력---*/
8    //address_size
8    //long int
8    //long long int
  • 기존에 주소값의 크기4byte로 알고있었지만 리눅스에서는 주소값의 크기8byte임을 알 수 있습니다.
  • 이와 비슷하게 long int의 크기가 동일하게 바뀜을 알 수 있습니다.
  • 결론적으로 포인터의 자료형unsigned long int로 지정하였습니다.

< %p(void*) >

if (*c == 'p')
{
	char *result;
	unsigned long int num;

	num = va_arg(ap, unsigned long int);
	(!(result = ft_ullitoa_malloc(num, LOWHEXA)))
            return ;
	write(1, "0x", 2);
	cnt += 2;
	cnt += ft_print_str(result);
	free(result);
}




7️⃣ %n 서식자

  • %nprintf함수서식지정자중에서 유일하게 매개변수포인터쓰기를 합니다.
  • int*자료형과 매칭되는 서식지정자입니다.

< %n을 사용하는 코드(간단한 예시) >

int main(void)
{
    char arr[] = "kirim";
    int temp;

    printf("%d%d%s%n\n", 4, 2, arr, &temp);
    printf("%d\n", temp);  //%n에 쓰인값을 출력
}
/*---출력---*/
42kirim   // 7글자 (%n이전출력)
7           // %n서식자가 쓴값
  • 지금까지 함수들의 대부분은 그 함수 내에서 출력한 문자개수를 출력하도록 구현했습니다.
  • 하지만 %n매개변수 포인터에 제대로 쓰기를 하기위해서는 출력한 문자에 대한 누적 값이 필요했습니다.
  • 그렇기 때문에 각 함수의 cnt변수를 없애고 본함수ft_printfread_len변수를 계속해서 사용하도록 함수들을 수정해 주었습니다.

변환 전 함수 원형

int set_form(va_list ap, const char **format);
int form_spec(va_list ap, const char *c);

변환 후 함수 원형

void set_form(va_list ap, const char **format, int *len);
void form_spec(va_list ap, const char *c, int *len);


< %n >

if (*c == 'n')
{
	int *n;
	n = va_arg(ap, int*);
	*n = *len;
}




8️⃣ 서식지정자를 처리하는 최종 form_spec함수(플래그옵션, 실수형 제외한 임시코드)

  • f, g, e(실수형)서식자의 출력은 플래그옵션을 고려해야할 부분이 많기 때문에 추후에 구현할 예정입니다.

< form_spec >

void form_spec(va_list ap, const char *c, int *len)
{
	if (*c == 'c')
	{
		char temp;
		temp = va_arg(ap, int);
		write(1, &temp, 1);
		*len++;
	}
	if (*c == 's')
	{
		char *temp;
		temp = va_arg(ap, char*);
		while(*temp != '\0')
		{
			write(1, temp, 1);
			temp++;
			*len++;
		}
	}
	if (*c == 'd' || *c == 'i' || *c == 'u' || *c == 'x' || *c == 'X' || *c == 'o')
	{
		long long int temp;
		char *result;

		temp = va_arg(ap, int);
		if (*c == 'u')
		{
			if(!(result = ft_ullitoa_malloc(temp,DIGITS)))
				return ;
		}
		else if (*c == 'i' || *c == 'd')
		{
			if (temp < 0)
			{
				write(1, "-", 1);
				*len++;
				temp *= -1;
			}
			if(!(result = ft_ullitoa_malloc(temp,DIGITS)))
				return ;		
		}
		else if (*c == 'x')
		{
			if(!(result = ft_ullitoa_malloc(temp, LOWHEXA)))
				return ;
		}
		else if (*c == 'X')
		{
			if(!(result = ft_ullitoa_malloc(temp, HIGHHEXA)))
				return ;
		}
		else if (*c == 'o')
		{
			if(!(result = ft_ullitoa_malloc(temp, OCTAL)))
				return ;
		}
		*len += ft_print_str(result);
		free(result);
	}
	if (*c == 'p')
	{
		char *result;
		unsigned long int num;

		num = va_arg(ap, unsigned long int);
		if(!(result = ft_ullitoa_malloc(num, LOWHEXA)))
                    return ;
		write(1, "0x", 2);
		*len += 2;
		*len += ft_print_str(result);
		free(result);
	}
	if (*c == 'n')
	{
		int *n;
		n = va_arg(ap, int*);
		*n = *len;
	}
	if (*c == '%')
	{
		write(1, "%", 1);
		(*len)++;
	}
}




© 2021.02. by kirim

Powered by kkrim