[42Seoul] ft_printf

jiyseo·2022년 7월 8일
0

42 Seoul

목록 보기
7/9

과제를 시작하기 전

가만보면 printf() 함수는

printf(”%d %d %d”, 3, 4, 5)

처럼 인자가 여러개여도 된다. 이것이 왜 가능한지 알아보기 위해 가변인자라는 것을 먼저 알아보자

printf() 함수의 매개변수는 0 ~ n 개의 값을 가질 수 있으며, format의 형식 지정자 값에 따라 매개변수의 수가 다르다.

만약 printf(”%d %d %d”, 3, 4, 5, 6) 인 경우 형식지정자가 3개 이므로 매개변수는 최소 3개 이상이 와야한다. 형식지정자가 매개변수보다 많을 경우 그 이후는 무시된다

형식지정자 ≤ 매개변수 조건이 맞춰줘야함

  • 가변인자
💡 #include

위의 헤더에 va_list, va_start, va_arg, va_end, va_copy 등이 정의되어 있다.

가변인자는 몇개가 될지 모르기 때문에 ‘...’ 으로 표시한다. 주의해야할 점은 최소 1개 이상의 고정 인수가 있어야 하며, ‘...’은 파라미터 순서 상 마지막에 위치해야한다.

int ft_printf(const char *, ...);

‘...’ 부분에 들어오는 변수명이 없는데, 이를 표현하기 위해 매크로 정의들을 이용한다.

반환값이 int인 이유는 출력된 문자열의 수가 반환되기 때문

  1. va_list

    va_list 라는 타입으로 길이가 변할 수 있는 인수들을 저장하기 위한 가변의 저장공간이다.

    가변인수들에 대한 정보를 홀드하기 위한 타입이다.

  2. void va_start(va_list ap, 첫 번째 매개변수)

    두 번째 인자로 첫 번째 매개변수를 넣어, ap가 이후 들어올 가변인자를 가르키게 한다.

    두개의 인자 va_list의 인스턴스, 그리고 고정인수를 받아 va_list를 초기화함

    첫번째 가변인자 주소를 알려면 고정인수가 필요하기 때문

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

    위와 같이 정의하는데, 이는 ap는 가변인수 함수에 들어오는 고정인수 다음부터 위치되어야하기 때문이다.

  1. type va_arg(va_list ap, type)

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

    ap(가변인자 포인터 변수)가 가르키는 곳에서 type 자료형만큼 데이터를 읽어와 type 자료형으로 반환

    ap 포인터가 위치한 부분의 데이터를 읽어 반환한다

    그 다음 ap 포인터를 타입길이만큼 뒤로 옮기기 때문에 그 다음 값이 이어서 출력될 수 있다.

    ap 주소값은 t 사이즈만큼 증가

    하지만 그 다음 값을 빼고 앞에 덧셈할 때처럼 ap에 대입하지는 않음

    즉 ap 사이즈는 증가한 위치에 고대로 있지만 실제 우리가 출력하는 부분은 기존 부분이 되는 것

    위의 그림처럼 ap 는 104번지에 가있지만 실제 우리가 데이터를 출력하는건 100번지에 있는 값

    그래서 다음에 매크로를 다시 호출했을 때 104번지 값을 호출하고 ap는 type만큼 뒤로 밀림
    이렇게 하는 이유는 타입마다 사이즈가 다른데 이 타입별로 ap 포인터를 뒤로 옮기기 위해서이다.

  2. void va_end(va_list)

    ap 가 가르키는 곳을 NULL로 초기화

  • 서식 지정자 ‘%’ 기호로 시작하며 각 변수의 자료형에 대한 정보를 안내하는 것 ft_printf() 에서는 cspdiuxX% 서식지정자를 구현해야한다
    • %c - 단일 문자 한 개 출력 (va_arg type : int)
    • %s - 문자열 출력 (va_arg type : char *)
    • %p - void * 형식의 포인터 인자를 16진수로 출력 (va_arg type : long long)
    • %d - 10진수 숫자 출력 (va_arg type : int)
    • %i - 10진수 정수 출력 (va_arg type : int)
    • %u - 부호 없는 10진수 숫자 출력 (va_arg type : unsigned int)
    • %x, X - 숫자를 16진수로 출력 (va_arg type : unsigned int)
    • %% - '%'기호 출력

char의 va_arg 타입이 int인 이유

- GCC(C표준)에서는 가변인자로 받은 값이 int보다 작다면 int를 지정해야한다.
- float는 double로 지정해야 한다.

서식지정자에서 d 와 i 의 차이

(d 와 i 는 printf 에서는 기본적으로 같으나, scanf 에서는 역할이 다르다고 한다. d : 10진수 입력받음, i : 10/8/16진수 입력 받음)

Bonus

bonus의 과제는 printf()의 아래 부분을 구현하는 것이다

  • '-', '0', '.' 의 플래그 조합 및 서식 지정자별 최소 폭을 구현
  • '#', '+', ' '(공백) 플래그를 구현

printf() 함수는 형식 태그라고 불리는 것이 추가적으로 들어감 형식태그는 아래와 같다

