과거 C++98/03 시절, 문자열 형식 지정 출력은 주로 C 표준 라이브러리 함수를 이용했습니다. printf 계열 함수들은 간단히 사용할 수 있지만, 타입 미스매치 문제나 서식 문자열 관리가 번거로운 단점이 있었습니다. 예를 들어 %d, %f, %s 등의 포맷 지정자가 실제 매개변수와 불일치할 경우 런타임 버그가 발생할 수 있으며, 이는 디버깅하기도 까다롭습니다.
모던 C++에서는 이러한 문제를 해결하기 위해 C++20부터 std::format 함수를 도입했습니다. 이는 타입 안전하고, Python의 f-string과 유사한 형식 지정 문법을 제공하며, 가독성과 유지보수성을 크게 향상시킵니다.
관련 참고 자료:
과거: printf 계열 함수의 문제점
printf는 서식 문자열과 가변 인자를 이용하여 출력을 형식화합니다. 하지만 포맷 문자열과 인자의 타입 불일치를 컴파일 타임에 잡기 어렵고, 문자열 결합 시 타입 변환 오류가 런타임까지 이어질 수 있습니다.
#include <cstdio>
int main() {
int x = 42;
// 정수 출력
std::printf("x = %d\n", x);
// 실수 서식에 정수 변수 전달(실수 출력 기대)
// std::printf("x as float = %f\n", x); // 정의되지 않은 동작(UB)!
return 0;
}
위 예제에서 %f 자리에 정수 변수를 전달하면 정의되지 않은 동작을 일으킬 수 있습니다. 컴파일러는 이를 잡아내지 못하고, 런타임에 이상한 값이 출력되거나 프로그램이 오동작할 수 있습니다.
또한 형식 문자열을 외부 소스로부터 받아 처리할 경우, %s나 %d 등의 포맷 지정자를 악용한 공격(포맷 문자열 공격)으로부터 안전하지 않습니다.
현재: std::format으로 타입 안전한 출력
C++20에서 도입된 std::format은 타입 안전성을 보장하고, 가독성 좋은 문자열 포맷팅을 제공합니다. Python의 f-string과 유사한 구문으로 인자를 순서대로 {} 안에 배치하며, 선택적으로 형식을 지정할 수 있습니다.
#include <format>
#include <iostream>
int main() {
int x = 42;
double pi = 3.14159;
std::string name = "Alice";
std::string msg = std::format("x = {}, pi = {:.2f}, name = {}", x, pi, name);
std::cout << msg << "\n";
// 출력: "x = 42, pi = 3.14, name = Alice"
return 0;
}
std::format은 인자 타입에 따라 적절한 형식 변환을 자동 처리하며, 컴파일러가 인자의 개수와 타입을 체크하므로 타입 불일치로 인한 런타임 에러를 예방할 수 있습니다. 또한 {:.2f}와 같이 포맷 스펙을 지정하여 부동소수점 정밀도를 간단히 조절할 수 있습니다.
std::format과 iostream
std::format은 문자열을 반환하므로, std::cout << std::format("...", ...) 형태로 바로 출력하거나, 다른 문자열 조작 후 출력할 수 있습니다. 이를 통해 기존 iostream과 자연스럽게 연계할 수 있습니다.
또한 std::format_to, std::format_to_n 함수로 특정 이터레이터나 버퍼에 출력할 수도 있어, 다양한 상황에 맞춘 유연한 포맷팅이 가능합니다.
왜 이런 변화가 필요한가?
- 타입 안전성 확보
std::format은 컴파일 타임에 인자 개수와 타입을 체크하여, printf 계열 함수의 런타임 에러 위험을 줄여줍니다. - 가독성과 유지보수성 향상
{} 플레이스홀더와 선택적 형식 지정자는 코드 읽기와 관리가 훨씬 수월합니다. 타입 변환 로직을 일일이 신경 쓸 필요가 없습니다. - 보안성과 신뢰성 개선
printf 형식 문자열 취약점이 없는 것은 아니지만, std::format은 C++ 타입 시스템에 더 밀접하게 연결되어 있어 문제 발생 가능성을 낮춥니다. 코드를 더 안전하게 유지할 수 있습니다.
'개발 이야기 > C++' 카테고리의 다른 글
[모던 C++ #8] 구식 function object와 std::bind를 버리고 람다와 std::function로! (0) | 2024.12.14 |
---|---|
[모던 C++ #7] SFINAE 트릭을 떠나 Concepts로! (0) | 2024.12.14 |
[모던 C++ #5] C-Style 문자열을 넘어: std::string과 std::string_view로! (0) | 2024.12.14 |
[모던 C++ #4] 옛날 for 루프를 버리고 range-based for와 C++20 Ranges로! (0) | 2024.12.14 |
[모던 C++ #3] 매크로 상수를 버리고 constexpr와 enum class로! (0) | 2024.12.14 |