absl::string_view 함정 — dangling·c_str·임시 객체
#한 줄 요약
string_view는 가볍지만 non-owning이다. 소유권을 다른 객체에 의탁한다는 뜻이며, lifetime을 잘못 다루면 모든 잘못이 정적 분석으로 잡히지 않는다. 가장 자주 보이는 세 함정은 dangling reference, 임시 std::string 바인딩, null-terminator 가정이다.
#함정 1 — 임시 std::string 바인딩
가장 흔하다. 함수가 std::string을 반환하는 경우에 발생한다.
std::string GetUserName();
// 회피 — UBabsl::string_view name = GetUserName();LOG(INFO) << name; // GetUserName()의 반환값은 이미 소멸됨GetUserName()의 반환값은 임시 객체다. name이 그 임시의 내부 버퍼를 가리키는 순간, full expression이 끝나면서 임시는 파괴된다. name은 해제된 메모리를 가리킨다.
규칙: string_view로 함수 반환값을 받지 말 것. 함수가 view를 반환하는 경우에만 view로 받는다.
// Good — std::string으로 받는다std::string name = GetUserName();LOG(INFO) << name;
// Good — view를 반환하는 함수라면absl::string_view ViewSomething();absl::string_view v = ViewSomething();리뷰 시점 패턴: string_view x = Func() 형태를 보면 Func의 반환 타입을 확인한다.
#함정 2 — 멤버 변수 보관
// 회피class Logger { public: explicit Logger(absl::string_view prefix) : prefix_(prefix) {} void Log(absl::string_view msg) { /* prefix_ 사용 */ } private: absl::string_view prefix_;};
// 호출 측std::string p = "[server] ";Logger logger(p);p = "[changed] "; // 원본이 변경되거나// p가 스코프 밖으로 나가면 prefix_는 danglingstring_view를 멤버로 들면 객체의 lifetime이 외부 문자열의 lifetime에 묶인다. 사용자가 이 규약을 어기는 순간 UB가 발생한다.
// Good — 보관할 거면 복사한다class Logger { public: explicit Logger(absl::string_view prefix) : prefix_(prefix) {} // ... private: std::string prefix_; // 복사 보관};예외는 flyweight 패턴처럼 외부 영구 객체의 view를 의도적으로 들고 있는 경우다. 이때 의도를 주석으로 명시한다.
class Token { // 원본 source의 lifetime > Token의 lifetime이 호출 규약으로 보장됨. absl::string_view text_;};#함정 3 — c_str 변환
string_view에는 c_str()이 없다. 메모리 종단에 null이 있다는 보장이 없기 때문이다.
// substring viewabsl::string_view full = "boom-shaka-laka";absl::string_view first = full.substr(0, 4); // "boom"// first.data()는 'b'를 가리키지만, first.data()[4]는 '-'이다C API에 넘기려면 새 string을 만들어야 한다.
// 회피 — 컴파일 안 됨open(view.c_str(), O_RDONLY);
// 회피 — view.data()는 null-terminated 아님open(view.data(), O_RDONLY);
// Good — 변환 비용을 인지하고 알로케이션std::string path(view);open(path.c_str(), O_RDONLY);이 변환은 alloc + 복사 비용이 든다. 짧은 경로라면 SSO 안에 들어가 alloc은 없지만 복사는 일어난다. C API 경계는 view의 약점이다. 가능하면 C++ API를 거치는 쪽으로 설계를 끌어간다.
#함정 4 — 비교 함수의 char* 트랩
absl::string_view sv = "boom";const char* cs = sv.data(); // 위험: cs는 null-terminated 아닐 수 있음
strcmp(cs, "boom") == 0; // sv가 full string이면 우연히 동작sv가 다른 view의 substring이면 strcmp는 종단을 넘어서 읽는다. 비교는 항상 view 비교 또는 absl 헬퍼로.
// Goodsv == "boom"absl::EqualsIgnoreCase(sv, "boom")#함정 5 — operator+의 함정
std::string은 string_view와의 +를 지원하지 않는다.
absl::string_view a = "hello";absl::string_view b = " world";
// 회피 — 컴파일 안 됨std::string s = a + b;
// 회피 — 부분 컴파일 (string + view는 지원)std::string s2 = std::string(a) + b; // 변환 비용
// Good — StrCatstd::string s3 = absl::StrCat(a, b);Part 4-03 — StrCat에서 StrCat이 왜 더 빠른지 본다.
#함정 6 — 잘못된 length 가정
생성자 두 종류를 혼동하면 데이터를 잘못 읽는다.
const char data[] = {'a', 'b', 'c', 0, 'd', 'e'}; // 6 bytes, 중간에 NUL
absl::string_view s1(data); // strlen 사용 → length=3 ("abc")absl::string_view s2(data, 6); // 명시 length=6 ("abc\0de")binary 데이터를 view로 다룰 때는 반드시 명시 length 생성자를 쓴다. strlen은 첫 NUL에서 끊는다.
#함정 7 — operator[]는 검사 없음
absl::string_view sv = "";char c = sv[0]; // UB — bound check 없음검사가 필요하면 sv.at(i)(예외)나 사전 size 검사로.
#코드 리뷰 패턴
이 함정들을 잡는 리뷰 룰은 단순하다.
- **
string_view x = Func();**가 보이면Func의 반환 타입을 본다.std::string이면 차단. - 클래스 멤버
string_view는 lifetime 주석이 있는지 확인. view.data()또는view.c_str()이 보이면 C API로 흘러가는지 확인.- binary 데이터 처리에
string_view(ptr)단일 인자 생성자가 쓰이는지 확인.
#정적 분석 도구
clang의 -Wdangling-gsl 경고가 일부 패턴을 잡는다. [[clang::lifetimebound]] 어노테이션은 함수 인자의 lifetime이 반환값에 묶인다는 정보를 컴파일러에 준다.
// 명시: 반환된 view의 lifetime은 input의 lifetime에 묶인다absl::string_view Trim(absl::string_view s [[clang::lifetimebound]]);
std::string GetSrc();absl::string_view v = Trim(GetSrc()); // 컴파일러 경고Abseil 자체도 일부 함수에 lifetimebound를 붙인다. 새 view 반환 함수를 만들 때 함께 붙이면 사용자 측 dangling을 줄인다.
#정리
string_view는 lifetime을 외부에 의탁하는 non-owning 참조.- 임시 std::string 바인딩, 멤버 변수 보관, C API c_str 변환이 3대 함정.
- C API 경계에서는
std::string변환 비용을 인지한다. - binary 데이터에는 명시 length 생성자만 쓴다.
[[clang::lifetimebound]]로 정적 분석 적용 범위를 넓힌다.
#다음 편
Part 4-03 — StrCat에서 string_view를 받는 가변 인자 문자열 연결 API를 본다.
#관련 항목
Abseil Code Review · 21 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 회피
관련 글
Abseil 자주 보는 anti-pattern
code review에서 반복적으로 지적하는 Abseil 오용 사례 — string_view dangling, mutex annotation 누락, StatusOr 무시, 잘못된 hash 등.
같은 시리즈에서 이어 읽기
Google 스타일의 Abseil 사용 패턴
Part 13-01: Google이 사내에서 Abseil을 어떻게 쓰는지 — code review에서 자주 보는 권장 패턴.
같은 시리즈에서 이어 읽기
absl::string_view — non-owning 문자열 참조
Part 4-01: absl::string_view — 복사 없는 문자열 전달, lifetime 책임, std::string_view와의 관계.
같은 시리즈에서 이어 읽기