【C++】?jī)?nèi)存中的字符串
前文 內(nèi)存中的字符串類型 學(xué)習(xí)研究了Go的字符串在內(nèi)存中的結(jié)構(gòu)和數(shù)據(jù)類型。
文本是兩年多前的一篇學(xué)習(xí)筆記,研究的是C++字符串在內(nèi)存中的結(jié)構(gòu)。
環(huán)境
- 1. 操作系統(tǒng):Ubuntu 16.04。
- 2. 調(diào)試軟件: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類的定義通過泛型編程技術(shù)實(shí)現(xiàn),詳細(xì)定義請(qǐng)參考/usr/include/c++/5/bits/basic_string.h頭文件,看起來非常復(fù)雜,具體實(shí)現(xiàn)此處并不關(guān)心,不再討論。
測(cè)試string對(duì)象占用內(nèi)存空間
通過以下代碼可以測(cè)試string類對(duì)象占用內(nèi)存空間情況。
- // demo.cpp
- #include <string>
- #include <iostream>
- int main(int argc, char const *argv[])
- {
- using namespace std;
- string s15(15, 'a'); // 字符串長(zhǎng)度15
- string s16(16, 'x'); // 字符串長(zhǎng)度16
- cout << "sizeof(string) = " << sizeof(string) << endl;
- cout << "sizeof(s15) = " << sizeof(s15) << endl;
- cout << "sizeof(s16) = " << sizeof(s16) << endl;
- return 0;
- }
因?yàn)?2位和64位可執(zhí)行程序不同,以下將分別編譯測(cè)試。
將以上代碼編譯成32位可執(zhí)行程序并執(zhí)行,結(jié)果如下:
- $ g++ -m32 -g demo.cpp
- $ ./a.out
- sizeof(string) = 24
- sizeof(s15) = 24
- sizeof(s16) = 24
從以上輸出結(jié)果,可以十分確定string類對(duì)象在內(nèi)存中占用24個(gè)字節(jié)。
將以上代碼編譯成64位可執(zhí)行程序并執(zhí)行,結(jié)果如下:
- $ g++ -m64 -g demo.cpp
- $ ./a.out
- sizeof(string) = 32
- sizeof(s15) = 32
- sizeof(s16) = 32
從以上輸出結(jié)果,可以十分確定string類對(duì)象在內(nèi)存中占用32個(gè)字節(jié)。
32位可執(zhí)行程序string對(duì)象的內(nèi)存分配
為了查看內(nèi)存分配,需要用到動(dòng)態(tài)調(diào)試工具,此處使用gdb,并在源碼16行設(shè)置斷點(diǎn)。
調(diào)試過程中,打印main方法的棧數(shù)據(jù),以及string對(duì)象及相關(guān)數(shù)據(jù)的內(nèi)存,可以清晰看到string對(duì)象數(shù)據(jù)的內(nèi)存占用情況。
- $ 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進(jìn)制格式打印24個(gè)寬度為4字節(jié)的main函數(shù)堆棧數(shù)據(jù)(共96個(gè)字節(jié))
- 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的內(nèi)存地址
- 0xffffcdec: 0xffffcdf4
- (gdb) x /6xw 0xffffcdec // 打印string對(duì)象s15占用的24個(gè)字節(jié)內(nèi)存數(shù)據(jù)
- 0xffffcdec: 0xffffcdf4 0x0000000f 0x61616161 0x61616161
- 0xffffcdfc: 0x61616161 0x00616161
- (gdb) x /s 0xffffcdf4 // string對(duì)象s15的1-4個(gè)字節(jié)是一個(gè)指向字符數(shù)據(jù)的指針
- 0xffffcdf4: 'a' <repeats 15 times>
- (gdb) x /16x 0xffffcdf4 // string對(duì)象s15的9-24個(gè)字節(jié)是代表數(shù)據(jù)的字符數(shù)組
- 0xffffcdf4: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x61
- 0xffffcdfc: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x00
- (gdb) x /wx &s16 // 打印變量s16的內(nèi)存地址
- 0xffffce04: 0x0804fa10
- (gdb) x /6xw 0xffffce04 // 打印string對(duì)象s16占用的24個(gè)字節(jié)內(nèi)存數(shù)據(jù)
- 0xffffce04: 0x0804fa10 0x00000010 0x00000010 0x00000001
- 0xffffce14: 0xffffced4 0xffffcedc
- (gdb) x /s 0x0804fa10 // string對(duì)象s16的1-4個(gè)字節(jié)是一個(gè)指向字符數(shù)據(jù)的指針
- 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
從以上調(diào)試可以看出,string對(duì)象的內(nèi)存結(jié)構(gòu)和以下結(jié)構(gòu)體的非常相似:
- typedef long int u32;
- struct String
- {
- char *data_ptr; // 指向字符數(shù)組的指針,在32位程序占用4個(gè)字節(jié)
- u32 length; // 字符數(shù)組的長(zhǎng)度,在32位程序占用4個(gè)字節(jié)
- char data[16]; // 可以容納15個(gè)字符的數(shù)組,占用16個(gè)字節(jié)
- };
1.string對(duì)象的1-4個(gè)字節(jié)是一個(gè)指向字符數(shù)據(jù)的指針。
2.string對(duì)象的5-8個(gè)字節(jié)是一個(gè)表示字符數(shù)據(jù)長(zhǎng)度的整形數(shù)值。
3.string對(duì)象的9-24個(gè)字節(jié)的含義根據(jù)字符數(shù)據(jù)的長(zhǎng)度發(fā)生變化。
- 如果string對(duì)象包含的字符數(shù)組長(zhǎng)度小于16,則將字符數(shù)據(jù)保存在string對(duì)象本身所占用的內(nèi)存中;以上述結(jié)構(gòu)體String為例,將字符數(shù)據(jù)保存在data中。
- s15.data_ptr == &(s15.data[0]);
- 如果string對(duì)象包含的字符數(shù)組長(zhǎng)度大于等于16,則其字符數(shù)據(jù)位于可執(zhí)行文件的數(shù)據(jù)區(qū)或分配到堆內(nèi)存中,而不是棧內(nèi)存中;以上述結(jié)構(gòu)體String為例,無法將字符數(shù)據(jù)保存在data字段中。
64位可執(zhí)行程序string對(duì)象的內(nèi)存分配
64位程序與32位程序非常相似,只不過64程序中,指針對(duì)象占用8字節(jié)內(nèi)存;通過動(dòng)態(tài)調(diào)試,發(fā)現(xiàn)內(nèi)存分配與以下結(jié)構(gòu)體非常相似:
- typedef long long int u64;
- struct String
- {
- char *data_ptr; // 指向字符數(shù)組的指針,在64位機(jī)器占用8個(gè)字節(jié)
- u64 length; // 字符數(shù)組的長(zhǎng)度,在64位機(jī)器占用8個(gè)字節(jié)
- char data[16]; // 可以容納15個(gè)字符的數(shù)組,占用16個(gè)字節(jié)
- };
以上內(nèi)容是兩年多前的學(xué)習(xí)筆記,最近在以下環(huán)境中進(jìn)行測(cè)試,得到的結(jié)論與上述內(nèi)容一致。
- 操作系統(tǒng):Ubuntu 20.04。
- 調(diào)試軟件:GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2。
- 編譯工具:g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0。
本文轉(zhuǎn)載自微信公眾號(hào)「Golang In Memory」