LLVM libc++ string 内存布局解析

这篇文章来看看 ARM64 架构下 libc++ 的 std::string 内存布局。libc++ 实现了短字符串优化(SSO, Small String Optimization),让小字符串直接存储在对象内部,避免堆内存分配。有趣的是,根据 ABI 的不同,libc++ 实际存在两种不同的内存布局方案,由 _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT 宏控制。

默认布局

在未定义特殊宏的情况下,libc++ 使用这种标准布局。__long__short 结构体的字段顺序如下:

  • __long(长字符串)__is_long_ + __cap_ 位域在前,然后是 __size_(长度),最后是 __data_(指向堆内存的指针)
  • __short(短字符串)__is_long_ + __size_ 位域在前,后面是 __data_(内联缓冲区,23 字节)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#import "@preview/rivet:0.3.0": schema, config

#set page(height: auto, margin: 0pt)

#let default_layout = schema.load(```yaml
structures:
main:
bits: 24
ranges:
22-0:
name: Payload
depends-on: 23
values:
"0":
description: # Short String (SSO)
structure: __short
"1":
description: # Long String (Heap)
structure: __long
23:
name: F
description: flag (MSB)
values:
"0": is_long=0, SSO mode
"1": is_long=1, heap mode

__short:
bits: 23
ranges:
22-0:
name: __data_
description: inline string buffer (23 bytes)
23:
name: '' # F
description: is_long (MSB) + size (7 LSBs)

__long:
bits: 23
ranges:
23-16:
name: __cap_
description: capacity (8 bytes, MSB is is_long)
15-8:
name: __size_
description: length (8 bytes)
7-0:
name: __data_
description: pointer to data (8 bytes)
23:
name: '' # F
description: is_long flag (MSB)
```)

#schema.render(default_layout, config: config.config(
default-font-family: "Monaspace Neon",
default-font-size: 14pt,
italic-font-family: "Monaspace Neon",
italic-font-size: 12pt
))

Alternate Layout

当 ABI 定义了 _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT 宏时,libc++ 使用这种布局。字段顺序与默认布局相反:

  • __long(长字符串)__data_ 指针在前,接着是 __size_,最后是 __cap_ + __is_long_ 位域
  • __short(短字符串)__data_ 缓冲区在前,__size_ + __is_long_ 位域在后

核心差异:两种布局的区别主要体现在字段顺序标志位位置

简单对比:

  • 默认布局:位域在结构体开头__is_long_ 位于最高位(MSB)
  • Alternate Layout:位域在结构体末尾__is_long_ 位于最低位(LSB)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#import "@preview/rivet:0.3.0": schema, config

#set page(height: auto, margin: 0pt)

#let alternate_layout = schema.load(```yaml
structures:
main:
bits: 24
ranges:
23-1:
name: Payload
depends-on: 0
values:
"0":
description: # Short String (SSO)
structure: __short
"1":
description: # Long String (Heap)
structure: __long
0:
name: F
description: flag (LSB)
values:
"0": is_long=0, SSO mode
"1": is_long=1, heap mode

__long:
bits: 24
ranges:
23-16:
name: __data_
description: pointer to data (8 bytes)
15-8:
name: __size_
description: length (8 bytes)
7-0:
name: __cap_
description: capacity (7 bytes, byte 0 holds cap_lo + is_long)
0:
name: '1'
description: is_long (LSB) and cap_lo (7 MSBs)

__short:
bits: 24
ranges:
23-1:
name: __data_
description: inline string buffer (23 bytes)
0:
name: '0'
description: is_long (LSB) and size (7 MSBs)
```)

#schema.render(alternate_layout, config: config.config(
default-font-family: "Monaspace Neon",
default-font-size: 14pt,
italic-font-family: "Monaspace Neon",
italic-font-size: 12pt
))

两种布局的核心差异

特性 默认布局 Alternate Layout
__is_long_ 位置 最高位(MSB) 最低位(LSB)
位域位置 结构体开头 结构体末尾

两者的本质区别是位域和标志位的位置安排不同,一个前置一个后置。

代码层面的差异

以下是两种布局在 libc++ 源码中的简化对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifdef _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
// Alternate Layout
struct __long {
pointer __data_;
size_type __size_;
size_type __cap_ : sizeof(size_type) * CHAR_BIT - 1;
size_type __is_long_ : 1;
};

struct __short {
value_type __data_[__min_cap];
unsigned char __padding_[sizeof(value_type) - 1];
unsigned char __size_ : 7;
unsigned char __is_long_ : 1;
};
#else
// 默认布局
struct __long {
struct {
size_type __is_long_ : 1;
size_type __cap_ : sizeof(size_type) * CHAR_BIT - 1;
};
size_type __size_;
pointer __data_;
};

struct __short {
struct {
unsigned char __is_long_ : 1;
unsigned char __size_ : 7;
};
value_type __data_[__min_cap];
};
#endif

总结

_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT 宏由 ABI 层面定义,控制着 libc++ std::string 的两种内存布局:

  1. 默认布局:位域字段在结构体开头,__is_long_ 位于最高位(MSB)
  2. Alternate Layout:位域字段在结构体末尾,__is_long_ 位于最低位(LSB)

两种布局在 ARM64 上 SSO 容量均为 23 字节(64 位系统),足以容纳大多数日常使用的短字符串。两种布局并存的原因主要与 ABI 兼容性有关,不同平台或历史版本可能采用不同的默认设置。

实际应用

在逆向工程或跨语言交互场景中,正确识别目标平台使用的布局至关重要。例如,在 Android 平台上与 libc++ 的 std::string 进行交互时,需要运行时检测当前使用的是哪种布局。相关实现可以参考我的 MistService-PoC,其中展示了如何通过运行时检测来确定 libc++ 的布局类型,并据此正确解析字符串内容。


LLVM libc++ string 内存布局解析
https://neo.mufanc.xyz/posts/17518/
作者
Mufanc
发布于
2026年3月22日
许可协议