absl::StrFormat — type-safe printf·FormatSpec
#한 줄 요약
absl::StrFormat은 printf 형식 문자열의 syntax를 그대로 쓰면서 type 검증을 컴파일 타임에 한다. %d에 std::string을 넘기면 컴파일 에러다. C++20의 std::format이 표준화되기 전부터 Google이 써 온 안전한 포맷 API다.
#동기
printf/snprintf는 syntax는 익숙하지만 안전하지 않다.
// 회피 — UBchar buf[64];snprintf(buf, sizeof(buf), "id=%d", "string_not_int"); // type 불일치snprintf(buf, sizeof(buf), "%s", nullptr); // glibc 외에는 UBsnprintf(buf, 4, "%s", "too long"); // 잘림 — 실수 잡기 어려움C++의 std::stringstream은 안전하지만 형식 표현력이 떨어지고 locale에 묶인다. absl::StrFormat은 printf의 표현력 + C++의 type 안전성을 결합한다.
format spec과 인자 타입이 컴파일 타임에 매칭되는 흐름은 다음과 같다.
#API와 사용법
#include "absl/strings/str_format.h"
namespace absl {template <typename... Args>std::string StrFormat(const FormatSpec<Args...>& format, const Args&... args);
template <typename... Args>bool StrAppendFormat(std::string* dst, const FormatSpec<Args...>& format, const Args&... args);
template <typename... Args>ABSL_MUST_USE_RESULT int SNPrintF(char* out, size_t n, const FormatSpec<Args...>& format, const Args&... args);}기본 사용은 printf와 동일하다.
std::string s = absl::StrFormat("user=%d action=%s", 42, "login");// "user=42 action=login"
std::string ip = absl::StrFormat("%d.%d.%d.%d", 192, 168, 0, 1);
double pi = 3.141592653589;std::string f = absl::StrFormat("%.4f", pi); // "3.1416"std::string과 absl::string_view는 %s로 받는다 — .c_str() 변환 불필요.
std::string name = "Alice";absl::string_view sv = "Bob";absl::StrFormat("hello %s and %s", name, sv);이 한 가지만으로도 raw printf보다 알로케이션이 줄어든다.
#type-safe 검증
FormatSpec은 가변 인자 템플릿 wrapper다. format string과 인자 타입이 일치하지 않으면 컴파일 에러다.
// 회피 — 컴파일 에러absl::StrFormat("id=%d", "not_int");// error: format string requires int, got const char[*]
absl::StrFormat("%s", 42);// error: format string requires string-like, got intFormatSpec은 constexpr이라 string literal은 컴파일 타임 검증이 가능하다. 런타임 결정 format은 별도 API가 필요하다 — 다음 절.
#FormatUntyped — 동적 format
format string이 런타임에 결정되는 경우(예: 로그 템플릿 외부 설정)는 FormatUntyped 또는 ParsedFormat<...>을 쓴다.
absl::UntypedFormatSpec spec("%d %s");std::string out;if (!absl::FormatUntyped(&out, spec, {absl::FormatArg(42), absl::FormatArg("hi")})) { // 검증 실패 (인자 타입 불일치 등)}타입 안전성은 런타임으로 미뤄지나, format 자체는 파싱·검증된다. printf의 raw UB는 발생하지 않는다.
ParsedFormat<chars...>로 미리 컴파일하면 반복 사용 시 파싱 비용을 절약한다.
absl::ParsedFormat<'d','s'> spec("%d %s");for (auto [n, s] : items) { std::string line = absl::StrFormat(spec, n, s);}#내부 구현
핵심 흐름은 다음과 같다.
// absl/strings/internal/str_format/extension.h (요약)class FormatRawSink { public: virtual void Write(string_view s) = 0;};
class FormatSinkImpl { std::string* buffer_; public: void Append(string_view s) { buffer_->append(s.data(), s.size()); }};각 conversion specifier(%d, %s, %f)는 대응 converter 함수가 있다. 컴파일러는 format string을 파싱해 각 위치별 converter를 인자 타입과 매칭한다. 매칭이 안 되면 static_assert 또는 SFINAE 실패로 컴파일 에러.
런타임에는 sink(buffer)에 순차 write한다. 한 호출당 alloc 0~1회.
#std::format / printf 비교
| API | type 안전 | format 표현력 | 컴파일 타임 검증 | locale-free |
|---|---|---|---|---|
printf | X | 강 | X | X |
std::stringstream | O | 약 | — | X |
std::format (C++20) | O | 강 | constexpr | O |
absl::StrFormat | O | 강 (printf 호환) | constexpr | O |
std::format은 새 syntax({0:.4f})다. absl::StrFormat은 printf syntax를 유지해 마이그레이션 비용이 낮다.
#코드 리뷰 포인트
1. snprintf → StrFormat
// 회피char buf[256];snprintf(buf, sizeof(buf), "user=%d ip=%s ms=%lld", uid, ip.c_str(), ms);std::string s(buf);
// Goodstd::string s = absl::StrFormat("user=%d ip=%s ms=%lld", uid, ip, ms);// .c_str() 불필요, 잘림 없음, type 검증2. log message format
LOG 시스템이 stream 기반(<<)이라도, 단일 line을 미리 만들어 넘기는 게 유리한 경우가 있다.
LOG(WARNING) << absl::StrFormat("slow query: %.2f ms (threshold=%.2f)", elapsed_ms, threshold_ms);3. 가변 정밀도 출력
%.*f 같은 가변 width/precision도 지원한다.
int prec = 6;absl::StrFormat("%.*f", prec, 3.141592); // "3.141592"#안티패턴
format string에 사용자 입력
printf 계열의 고전적 취약점. format에 user input이 그대로 들어가면 conversion specifier가 주입된다. StrFormat은 type 검증이 강하지만, 사용자 입력은 인자로 넘긴다.
// 회피 — UB / 보안 문제absl::StrFormat(user_supplied, ...);
// Goodabsl::StrFormat("%s", user_supplied);과도한 format 비용 hot path
StrFormat은 빠르지만 StrCat보다 무겁다(format string 파싱). 컴파일 타임에 specifier가 단순한 추가뿐이라면 StrCat이 더 빠르다.
// hot pathabsl::StrFormat("k=%d", n); // 더 무거움absl::StrCat("k=", n); // 더 가벼움format이 단순 concat 수준이면 StrCat, 정렬/정밀도/지수 표기가 필요하면 StrFormat.
#정리
StrFormat은 printf syntax + 컴파일 타임 type 검증.%s는std::string,absl::string_view를 받는다(.c_str()불필요).- 동적 format은
FormatUntyped/ParsedFormat. - buffer alloc 0~1회, locale-free, type-safe.
- 사용자 입력은 format이 아니라 인자로.
#다음 편
Part 4-07 — ASCII 함수에서 locale-free ASCII 유틸을 본다.
#관련 항목
Abseil Code Review · 25 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
absl::Cord — 분산 시스템용 대용량 문자열
absl::Cord — tree 구조로 표현되는 immutable-ish 문자열. zero-copy concat, shared substring, Google 내부 RPC payload의 기본 표현.
같은 시리즈에서 이어 읽기
absl::Time Format·Parse
FormatTime, ParseTime — Abseil이 strftime/RFC3339를 한 함수로 흡수하는 방법.
같은 시리즈에서 이어 읽기
Abseil Escape — CEscape·HexEscape·Base64
Part 4-08: absl::CEscape / CHexEscape / Base64Escape / WebSafeBase64Escape — 안전한 문자열 이스케이프와 base64 인코딩의 모든 변형.
같은 시리즈에서 이어 읽기