Abseil Versioning과 ABI 호환성 정책
한 줄 요약: Abseil의 ABI 정책은 “같은 빌드 단위 안에서만 호환을 보장”한다. 이 제약은 inline namespace로 강제되고, 다른 옵션으로 빌드된 두 Abseil 코드를 같은 실행 파일에 링크하면 컴파일 또는 링크 단계에서 실패하도록 설계되어 있다.
#어떤 문제를 푸는가
C++ 라이브러리의 ABI는 미묘하다. 같은 헤더, 같은 함수 시그니처라도 컴파일 옵션이 다르면 메모리 레이아웃이나 calling convention이 달라질 수 있다.
// 시나리오: 라이브러리 A는 std=c++17로 빌드, 라이브러리 B는 c++20로 빌드.// 둘 다 absl::Time을 인자로 받는 함수를 export.// 같은 binary에 링크하면? — undefined behavior.C++ 표준 라이브러리는 이런 충돌을 막기 위해 inline namespace를 쓴다. libstdc++는 __cxx11, libc++는 __1 같은 이름이 그 예다. Abseil도 같은 기법을 더 적극적으로 활용한다.
#정책: 단일 빌드 단위 내 ABI 호환
Abseil의 공식 약속은 한 줄이다.
같은 컴파일 옵션으로 같은 시점에 빌드된 코드끼리만 ABI 호환을 보장한다.
이 정책은 두 가지 자유를 가져온다.
- 내부 구현을 자유롭게 바꿀 수 있다 —
flat_hash_map의 probing 방식이 LTS 사이에 바뀐 적이 있다. - 사용자가 잘못된 mix를 하면 빌드 시점에 잡힌다 — runtime crash가 아니라 link error.
대신 사용자는 다음 두 가지 시나리오를 피해야 한다.
- 시나리오 A — 사전 빌드된
absl.so에 사용자가 빌드한 Abseil 사용 코드를 링크. 두 쪽의 컴파일 옵션이 다를 수 있어 위험하다. - 시나리오 B — 두 third-party가 각자 prebuild Abseil을 들고 온다. 같은 심볼이 두 번 정의되어 ODR violation이 날 수 있다.
#inline namespace로 강제
Abseil은 ABSL_NAMESPACE_BEGIN / ABSL_NAMESPACE_END라는 매크로를 모든 헤더 안에 둔다.
// absl/base/config.h에서 발췌#define ABSL_NAMESPACE_BEGIN \ inline namespace lts_20240722 {
#define ABSL_NAMESPACE_END }namespace 이름은 LTS 날짜 또는 HEAD commit hash에서 유래한다. 다른 버전으로 빌드된 Abseil의 심볼이 같은 binary 안에 들어오면 namespace 이름이 달라 link 단계에서 분리된다. 같은 심볼이 두 정의를 갖지 않게 된다.
// 사용자 입장에서는 inline namespace이므로 보이지 않음absl::Status s = absl::OkStatus();// 실제 mangled name은 absl::lts_20240722::Status
// 두 버전이 섞이면 두 개의 다른 type으로 인식됨// 컴파일 또는 link 단계에서 에러 발생inline namespace의 핵심 속성은 다음과 같다.
- 사용자 코드는
absl::Status로 그대로 쓴다 (inline의 의미). - 컴파일러가 보는 실제 namespace는 버전이 인코딩된 이름.
- ADL, template argument deduction도 정상 작동.
#ABI에 영향을 주는 옵션
같은 Abseil 소스 코드라도 다음 옵션이 다르면 ABI가 달라질 수 있다.
| 옵션 | ABI 영향 |
|---|---|
| C++ 표준 (c++17 vs c++20) | sizeof, layout 변경 가능 |
_GLIBCXX_USE_CXX11_ABI | libstdc++의 string/list 표현 |
-fno-exceptions | exception 비활성화 코드 경로 |
-fno-rtti | RTTI 없는 환경 |
NDEBUG | assert 코드 포함 여부 |
_LIBCPP_HARDENING_MODE | libc++ 안전성 모드 |
Abseil은 이 중 일부를 inline namespace 이름에 인코딩한다. 모든 옵션을 잡지는 않지만, 가장 흔한 mismatch는 잡는다.
#ABSL_OPTION_USE_STD_*
Abseil은 polyfill 타입에 대해 컴파일 시점에 std 버전을 쓸지 absl 버전을 쓸지 선택하는 옵션을 제공한다.
#define ABSL_OPTION_USE_STD_OPTIONAL 2// 0 = absl::optional 자체 구현// 1 = std::optional 사용 (C++17 이상에서)// 2 = 자동 감지
#define ABSL_OPTION_USE_STD_STRING_VIEW 2#define ABSL_OPTION_USE_STD_VARIANT 2#define ABSL_OPTION_USE_STD_ANY 2옵션 2가 default다. C++ 표준 버전을 감지해서 자동으로 선택한다.
ABI 관점에서 중요한 것은 다음이다.
// 시나리오: A는 ABSL_OPTION_USE_STD_OPTIONAL=1로 빌드 (std::optional alias)// 시나리오: B는 ABSL_OPTION_USE_STD_OPTIONAL=0로 빌드 (absl 자체 구현)// A의 absl::optional<int>와 B의 absl::optional<int>는 다른 type// → link 단계에서 잡혀야 한다옵션이 다르면 inline namespace 이름이 달라지도록 빌드 시스템이 처리한다.
#prebuilt 사용 시 함정
ABI 정책을 가장 자주 어기는 시나리오는 시스템 패키지 사용이다.
# Ubuntu aptsudo apt install libabsl-dev# /usr/lib/x86_64-linux-gnu/libabsl_*.so 설치
# 사용자 코드에서g++ -std=c++17 main.cc -labsl_strings -labsl_status이때 /usr/lib의 libabsl_*.so가 어떤 옵션으로 빌드되었는지 사용자는 모른다. 사용자의 -std=c++17와 시스템 라이브러리의 빌드 옵션이 일치하지 않으면 미묘한 ABI 불일치가 발생한다. inline namespace로 잡히면 link error로 끝나지만, 어떤 경우는 runtime에 문제가 드러난다.
권장하는 빌드 모델은 다음과 같다.
- 사용자 프로젝트가 Abseil 소스를 가져와 자체 빌드한다.
- Bazel, CMake FetchContent, vcpkg manifest mode 모두 무방하다.
- apt/yum/brew의 시스템 패키지는 비권장이다. 다만 toy나 learning 용도에는 무방하다.
#두 라이브러리가 Abseil을 각자 갖고 오는 경우
my_app├── lib_x.so → Abseil 20240116.0 prebuilt 포함├── lib_y.so → Abseil 20240722.0 prebuilt 포함└── 사용자 코드 → Abseil 20240722.0 source build이 시나리오에서 lib_x와 lib_y의 Abseil 심볼은 inline namespace 이름이 다르다. dynamic linker가 같은 심볼을 두 번 보지 않는다. 표면상은 동작한다.
문제는 인터페이스에 Abseil type이 노출된 경우다.
#include "absl/status/status.h"absl::Status DoSomething(); // 20240116 namespace
// lib_y.h#include "absl/status/status.h"absl::Status DoOther(); // 20240722 namespace
// 사용자 코드 — Status를 두 라이브러리 사이에 주고받으려 함auto s = DoSomething();return DoOther(); // 다른 type, 컴파일 에러해법은 인터페이스에 Abseil type을 노출하지 않는 것이다. 라이브러리는 자기 내부에서만 Abseil을 쓰고, 인터페이스는 std 또는 자체 type으로.
#ABSL_LTS_RELEASE_VERSION
Abseil은 LTS 버전을 컴파일러 매크로로 노출한다.
#include "absl/base/config.h"
#ifdef ABSL_LTS_RELEASE_VERSION // LTS 빌드. 값은 예: 20240722 static_assert(ABSL_LTS_RELEASE_VERSION >= 20240116, "Need at least 20240116 LTS");#else // HEAD 빌드#endif이 매크로로 LTS 간 차이를 #if로 분기하는 것은 가능하지만 권장하지 않는다. 한 LTS 안에서 일관된 코드를 쓰는 게 낫다.
#std로의 마이그레이션 부담
ABI 정책 때문에 마이그레이션도 신중해야 한다.
// 코드베이스 일부가 absl::optional, 나머지가 std::optionalvoid Process(absl::optional<int>);void Process(std::optional<int>);// 다른 type. 같은 함수 두 번 정의해야 하나? — 마이그레이션이 한 번에 끝나야 한다는 신호
// ABSL_OPTION_USE_STD_OPTIONAL=1로 두면 둘이 같은 type이 됨// 하지만 이 옵션은 전체 코드베이스에서 통일되어야 함마이그레이션은 코드베이스 전체를 한 단위로 본다. 점진적 변환은 어렵고, 한 commit으로 끊는 것이 깔끔하다.
#코드 리뷰 포인트
// 리뷰 질문 1: 라이브러리 인터페이스에 absl type이 들어가 있는가?// my_lib.h (public header)#include "absl/status/status.h"absl::Status MyAPI(); // 사용자가 같은 LTS의 Abseil을 써야만 한다는 제약
// Good — 가능하면 std 또는 자체 typestd::error_code MyAPI();// 또는 라이브러리 사용자에게 명확히 "Abseil 20240722.0 이상 필요" 문서화# 리뷰 질문 2: 시스템 Abseil + bundled Abseil이 mix되는가?find_package(absl REQUIRED) # 시스템add_subdirectory(third_party/absl) # bundled# 컴파일 에러는 안 나지만 link 시점에 충돌 가능// 리뷰 질문 3: ABSL_OPTION_USE_STD_*를 임의로 변경하는가?// 새 옵션을 코드베이스 일부에만 적용하면 type mismatch#define ABSL_OPTION_USE_STD_OPTIONAL 1 // 절대 부분 변경 금지#자주 보는 안티패턴
// 회피 — Abseil 내부 namespace를 직접 참조absl::lts_20240722::Status s = ...;// inline namespace는 추상화. 직접 참조하면 LTS 업데이트 시 깨짐.
// Goodabsl::Status s = ...;// 회피 — 두 ABI 버전을 같은 binary에 mix// 위에서 다룬 시나리오 — link error로 잡힐 수도 있고 안 잡힐 수도 있다.#정리
- Abseil의 ABI 호환은 “같은 빌드 단위 내”에서만.
- inline namespace에 LTS 버전과 옵션을 인코딩해서 mismatch를 link 단계에서 잡으려 한다.
- 시스템 패키지(apt 등)는 권장하지 않는다. source build가 안전.
- 라이브러리 인터페이스에 Abseil type을 노출하면 사용자에게 같은 Abseil 버전을 강제하게 됨.
ABSL_OPTION_USE_STD_*는 코드베이스 전체에서 통일되어야 한다.
#다음 편
Part 1을 마치고 Part 2로 넘어간다. Part 2-01에서 ABSL_HAVE_*, ABSL_ATTRIBUTE_* 매크로 군을 본다. 컴파일러와 플랫폼 차이를 흡수하는 첫 번째 도구.
#관련 항목
Abseil Code Review · 6 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 LTS vs HEAD 릴리스 모델 분석
Part 1-04: LTS vs HEAD — Abseil의 두 가지 릴리스 모델, breaking change 정책, 마이그레이션 비용.
같은 시리즈에서 이어 읽기
absl::PeriodicSampler — 적응형 샘플링·jitter 회피
absl::profiling_internal::PeriodicSampler — sampling rate를 동적으로 조정, geometric distribution으로 jitter 회피. 메모리 할당 추적·profiling 인프라의 기반.
같은 시리즈에서 이어 읽기
absl::ComputeCrc32c — 하드웨어 가속 체크섬
absl::ComputeCrc32c — SSE4.2 CRC32, ARM CRC 명령어로 가속된 CRC32C 구현. iSCSI·Btrfs·protobuf에서 표준화된 무결성 검사.
같은 시리즈에서 이어 읽기
이 글을 참조하는 글 (7)
- Abseil Conformance·Policy 분석 — Abseil Code Review
- Abseil LTS vs HEAD 릴리스 모델 분석 — Abseil Code Review
- Abseil 빌드와 의존성 — Bazel vs CMake — Abseil Code Review
- Abseil 설계 철학 — std 호환과 추가 기능의 균형 — Abseil Code Review
- Abseil 개요 — Google이 std를 보완한 이유 — Abseil Code Review
- Folly API stability 정책 — 어떤 보장도 없다는 솔직함 — Folly Code Review
- Folly vs Abseil 철학 비교 — performance-first vs std-compatible — Folly Code Review