前文 内存中的字符串类型 学习研究了Go的字符串在内存中的结构和数据类型。
文本是两年多前的一篇学习笔记,研究的是C++字符串在内存中的结构。
环境
- 1. 操作系统:Ubuntu 16.04。
- 2. 调试软件:GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1。
- 3. 编译工具:g++ (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609。
string类的定义
string定义在/usr/include/c++/5/bits/stringfwd.h头文件中,如下:
- typedef basic_string<char> string;
basic_string类的定义通过泛型编程技术实现,详细定义请参考/usr/include/c++/5/bits/basic_string.h头文件,看起来非常复杂,具体实现此处并不关心,不再讨论。
测试string对象占用内存空间
通过以下代码可以测试string类对象占用内存空间情况。
- // demo.cpp
- #include <string>
- #include <iostream>
- int main(int argc, char const *argv[])
- {
- using namespace std;
- string s15(15, 'a'); // 字符串长度15
- string s16(16, 'x'); // 字符串长度16
- cout << "sizeof(string) = " << sizeof(string) << endl;
- cout << "sizeof(s15) = " << sizeof(s15) << endl;
- cout << "sizeof(s16) = " << sizeof(s16) << endl;
- return 0;
- }
因为32位和64位可执行程序不同,以下将分别编译测试。
将以上代码编译成32位可执行程序并执行,结果如下:
- $ g++ -m32 -g demo.cpp
- $ ./a.out
- sizeof(string) = 24
- sizeof(s15) = 24
- sizeof(s16) = 24
从以上输出结果,可以十分确定string类对象在内存中占用24个字节。
将以上代码编译成64位可执行程序并执行,结果如下:
- $ g++ -m64 -g demo.cpp
- $ ./a.out
- sizeof(string) = 32
- sizeof(s15) = 32
- sizeof(s16) = 32
从以上输出结果,可以十分确定string类对象在内存中占用32个字节。
32位可执行程序string对象的内存分配
为了查看内存分配,需要用到动态调试工具,此处使用gdb,并在源码16行设置断点。
调试过程中,打印main方法的栈数据,以及string对象及相关数据的内存,可以清晰看到string对象数据的内存占用情况。
- $ gdb a.out
- GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
- Reading symbols from a.out...done.
- (gdb) b 16
- Breakpoint 1 at 0x8048a80: file demo.cpp, line 16.
- (gdb) r
- Starting program: a.out
- sizeof(string) = 24
- sizeof(s15) = 24
- sizeof(s16) = 24
- Breakpoint 1, main (argc=1, argv=0xffffced4) at demo.cpp:16
- 16 return 0;
- (gdb) x /24wx $esp // 以16进制格式打印24个宽度为4字节的main函数堆栈数据(共96个字节)
- 0xffffcdd0: 0x08048790 0x0804a0ed 0x0804a04c 0xffffced4
- 0xffffcde0: 0xffffffff 0x00004a00 0xffffce08 0xffffcdf4
- 0xffffcdf0: 0x0000000f 0x61616161 0x61616161 0x61616161
- 0xffffce00: 0x00616161 0x0804fa10 0x00000010 0x00000010
- 0xffffce10: 0x00000001 0xffffced4 0xffffcedc 0xd0415500
- 0xffffce20: 0xffffce40 0x00000000 0x00000000 0xf7c86637
- (gdb) x /wx &s15 // 打印变量s15的内存地址
- 0xffffcdec: 0xffffcdf4
- (gdb) x /6xw 0xffffcdec // 打印string对象s15占用的24个字节内存数据
- 0xffffcdec: 0xffffcdf4 0x0000000f 0x61616161 0x61616161
- 0xffffcdfc: 0x61616161 0x00616161
- (gdb) x /s 0xffffcdf4 // string对象s15的1-4个字节是一个指向字符数据的指针
- 0xffffcdf4: 'a' <repeats 15 times>
- (gdb) x /16x 0xffffcdf4 // string对象s15的9-24个字节是代表数据的字符数组
- 0xffffcdf4: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x61
- 0xffffcdfc: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x00
- (gdb) x /wx &s16 // 打印变量s16的内存地址
- 0xffffce04: 0x0804fa10
- (gdb) x /6xw 0xffffce04 // 打印string对象s16占用的24个字节内存数据
- 0xffffce04: 0x0804fa10 0x00000010 0x00000010 0x00000001
- 0xffffce14: 0xffffced4 0xffffcedc
- (gdb) x /s 0x0804fa10 // string对象s16的1-4个字节是一个指向字符数据的指针
- 0x804fa10: 'x' <repeats 16 times>
- (gdb) x /16x 0x0804fa10
- 0x804fa10: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
- 0x804fa18: 0x78 0x78 0x78 0x78 0x78 0x78 0x78 0x78
- (gdb) c
- Continuing.
- [Inferior 1 (process 20982) exited normally]
- (gdb) q
从以上调试可以看出,string对象的内存结构和以下结构体的非常相似:
- typedef long int u32;
- struct String
- {
- char *data_ptr; // 指向字符数组的指针,在32位程序占用4个字节
- u32 length; // 字符数组的长度,在32位程序占用4个字节
- char data[16]; // 可以容纳15个字符的数组,占用16个字节
- };
1.string对象的1-4个字节是一个指向字符数据的指针。
2.string对象的5-8个字节是一个表示字符数据长度的整形数值。
3.string对象的9-24个字节的含义根据字符数据的长度发生变化。
- 如果string对象包含的字符数组长度小于16,则将字符数据保存在string对象本身所占用的内存中;以上述结构体String为例,将字符数据保存在data中。
- s15.data_ptr == &(s15.data[0]);
- 如果string对象包含的字符数组长度大于等于16,则其字符数据位于可执行文件的数据区或分配到堆内存中,而不是栈内存中;以上述结构体String为例,无法将字符数据保存在data字段中。
64位可执行程序string对象的内存分配
64位程序与32位程序非常相似,只不过64程序中,指针对象占用8字节内存;通过动态调试,发现内存分配与以下结构体非常相似:
- typedef long long int u64;
- struct String
- {
- char *data_ptr; // 指向字符数组的指针,在64位机器占用8个字节
- u64 length; // 字符数组的长度,在64位机器占用8个字节
- char data[16]; // 可以容纳15个字符的数组,占用16个字节
- };
以上内容是两年多前的学习笔记,最近在以下环境中进行测试,得到的结论与上述内容一致。
- 操作系统:Ubuntu 20.04。
- 调试软件:GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2。
- 编译工具:g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0。
本文转载自微信公众号「Golang In Memory」