C언어

[C언어] printf 함수 구현

_minit 2024. 12. 19. 15:59

`printf()` 함수란?

`printf()` 함수는 C 프로그래밍에서 데이터를 출력하기 위해 가장 널리 사용되는 함수이다. 형식 지정자를 활용하여 다양한 데이터 타입을 출력할 수 있으며, 콘솔 기반 출력의 기본 도구이다.

 

형식 문자열과 형식 지정자

`printf()` 함수는 형식 문자열을 입력받아 처리한다. 형식 문자열을 일반 텍스트와 형식 지정자로 구성되며, 형식 지정자는 `%d`, `%f`, `%s` 등으로 데이터 타입을 정의한다.

 

가변 인자의 처리 방법

`printf()`는 가변 인자를 처리하기 위해 `stdarg.h` 에 정의된 `va_list`, `va_start`, `va_arg`, `va_end`를 사용한다. 이를 통해 함수 호출 시 전달된 인자들을 순차적으로 읽어 형식 지정자에 따라 처리한다.

 

`stdarg.h`의 사용법

가변 인자를 처리하기 위해 반드시 알아야 할 라이브러리이다. `va_list`를 선언하고, `va_start`로 초기화한 뒤 `va_arg`로 각 인자를 읽어보는 방식이다.

 

문자열 처리와 출력 스트림 개념

문자열을 구성하고 이를 출력 스트림에 전달하는 과정을 이해해야 한다. 저수준 출력 함수인 `putchar()`를 활용해 출력 스트림을 관리할 수 있다.

 

함수 구현

Step 1 : 기본 출력 함수 만들기

가장 간단한 형태로 문자열을 출력하는 함수부터 시작해 보겠다.

  • 문자열을 하나씩 읽어 출력하는 방식으로 작동한다.
  • `putchar()`를 활용해 각 문자를 출력한다.
#include <stdio.h>

void my_printf(const char *str) { 
    while(*str) {		// 가리키는 문자가 NULL이 아닐 때까지
        putchar(*str);		// 출력
        str++;			// 다음 문자로 이동
    }
}

int main(void) {
    my_printf("Hello, World!\n");
    return 0;
}

이 함수는 문자열만 출력할 수 있고, 서식 지정(format specifiers, e.g., `%d`, `%s`)을 지원하지 않는다.

 

Step 2 : 포맷 처리 없이 숫자 출력

`printf()`는 숫자도 출력할 수 있다. 이를 위해 숫자를 문자열로 변환하는 기능을 추가한다.

  • 음수 처리 및 재귀적인 숫자 출력 로직을 작성한다.
  • 각 자리 숫자를 `putchar()`를 통해 출력한다.
#include <stdio.h>

void print_number(int num) {
    if(num < 0) {
        putchar('-');
        num = -num;
    }
    if(num / 10) {
        print_number(num / 10);
    }
    putchar('0' + (num % 10));
}

int main(void) {
    print_number(12345);
    putchar('\n');
    print_number(-6789);
    return 0;
}

음수인 경우 `-`을 출력하고 양수로 변환한 후 숫자를 출력한다. 

예를 들어, `12345`를 처리할 때

  • `print_number(1234)` -> `print_number(123)` -> `print_number(12)` -> `print_number(1)`로 재귀 호출된다.

 

Step 3 : 간단한 %d 포맷 처리

숫자와 문자열 출력을 구분하여 처리하는 간단하나 버전을 구현한다.

  • `stdarg.h`의 `va_list`를 사용해 가변 인자를 읽어온다.
  • `%d` 형식 지정자를 만나면 정수를 출력한다.
#include <stdio.h>
#include <stdarg.h>

void print_number(int num) {
    if(num < 0) {
        putchar('-');
        num = -num;
    }
    if(num / 10) {
        print_number(num / 10);
    }
    putchar('0' + (num % 10));
}

void my_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    while(*format) {
        if(*format == '%' && *(format + 1) == 'd') {
            int num = va_arg(args, int); // 가변 인자에서 정수 가져오기
            print_number(num);
            format += 2;
        } else {
            putchar(*format);
            format++;
        }
    }

    va_end(args);
}

int main(void) {
    my_printf("Number : %d\n", 42);
    return 0;
}

format 문자열을 한 문자씩 읽어 처리하고 현재 문자가 `'%'`이고 다음 문자(`*(format + 1)`)가 `'d'`라면 

  • `va_arg(args, int)`를 사용해 가변 인자 리스트에서 정수를 가져온다.
  • `print_number`를 호출해 해당 정수를 출력한다.
  • `format += 2`로 포맷 문자열에서 `%d`를 건너뛴다.

`%d`가 아닌 일반 문자는 그대로 출력하고 `*format`이 NULL이면 종료한다.

 

Step 4 : 다중 포맷 처리

이제 문자열 포맷인 '%s'를 추가한다.

void my_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    while (*format) {
        if (*format == '%' && *(format + 1) == 'd') {
            int num = va_arg(args, int);
            print_number(num);
            format += 2;
        } else if (*format == '%' && *(format + 1) == 's') {
            const char *str = va_arg(args, const char *);
            my_printf(str);
            format += 2;
        } else {
            putchar(*format);
            format++;
        }
    }

    va_end(args);
}

현재 문자가 `'%'`이고 다음 문자가 `'s'`인 경우

  • `va_arg(args, const char *)`로 가변 인자에서 문자열 포인터를 가져온다.
  • `my_printf(str)`를 호출하여 해당 문자열을 출력한다.
    • 문자열을 출력하기 위해 `my_printf` 자신을 재귀 호출한다.
  • `format += 2`로 `%s`를 건너뛴다.

 

오류 처리 및 예외 상황

기본적인 틀을 이렇게 잡고 잘못된 형식 문자열이나 부족한 인자 개수에 대해 오류를 감지하고 적절히 처리해야 한다.

 

728x90

'C언어' 카테고리의 다른 글

[C언어] 개행 문자  (0) 2024.12.13
[C언어] qsort() 함수란?  (0) 2024.11.18
srand() 함수란?  (0) 2024.11.15
[C언어] OOP 공부 (2)  (1) 2024.11.13
티스토리 기본모드 수식 삽입  (2) 2024.11.13