【C++】內存中的字符串
前文 內存中的字符串類型 學習研究了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」