`printf`는 다양한 프로그래밍 언어에서 형식화된 출력을 생성하는 데 사용되는 함수이다. 초기 프로그래밍 언어인 포트란에서 시작하여 BCPL, ALGOL 68, C 언어에 이르기까지 발전해왔으며, 셸 명령어와 C++의 발전에도 영향을 미쳤다. `printf`는 형식 지정자를 사용하여 출력 형식을 제어하며, 플래그, 폭, 정밀도, 길이, 타입 등의 세부 설정을 지원한다. 그러나 형식 문자열 공격과 같은 보안 취약점을 가지고 있으며, C++20 이후 `std::format`과 C++23의 `std::print`와 같은 안전한 대안이 등장했다. `printf`는 C, C++, 자바, 파이썬, 셸 스크립트 등 다양한 언어에서 지원되며, 한국에서는 C/C++ 기반 시스템 프로그래밍 및 웹 개발에서 널리 사용된다.
더 읽어볼만한 페이지
Stdio.h - Scanf `scanf`는 C 언어에서 표준 입력을 통해 데이터를 입력받는 함수로, `stdio.h`에 포함되어 있으며 다양한 형식 지정자를 통해 여러 형태의 데이터를 입력받을 수 있지만, 보안 취약점에 유의해야 하고, 관련 함수로 `fscanf`와 `sscanf`가 있다.
유닉스 소프트웨어 - GNU 코어 유틸리티 GNU 코어 유틸리티는 유닉스 계열 운영체제에서 파일, 셸, 텍스트 조작을 위한 기본적인 명령어 모음으로, GNU 파일 유틸리티에서 시작하여 3개의 패키지가 통합되어 발전했으며 셸 스크립트 및 시스템 관리에 필수적인 도구를 제공한다.
유닉스 소프트웨어 - 한/글 한/글은 1980년대 후반부터 개발된 대한민국의 대표적인 워드프로세서로, 다양한 운영체제 지원, 정부 전자 문서 시스템에서의 역할, 꾸준한 기능 발전과 사용자 편의성 및 국제 표준을 고려한 업데이트를 통해 발전해왔다.
Printf
기본 정보
이 문서는 컴퓨터 프로그래밍에 관한 것입니다. 다른 의미에 대해서는 프린트 문서를 참조하십시오.
C
유형
함수
정의
stdio.h
첫 번째 나타남
유닉스 버전 5
POSIX
표준
ISO/IEC 9899:1990 / ANSI X3.159-1989: 표준 C ISO/IEC 9899:1999: C99 ISO/IEC 9899:2011: C11
`printf` 함수는 C 언어의 표준 입출력 라이브러리에 포함된 중요한 함수로, 지정된 형식에 맞춰 문자열을 출력하는 기능을 수행한다. 이 함수의 기원은 C 언어보다 앞선 초기 프로그래밍 언어들에서 찾아볼 수 있다.
1950년대의 포트란에서는 `FORMAT` 문을 사용하여 출력 형식을 지정하는 방식을 사용했다.[1] 이후 1960년대에는 BCPL의 `writef` 함수[3]와 ALGOL 68의 `printf` 함수가 등장하여 형식 지정 출력 기능을 제공했지만, 각각 고유한 문법적 특징을 가졌다.
1970년대에 이르러 C 언어가 개발되면서 `printf` 함수가 Version 4 Unix에 포함되었고[7], 이후 C 언어의 표준 라이브러리 함수로 자리 잡았다. 1990년대에는 유닉스 셸 환경에서도 `printf` 명령어가 도입되어 널리 사용되기 시작했다.[8]
그러나 C 언어의 `printf` 함수는 형식 문자열과 전달되는 인자의 타입이 일치하지 않을 경우 버퍼 오버플로와 같은 메모리 손상 취약점을 유발할 수 있는 타입 안전성 문제가 있었다.[10] 이러한 문제를 해결하기 위해 C++에서는 컴파일 시점에 형식 문자열을 검사하는 기능(GCC의 `-Wformat` 옵션 등[9])이 추가되었고, C++20 표준에서는 타입 안전성을 보장하는 `std::format` 라이브러리[12][14]와 C++23 표준에서는 `std::print` 함수[16]가 도입되어 더욱 안전하고 현대적인 형식 지정 출력 방식을 제공하게 되었다.
2. 1. 포트란 (1950년대)
초창기 프로그래밍 언어인 포트란은 형식 지정을 다른 계산과는 다른 문법을 가진 특수한 구문을 사용했다.[1] 이 예시에서 형식은 601번째 줄에 지정되어 있으며, `PRINT` 명령은 이 줄 번호를 참조하여 연결된 라인 프린터에 인쇄한다. `FORMAT` 구문은 자기 테이프 데이터 저장소에 데이터를 기록하는 `WRITE OUTPUT TAPE` 명령에도 사용되었다.
이러한 방식은 Fortran과 달리 일반적인 함수 호출과 데이터 유형을 사용함으로써 언어와 컴파일러를 단순화하고, 입출력 기능을 언어 자체로 구현할 수 있게 하는 장점이 있었다.
2. 3. C 언어 (1970년대)
1973년, printf는 C 루틴으로 Version 4 Unix의 일부로 포함되었다.[7]
2. 4. 셸 명령어 (1990년대)
1990년, printf 셸 명령어가 4.3BSD-Reno의 일부로 포함되었다. 이는 표준 라이브러리 함수를 모델로 했다.[8] 1991년에는 printf 명령어가 GNU shellutils(현재 GNU Core Utilities)에 번들로 제공되었다.
2. 5. 안전성 문제와 C++의 발전 (2000년대 ~ 현재)
`printf` 함수의 타입 안전성 부족으로 인한 여러 문제점을 해결하기 위해 C++ 컴파일러가 `printf` 함수 호출을 인식하고 검사하려는 시도가 이루어졌다.
GCC의 `-Wformat` 옵션은 컴파일 시점에 `printf` 호출의 형식 문자열과 인자들의 타입을 검사하여 잘못된 사용을 감지할 수 있게 한다. 컴파일러 설정에 따라 경고를 표시하거나 오류로 처리하여 컴파일을 중단시킬 수도 있다.[9] 이 옵션을 사용하면 컴파일러가 `printf` 형식 지정자를 이해하게 되므로, 사실상 형식 지정자를 C++ 구문의 일부로 확장하는 효과를 가진다.
하지만 `printf()`의 근본적인 형식 안정성 부족 문제[10] 때문에 형식 지정 방식 자체를 개선하려는 노력이 이어졌고[11], C++20 표준부터는 타입 안전성을 보장하는 새로운 형식 지정 기능이 언어 자체에 포함되었다.[12]
C++20에 도입된 `std::format`은 Victor Zverovich가 개발한 `libfmt` 라이브러리[13]의 API와 구문을 기반으로 하며, 사실상 `libfmt`를 표준 라이브러리로 통합한 것이다.[14] Zverovich는 이 새로운 형식 지정 기능의 표준 제안서 초안 작성에도 참여했다.[15] 결과적으로 `libfmt`는 C++20 형식 사양의 구현체 역할을 한다.
C++23 표준에서는 형식 지정 기능과 콘솔 출력을 결합하여, 기존 `printf()` 함수를 대체할 수 있는 `std::print` 함수를 새롭게 제공한다.[16]
이제 형식 지정이 언어 구문의 일부가 되었기 때문에, C++ 컴파일러는 `-Wformat` 옵션과 같은 별도의 설정 없이도 기본적으로 형식 지정자와 인자 타입의 불일치를 감지하고 오류로 처리할 수 있다.
`libfmt` 및 `std::format`에서 사용하는 형식 지정 방식은 그 자체로 확장 가능한 "미니 언어"(특정 도메인 언어)로 설계되었다.[17]
결론적으로, C++ 언어는 형식 지정을 위해 별도의 도메인 특정 미니 언어를 구문 내에 통합함으로써, 1950년대 FORTRAN의 초기 `PRINT` 구현 방식과 유사한 형태로 회귀하는 듯한 역사적 순환을 보여준다.
3. 서식 지정자
`printf` 함수는 서식 지정자(format specifier)를 사용하여 출력될 값의 형식을 지정한다. 서식 지정자는 형식 문자열(`format`) 내에서 사용되며, % 기호로 시작하여 뒤따르는 인수의 변환 방법을 정의한다.
예를 들어, 다음 코드는 문자열 "Your age is "를 출력한 뒤, 변수 age의 값을 10진수 정수(%d) 형식으로 출력한다.
```c
printf("Your age is %d", age);
```
서식화 문자열은 일반적인 멀티바이트 문자이거나 % 문자로 시작하는 변환 지정(conversion specification)으로 이루어진다. 멀티바이트 문자가 포함되어 있고, 문자 코드가 시프트 시퀀스에 의존하는 경우, 서식화 문자열은 초기 시프트 상태에서 시작하고 끝나야 한다. 서식 지정을 수행하는 %로 시작하는 각 부분을 서식 지정자(format specifier)라고 한다.
서식 지정자의 일반적인 구문은 다음과 같으며, 대괄호(`[ ]`) 안의 요소는 생략 가능하다.
%[인수 순서][플래그][최소 필드 폭][.정밀도][길이 수식어]변환 지정자
각 부분(플래그, 폭, 정밀도, 길이, 변환 지정자)은 출력 형식을 세부적으로 제어하는 역할을 하며, 이에 대한 자세한 내용은 하위 섹션에서 설명한다.
3. 1. 기본 구문
C 언어 표준 라이브러리 stdio.h 헤더 파일에 선언된 `printf` 함수의 원형은 일반적으로 다음과 같다.
#include
int printf(const char * restrict format, ...);
여기서 첫 번째 인수 `format`은 문자열 형태로, 뒤이어 오는 가변 인자(`...`)들이 어떤 형식으로 변환되어 출력될지를 지정하는 서식화 문자열이다. 서식화 문자열은 일반적인 멀티바이트 문자이거나 % 문자로 시작하는 변환 지정(conversion specification)으로 이루어진다. 멀티바이트 문자가 포함되어 있고, 문자 코드가 시프트 시퀀스에 의존하는 경우, 서식화 문자열은 초기 시프트 상태에서 시작하고 끝나야 한다. 서식 지정을 수행하는 %로 시작하는 각 부분을 서식 지정자(format specifier)라고 한다.
변환 지정, 즉 서식 지정자의 구문은 다음과 같은 형식을 가진다. 대괄호(`[ ]`) 안의 요소는 생략할 수 있다.
`%[인수 순서][플래그][최소 필드 폭][.정밀도][길이 수식어]변환 지정자`
예를 들어, 다음 C 코드는 `printf` 함수를 사용하여 여러 종류의 데이터를 지정된 형식으로 출력하는 방법을 보여준다.
문자열 test, 정수 20, 16진수 0xf747, 소숫점 3.14, 자른 문자열 toa
각 변환 지정(`%s`, `%d`, `%#x`, `%3.2f`, `%.*s`)이 어떻게 해당 인자("test", 20, 0xf747, 3.1415f, 3, "toast")를 형식화하여 출력하는지 보여준다.
3. 2. 플래그
(마이너스)
출력을 왼쪽으로 정렬한다. (기본값은 오른쪽 정렬)
+ (플러스)
부호가 있는 숫자 형식으로 변환할 때, 양수일 경우 앞에 + 기호를 붙인다. (예: 양수는 +, 음수는 -) (기본값은 양수 앞에 아무 기호도 붙이지 않음)
(공백)
부호가 있는 숫자 형식으로 변환할 때, 양수일 경우 앞에 공백을 붙인다. (예: 양수는 , 음수는 -) 이 플래그는 + 플래그가 사용되면 무시된다. (기본값은 양수 앞에 아무것도 붙이지 않음)
0 (영)
폭(width) 옵션이 지정되었을 때, 숫자 앞에 공백 대신 0을 채워 넣는다. 예를 들어, printf("%4X", 3)은 " 3"을 출력하지만, printf("%04X", 3)은 "0003"을 출력한다. 정수 및 부동소수점 형식에 적용된다. 일부 구현에서는 왼쪽 정렬(-) 플래그가 있으면 이 플래그를 무시한다.
\' (아포스트로피)
10진수 변환(정수 i, d, u 또는 부동소수점 f, g)의 정수 부분에 천 단위 구분 기호(예: 쉼표 ,)를 적용한다. 로캘 설정에 따라 구분 기호가 달라질 수 있다.
# (해시)
대체 형식을 사용한다. * g 및 G 형식: 후행 0을 제거하지 않는다. * f, F, e, E, g, G 형식: 소수점 이하 자릿수가 없더라도 항상 소수점을 출력한다. * o, x, X 형식: 0이 아닌 숫자 앞에 각각 0, 0x, 0X 접두사를 붙인다.
3. 3. 폭
폭 필드는 printf 함수에서 출력될 문자열의 최소 너비를 지정하는 데 사용된다. 만약 출력될 값의 실제 길이가 지정된 폭보다 짧다면, 남는 공간은 값의 왼쪽에 공백으로 채워진다. 예를 들어, `printf("%3d", 12)`는 폭을 3으로 지정했기 때문에, 숫자 12 앞에 공백 하나를 추가하여 ` 12` 와 같이 총 3칸에 맞춰 출력한다.
하지만 출력될 값의 길이가 지정된 폭보다 길 경우에는, 폭 지정은 무시되고 값 전체가 그대로 출력된다. 값은 절대로 잘리지 않는다. 예를 들어, `printf("%3d", 1234)`는 폭을 3으로 지정했지만 실제 값 1234는 4자리이므로, `1234` 전체가 출력된다.
폭 필드를 생략하면, 출력되는 너비는 해당 값을 표현하는 데 필요한 최소한의 칸 수가 된다.
폭 필드 자리에 `*` 문자를 사용하면, 실제 폭 값은 함수 호출 시 전달되는 인수 목록에서 읽어오게 된다.[18] 예를 들어, `printf("%*d", 3, 10)` 코드는 첫 번째 인자인 `*`에 해당하는 폭 값으로 두 번째 인수인 3을 사용하고, `%d`에 해당하는 값으로 세 번째 인수인 10을 사용한다. 따라서 폭이 3으로 지정되어 ` 10` 와 같이 출력된다.
폭 필드는 여러 값을 표(테이블) 형태로 정렬하여 출력할 때 유용하게 사용될 수 있다. 그러나, 값 중 하나라도 지정된 폭에 맞지 않으면 열이 정렬되지 않는다. 예를 들어, 아래 예시의 마지막 줄 값(1234)은 폭이 3인 첫 번째 열에 맞지 않으므로 열이 정렬되지 않는다.
문자열 test, 정수 20, 16진수 0xf747, 소숫점 3.14, 자른 문자열 toa
정밀도 필드는 일반적으로 특정 서식 지정자에 따라 출력의 최대 한계를 지정한다. 부동소수점 숫자 형식의 경우, 소수점 오른쪽에 출력할 자릿수를 지정하며, 지정된 자릿수까지 출력하고 그 다음 자리에서 반올림한다. 문자열 형식의 경우, 출력할 문자의 최대 개수를 제한하며, 이 개수를 초과하는 문자열은 잘린다.
정밀도 필드는 생략하거나, 숫자 정수 값으로 지정하거나, 별표(*)를 사용하여 동적으로 지정할 수 있다. 별표를 사용하면 정밀도 값은 인수를 통해 전달된다. 예를 들어, printf("%.*s", 3, "abcdef") 코드는 abc를 출력한다.
3. 5. 길이
길이 필드는 생략하거나 다음과 같은 값을 가질 수 있다.
수식자
의미
도입 버전
hh
인수는 char형 (signed char 또는 unsigned char로 해석 후 int로 승격됨)
C99 이후
h
인수는 short형 (signed short 또는 unsigned short로 해석 후 int 또는 unsigned int로 승격됨)
모든 버전
|정수형의 경우 인수는 long형 또는 unsigned long형. 부동소수점형의 경우 인수는 double형 (가변 인자에서는 float가 double로 승격되므로 사실상 불필요[19]). 문자/문자열의 경우 인수는 wint_t형 또는 wchar_t*형.
wint_t 및 wchar_t에 대해서는 C95 이후, double에 대해서는 C99 이후 (호환성 목적)
|인수는 long long형 또는 unsigned long long형
C99 이후
j
인수는 intmax_t형 또는 uintmax_t형
C99 이후
z
인수는 size_t형 또는 대응하는 부호 있는 정수형
C99 이후
t
인수는 ptrdiff_t형 또는 대응하는 부호 없는 정수형
C99 이후
L
인수는 long double형
모든 버전
플랫폼별 길이 옵션은 ISO C99 확장이 널리 사용되기 전에 존재했으며, 다음을 포함한다.
문자
설명
일반적으로 발견되는 플랫폼
I
부호 있는 정수형의 경우, printf는 ptrdiff_t 크기의 정수 인수를 예상한다. 부호 없는 정수형의 경우, printf는 size_t 크기의 정수 인수를 예상한다.
ISO C99는 플랫폼 독립적인 printf 코딩을 위한 여러 매크로를 포함하는 inttypes.h 헤더 파일을 포함한다. 예를 들어, printf("%" PRId64 "\n", t);는 64비트 부호 있는 정수(int64_t 타입의 변수 t)에 대한 10진수 형식을 지정한다. 매크로가 문자열 리터럴로 평가되고 컴파일러가 인접한 문자열 리터럴을 연결하므로, 표현식 "%" PRId64 "\n"는 단일 문자열로 컴파일된다.
주요 매크로는 다음과 같다.
매크로
설명 (일반적인 값)
PRId32
I32d (Win32/Win64) 또는 d
PRId64
I64d (Win32/Win64), lld (32비트 플랫폼) 또는 ld (64비트 플랫폼)
PRIi32
I32i (Win32/Win64) 또는 i
PRIi64
I64i (Win32/Win64), lli (32비트 플랫폼) 또는 li (64비트 플랫폼)
PRIu32
I32u (Win32/Win64) 또는 u
PRIu64
I64u (Win32/Win64), llu (32비트 플랫폼) 또는 lu (64비트 플랫폼)
PRIx32
I32x (Win32/Win64) 또는 x
PRIx64
I64x (Win32/Win64), llx (32비트 플랫폼) 또는 lx (64비트 플랫폼)
C언어의 가변 길이 인수에는 "기본 인수 확장"(default argument promotion)이라고 불리는 암묵적인 자료형 변환이 적용된다[29][30]。정수형의 경우 범용 정수 확장과 같으며, 순위(rank)가 int보다 낮은 char, signed char, unsigned char 및 short의 값은 int로 변환된다. unsigned short에 대해서는, 해당 타입의 값을 모두 int로 표현할 수 있는 환경의 경우에는 int로, 그렇지 않은 경우에는 unsigned int로 변환된다. float의 값은 double로 변환된다. 따라서, h, hh 및 double에 대한 l은 printf에서는 기술적으로 생략 가능하다 (단, 변환 지정자 x, X 또는 o를 사용하여 음의 정수 값을 16진수 또는 8진수로 변환하는 경우, h나 hh의 유무에 따라 결과가 변화할 수 있다). 원래 double에 대한 l은 필요하지 않았지만, 많은 프로그래머가 실수를 계속 범했기 때문에, C99 표준에서 호환성을 위해 추가되었다[31]。단, scanf에서는 인수로 포인터가 전달되므로, 길이 수식자의 지정을 통한 구별이 반드시 필요하다.
또한, C99에서 추가된 int32_t나 int64_t 등의 고정 너비 정수형(fixed-width integer type) 인수에 대해, 위 표의 길이 수식자를 직접 사용했을 경우의 동작은 정의되지 않는다. 특정 구현에서 기대대로 동작하더라도, 데이터 모델(LP64, LLP64 등)에 따라 이식 시 비호환성이 발생할 수 있다 (예를 들어 LP64 환경에서는 long은 64비트이며, 만약 int64_t형 인수에 대해 %ld 서식을 사용하더라도 대부분의 구현에서는 기대대로 동작할 가능성이 높지만, LLP64 환경에서는 long은 32비트이며, int64_t형 인수에 대해 %ld 서식을 사용하면 정의되지 않은 동작을 일으킨다. 또한, long long은 적어도 64비트 이상의 값을 표현할 수 있음이 보장되어 있지만, int64_t와 같은 타입이라는 보장은 없으며, int64_t형 인수에 대해 %lld 서식을 사용했을 경우의 동작은 역시 정의되지 않는다. 마찬가지로, int와 int32_t가 같은 타입이라는 보장도 없다). 이러한 고정 너비 정수형을 인수로 사용할 때는, 명시적인 타입 캐스트를 하거나, inttypes.h 헤더에 정의된 PRI로 시작하는 매크로(예: PRId64)를 이용해야 한다[32][33]。
3. 6. 타입
Type 필드는 다음과 같다.
문자
설명
%
문자 % 자체를 출력한다 (이 유형은 플래그, 너비, 정밀도, 길이 필드를 허용하지 않는다).
d, i
부호 있는 정수로서의 int. %d와 %i는 출력 시 동일하지만, 입력 시 scanf와 함께 사용될 때는 다르다 (%i를 사용하면 숫자가 0x로 시작하면 16진수로, 0으로 시작하면 8진수로 해석된다).
u
10진수 unsigned int를 출력한다.
f, F
일반적인 (고정 소수점) 표기법으로 double. f와 F는 무한대 또는 NaN에 대한 문자열 출력 방식만 다르다 (f의 경우 inf, infinity, nan; F의 경우 INF, INFINITY, NAN).
e, E
표준 형식으로 double 값 (d.ddde±dd). E 변환은 지수를 나타내기 위해 문자 E를 사용한다 (e 대신). 지수는 항상 두 자리 이상을 포함하며, 값이 0이면 지수는 00이다. Windows에서는 지수가 기본적으로 세 자리 (예: 1.5e002)를 포함하지만, Microsoft 고유의 _set_output_format 함수로 변경할 수 있다.
g, G
일반 또는 지수 표기법으로 double을 출력하며, 크기에 따라 더 적절한 형식을 사용한다. g는 소문자를 사용하고, G는 대문자를 사용한다. 이 유형은 고정 소수점 표기법과 약간의 차이가 있는데, 소수점 오른쪽에 있는 의미 없는 0은 포함되지 않으며, 정수에는 소수점이 포함되지 않는다.
x, X
16진수로 unsigned int를 출력한다. x는 소문자를 사용하고, X는 대문자를 사용한다.
o
8진수로 unsigned int를 출력한다.
s
널 종료 문자열.
c
char (문자).
p
구현 정의 형식으로 void* (void에 대한 포인터).
a, A
0x 또는 0X로 시작하는 16진수 표기법으로 double. a는 소문자를 사용하고, A는 대문자를 사용한다.[20][21] (C++11 iostreams에는 동일하게 작동하는 hexfloat가 있다).
n
아무것도 출력하지 않지만, 지금까지 출력된 문자 수를 정수 포인터 매개변수에 쓴다. Java에서는 줄 바꿈을 출력한다.[22]
3. 7. POSIX 확장 (인수 순서 지정)
서식 문자열에서 `%` 대신 `%''n''$` 형식을 사용하여, 이어지는 가변 인자 중 몇 번째 인자를 사용할지 번호 ''n''으로 지정할 수 있다. 예를 들어 `%2$d`는 두 번째 가변 인자를 십진 정수로 출력하라는 의미이다.
이 형식을 사용하면 형식 문자열 내에서 특정 인자를 원하는 순서대로 사용하거나, 같은 인자를 여러 번 참조할 수 있다. 값을 여러 번 전달할 필요가 없다는 장점이 있다. 다만, 인수 순서를 지정하는 지정자가 하나라도 사용되면, 이후의 모든 형식 지정자도 `%''n''$` 형식을 사용하여 순서를 명시해야 한다.
예를 들어, 다음 코드는 첫 번째 인자(16)와 두 번째 인자(17)의 순서를 바꾸고, 두 번째 인자를 두 번 사용하여 출력한다.
printf("%2$d %2$#x; %1$d %1$#x", 16, 17);
출력 결과는 다음과 같다.
17 0x11; 16 0x10
이 기능은 특히 한국어와 같이 어순이 다른 자연어로 메시지를 번역(지역화)할 때 매우 유용하다. 프로그램 코드에서 인자를 전달하는 순서를 변경하지 않고, 서식 문자열만 해당 언어의 어순에 맞게 수정하면 되기 때문이다.
예를 들어, 영어 메시지와 이를 한국어로 번역한 메시지를 출력하는 경우를 보자.
/* 영어 원문 형식 */
const char* fmt_en = "Invalid command %1$s at line %2$d.\n";
/* 한국어 번역 형식 (어순 변경) */
const char* fmt_ko = "%2$d행의 명령 '%1$s'는 잘못되었습니다.\n";
const char* cmd = "hoge";
const int lineNumber = 20;
printf(fmt_en, cmd, lineNumber);
printf(fmt_ko, cmd, lineNumber);
위 코드는 다음과 같이 출력된다.
Invalid command hoge at line 20.
20행의 명령 'hoge'는 잘못되었습니다.
이처럼 코드 변경 없이 서식 문자열만 수정하여 자연스러운 번역 결과를 얻을 수 있다.
마이크로소프트 윈도우 환경에서는 `printf_p`라는 별도의 함수를 통해 이 기능을 지원한다.
4. 변형 함수
`printf` 함수에는 다양한 변형 함수가 존재하며, 각각 특정 목적에 맞게 수정된 기능을 제공한다.
`fprintf`: 표준 출력(stdout) 대신 지정된 FILE 스트림 객체에 형식화된 출력을 보낸다. 파일 처리 시 유용하게 사용된다.
#include
int fprintf(FILE * restrict fp, const char * restrict format, ...);
`sprintf`: 표준 출력 대신 문자열 버퍼(`char` 배열)에 형식화된 출력을 쓴다. 이 함수는 지정된 버퍼의 크기를 확인하지 않기 때문에, 출력 내용이 버퍼 크기를 초과하면 버퍼 오버플로 취약점이 발생할 수 있다. `sprintf`는 대상 문자열 버퍼(`str`)에 기존 내용을 지우고 새로운 내용을 쓴다.
#include
int sprintf(char * restrict str, const char * restrict format, ...);
예시:
#include
int main(void)
{
char text[11];
sprintf(text, "ABCDEFGHIJ");
printf("%s", text); // 출력: ABCDEFGHIJ
return 0;
}
`snprintf`: `sprintf`와 유사하게 문자열 버퍼에 출력하지만, 추가 인수로 버퍼에 쓸 최대 문자 수(`size`)를 지정받는다. 이 크기를 넘어서는 데이터는 버퍼에 쓰이지 않으므로 버퍼 오버플로를 방지할 수 있어 `sprintf`보다 안전한 대안으로 권장된다. C99 표준부터 포함되었다.
#include
int snprintf(char * restrict str, size_t size, const char * restrict format, ...);
`wprintf`, `fwprintf`, `swprintf`: 각각 `printf`, `fprintf`, `snprintf`에 대응하는 함수로, 와이드 문자(`wchar_t`)를 사용하여 유니코드와 같은 다국어 문자를 처리한다. 이 함수들은 C95 표준에서 도입되었다. `sprintf`에 직접 대응하는 와이드 문자 버전 함수(출력 버퍼 크기를 받지 않는 함수)는 표준에는 없다.[34][35]
#include /* 또는 wchar.h */
int wprintf(const wchar_t * restrict format, ...);
int fwprintf(FILE * restrict fp, const wchar_t * restrict format, ...);
int swprintf(wchar_t * restrict str, size_t size, const wchar_t * restrict format, ...);
`vprintf`, `vfprintf`, `vsprintf`, `vsnprintf`, `vwprintf`, `vfwprintf`, `vswprintf`: 이 함수들은 이름 앞에 'v'가 붙으며, 가변 인자 목록(...) 대신 `va_list` 타입의 인수를 받는다. 이는 다른 함수 내에서 가변 인자를 받아 `printf` 계열 함수에 그대로 전달해야 할 때 유용하다. 예를 들어, 사용자 정의 출력 함수를 만들 때 사용될 수 있다. 일부 `printf`가 구현되어 있지 않은 임베디드 시스템에서 직접적인 구현을 위해 사용되기도 한다. `vsnprintf`는 C99 표준에 포함되었다.
#include
int vprintf(const char * restrict format, va_list args);
int vfprintf(FILE * restrict fp, const char * restrict format, va_list args);
int vsprintf(char * restrict str, const char * restrict format, va_list args);
int vsnprintf(char * restrict str, size_t size, const char * restrict format, va_list args);
#include /* 또는 wchar.h */
int vwprintf(const wchar_t * restrict format, va_list args);
int vfwprintf(FILE * restrict fp, const wchar_t * restrict format, va_list args);
int vswprintf(wchar_t * restrict str, size_t size, const wchar_t * restrict format, va_list args);
5. 취약점
`printf` 계열 함수들은 강력한 서식 지정 기능을 제공하지만, 잘못 사용될 경우 여러 가지 보안 취약점을 유발할 수 있다.
'''형식 문자열 관련 취약점'''
'''형식 지정자 개수 불일치''': 형식 문자열에 지정된 형식 지정자의 개수보다 함수에 전달된 인수의 개수가 적으면 정의되지 않은 동작이 발생한다. 일부 컴파일러에서는 부족한 인수에 해당하는 값을 스택에서 읽으려고 시도하는데, 이를 악용하면 스택의 내용을 읽을 수 있는 형식 문자열 공격으로 이어질 수 있다.[39]
'''제어되지 않은 형식 문자열''': 프로그램 외부(예: 사용자 입력)에서 받아온 문자열을 검증 없이 그대로 형식 문자열로 사용하는 경우, 공격자가 악의적인 형식 지정자(예: `'%n'`, `'%x'`)를 포함한 문자열을 입력하여 메모리 내용을 읽거나 쓰는 등의 공격을 수행할 수 있다. 이를 제어되지 않은 형식 문자열 익스플로잇이라고 한다. 일반적으로 형식 문자열은 문자열 리터럴로 사용하는 것이 안전하며, 이를 통해 정적 프로그램 분석으로 잠재적인 문제를 미리 발견할 수 있다.
'''`%n` 서식 지정자''': `'%n'` 서식 지정자는 출력된 문자 수를 해당 인수가 가리키는 메모리 주소에 쓰는 기능을 한다. 겉보기에는 단순 출력 함수 같지만, 이 기능을 악용하면 임의의 메모리 위치에 값을 쓸 수 있어 정교한 형식 문자열 공격에 사용될 수 있다.[24][39]Visual C++의 C 런타임 라이브러리(CRT)에서는 보안 강화를 위해 기본적으로 `'%n'` 서식의 사용을 비활성화했다.[40] 흥미롭게도 `'%n'` 기능 때문에 `'printf'` 함수는 잘 구성된 인수 집합과 함께 사용될 경우 우연히 튜링 완전성을 갖게 되기도 한다. 이를 이용해 형식 문자열만으로 틱택토 게임을 구현한 사례가 제27회 IOCCC에서 우승하기도 했다.[25]
'''버퍼 오버플로우'''
문자열 버퍼에 서식화된 결과를 출력하는 `'sprintf'`나 `'vsprintf'` 함수는 출력될 문자열의 최대 길이를 제한하는 기능이 없다. 만약 서식화된 결과 문자열의 길이가 준비된 버퍼의 크기보다 크면 버퍼 경계를 넘어 데이터를 쓰게 되어 버퍼 오버플로우 취약점이 발생한다. 이는 심각한 보안 구멍으로 이어질 수 있으므로, 이 함수들의 사용은 지양해야 한다. 대신 C99 표준에 추가된 `'snprintf'`나 `'vsnprintf'` 함수를 사용해야 한다. 이 함수들은 출력 버퍼의 크기를 인수로 받아 지정된 크기를 넘지 않도록 보장해주므로 버퍼 오버플로우를 예방할 수 있다.
'''기타 문제 및 대응'''
'''인수 타입 불일치''': 형식 지정자가 요구하는 데이터 타입과 실제로 전달된 인수의 타입이 일치하지 않으면 미정의 동작을 일으킨다. C 언어의 가변 인자 처리 방식은 타입 검사를 수행하지 않으므로, 함수 내부에서는 이러한 불일치를 감지할 수 없다. 따라서 올바른 형식 지정자와 인수를 사용하는 것은 전적으로 프로그래머의 책임이다.
'''컴파일러 경고 활용''': GCC와 같은 일부 최신 컴파일러는 `'-Wall'` 또는 `'-Wformat'` 컴파일 옵션을 사용하여 형식 문자열과 인수의 불일치 등을 정적으로 검사하고 경고를 출력해준다. 이를 통해 잠재적인 오류를 컴파일 단계에서 발견하는 데 도움을 받을 수 있다.
'''안전 강화 함수 사용''': C11 표준의 Annex K에서는 보안을 강화한 `'printf_s'`, `'sprintf_s'` 등의 함수군(소위 "Bounds-checking interfaces")을 제안한다. 이 함수들은 실행 시점에 널 포인터 검사, 출력 버퍼 크기 검사 등을 수행하고, `'%n'` 형식 지정자의 사용을 금지하여 기존 함수들의 취약점을 보완한다. 오류가 발생하면 미리 설정된 제약 핸들러 함수가 호출된다.
6. printf를 지원하는 프로그래밍 언어
`printf` 함수 또는 이와 유사한 서식 지정 출력 기능은 여러 프로그래밍 언어에서 지원된다. 주요 지원 언어는 다음과 같다.
파이썬의 경우, 버전 2.x에서는 `print`가 문이었으나, 3.x부터는 `print` 함수로 변경되었다. 파이썬처럼 `'%'` 연산자를 사용하여 `sprintf()`와 유사한 기능을 지원하는 언어도 있다. Boost C++ 라이브러리의 Boost.Format[44]에서는 `boost::format` 클래스의 `'%'` 연산자 오버로딩을 통해 비슷한 기능을 구현했다[45].
C++11 표준에서는 C99의 라이브러리 함수 대부분을 포함하게 되었다. 이후 C++20에서는 함수 템플릿 `std::format()`[46]이, C++23에서는 `std::print()`[47]가 표준 라이브러리에 추가되었다.
또한, 유닉스 계열 운영 체제에서는 printf 명령어를 사용할 수 있다. 이는 외부 명령어로 제공될 뿐만 아니라, Bash와 같은 일부 유닉스 셸에서는 내장 명령어로도 구현되어 있다.
7. 한국어 위키백과 특화 정보
(내용 없음)
7. 1. 한국 프로그래밍 환경
printf 함수 또는 이와 유사한 출력 형식 지정 기능은 한국 프로그래밍 환경에서도 널리 사용되는 여러 언어에 포함되어 있다.[26][27][28] 대표적으로 C와 C++를 비롯하여, 웹 개발 등에서 자주 사용되는 자바(버전 1.5 이상), PHP, 파이썬(`%` 연산자 사용), Go, 루비 등 다양한 언어에서 지원된다.[26][28] 이러한 기능은 시스템 프로그래밍, 임베디드 시스템 개발, 웹 애플리케이션 개발 등 다양한 분야에서 디버깅 및 로깅을 포함한 데이터 출력 목적으로 활용된다.
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.