**%[플래그(flag)][폭(width)][.정밀도][크기(length)]서식 지정자(specifier)**

  • 플래그
      • : 왼쪽 정렬 (기본값은 오른쪽 정렬이다)
      • : 양수일 때는 + 부호, 음수일때는 - 부호 출력
    • (공백) : 음수일때만 - 부호 출력
    • * : 가변인자로 폭을 받아낸다.
    • 0 : 출력하는 폭의 남는 공간을 0으로 채워넣는다.
    • ‘# : 출력하고자 하는 진법에 맞게 부호, 서식지정자 o, x, X와 사용 (8진 : o, 16진 : x, 16진 : X)
    • 숫자 : 지정한 숫자만큼 폭을 지정하여 출력한다. 실수는 .(소수점) , e+까지 폭에 포함된다.

    • 출력할 값이 지정한 폭보다 작으면 자릿수를 맞추기 위해 공백 또는 0을 채워 넣는다.

    • 출력값이 지정된 폭 보다 크다면 폭은 무시된다.

    • width에 음수가 할당되면 -플래그(좌측정렬) + width로 간주한다.

    • 폭은 * 를 이용하여 가변인자로도 받을 수 있다.

      ex) %5d → 5칸 안에 우측정렬하여 출력

  • 정밀도
    • 폭과 구분되도록 앞에 dot(.)을 찍어 입력해야한다.
    • 출력할 값의 정밀도를 위한 자릿수를 설정한다.
    • 형식 문자열에서 정밀도를 나타내지는 않지만 뒤에 인자로 정밀도 값을 준다. 이 때 인자는 형식 태그가 적용되는 데이터 앞에 있어야 한다.
    • 출력값 정수(d, i)일 때 : 설정한 정밀도를 맞추기 위해 자릿수 만큼 0을 추가한다. 출력값의 길이기 더 길다면, 정밀도는 무시된다.
    • 출력할 값이 실수일 때 : 반올림 혹은 소수점 이하에 0을 붙여서, 정밀도 만큼의 소수점 자리를 출력한다. 실수 전체의 자릿수 설정은 width옵션을 사용한다.
    • 정밀도에 음수가 할당되면 무시된다.
  • ft_printf 구현시 고려할 점
      • 플래그와 0 플래그가 같이 있다면 0은 무시된다.
    • 정밀도와 0플래그가 같이 있어도 0은 무시된다.
    • 설정한 폭보다 출력할 내용이 더 길다면 ? 폭은 무시된다.
    • 폭은 *을 사용하여 가변인자로 값을 받아올 수도 있다.
    • 정밀도도 *을 사용하여 가변인자로 값을 받아올 수 있다.
    • 정밀도와 정수를 함께 사용하면 ‘0’을 채워넣어 출력된다.

bit flag

https://velog.io/@wldus24/%EB%B9%84%ED%8A%B8%ED%94%8C%EB%9E%98%EA%B7%B8-bitflag%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

비트플래그 내용 참고

Logic

  1. 우선 여러 정보들을 담을 info 구조체를 만들어 어떤 타입, 플래그, 폭, 정밀도, 글자 수, 첫 인자로 들어오는 format을 저장한다.
typedef struct	s_info {
	char		type;
	char		flag;
	int			width;
	int			precision;
	int			count;
	const char	*fmt;
	va_list		ap;
}	t_info;
  1. 비트플래그를 사용하기 위해 플래그들을 임의의 비트로 define한다

    # define FLAG_MINUS	0x01
    # define FLAG_PLUS	0x02
    # define FLAG_SPACE	0x04
    # define FLAG_ZERO	0x08
    # define FLAG_SHARP	0x10
  2. format을 한글자씩 읽어 해당 글자가 %인 경우 다음이 어떤 특성을 가지는지 parse하도록 parse_format() 함수를 호출하고 %가 아닌 경우 문자를 바로 출력하도록 한다.

  3. % 다음에 어떤 플래그를 사용했는지 확인하고 비트플래그를 이용하여 사용된 플래그를 켠다.

  4. 정밀도나 폭들을 확인하여 width, precision에 저장한다.

  5. 서식지정자에 따라 각각 print_char(), print_nbr(), print_string()를 호출해준다.

    • print_char()에서는 제로 플래그, 공백 플래그, 폭이 큰 경우에 따라 왼쪽 오른쪽을 채워주고 한 글자를 출력한다.
    • print_string()에서도 왼쪽, 오른쪽을 채워준 후 string을 출력한다.
    • print_nbr()에서는 #이 있는경우나 X, x, p가 있는 경우에는 16진수로 출력하도록 sharp 플래그를 켜주고 그게 아닌 경우에는 sharp 플래그를 꺼주고 자릿수를 계산하고 이로 나눠가며 숫자 하나씩 출력하도록 한다.
  6. 6번의 과정에서 count를 증가시켜 몇글자가 출력되는지 확인하고 구조체에 저장해놓아 이를 return 하도록 한다.

profile
코딩뿡뿡이

0개의 댓글