第3篇:C++的string内部原理

本文假定你对C/C++的string语法已经有基本的了解。

C ++的string对象实质上就是一个容器,其内部有一个c_str方法能够返回一个指向的实质存储字符串副本的数据成员。即通过string::c_str()配合printf函数可以获取的字符串副本的内存地址。

栈中的string的内存分配

首先,我们来看看如下代码的关于string对象内部的栈中内存分配,不少C++读物强力建议在C++开发中使用标准库的string对象,而非C版本的char*指针和char[]数组。但没有详细告诉读者为什么?string对象底层都做了些什么,因此理解string内部实现原理,对于你后续使用string类实现各种字符串操作的算法非常有必要,以下代码,是前一篇文章代码的深入的演示版本

首先我们在全局作用域重载了operator new和operator delete的函数原型,内部分别用C版本的malloc和free函数,目的在于:显式展示给读者,你在使用string过程中,它已经在底层自动完成了所有的内存分配和内存释放。实际开发过程不建议这样重载operator newoperator delete

show_str()函数是用于打印传入参数string对象str内部的字符串的地址和函数内部的局部变量的string对象tmp的内部字符串的地址。

下面是调用函数


输出结果:
首先继续进行下文之前,需要说明的是Linux下的x86_64版本的GCC/G++编译器默认情况下(编译时没有附带 -O 优化选项),仍然按照x86平台的过程调用约定组织程序栈,下文编译时使用的是默认设置。

从上面程序输出看来,在每次调用show_str()函数输出的内存地址看来,string对象内部持有字符串副本的内存分配都发生在程序栈帧中,有一些有趣的分析。

  • main函数我们知道string对象内部持有字符串副本的地址是"0x7ffc5b140990",输出的参数地址跟main函数中的变量you是一致的,因为我们show_str()的参数类型是const string&即使用了引用传参,我们这里避免了字符串的拷贝.
  • 每次string类型的局部变量赋值操作,string对象内部自动执行字符串拷贝,从每次打印的tmp程序地址可以得知。

匿名字符串字面量

我们第二次调用show_str()函数时,你们是否思考过如下两个问题。

  1. 0x7ffc5b1409b0从那里冒出来的,为何跟main函数的you不是一致的?
  2. 我们又没有定义新的string类型的局部变量,0x7ffc5b1409b0这个地址为什么后面会出现了两次?

首先,解答第一个疑问,从内存寻址的角度分析,一个变量必定对应于一个内存地址,也就是0x7ffc5b1409b0这个地址必定存在一个变量与之对应,但第二次调用show_str()函数,我们没有向其传入任何定义的string类型的局部变量,只是直接传入一个字符串字面量。关键就是在这里,当我们直接向show_str传入一个字符串字面量之前,C++编译器会隐式创建一个临时变量,我们假设变量的名称是任意的x。隐式的临时变量它的内部字符串副本的地址自然就指向0x7ffc5b1409b0这个地址,我们第二次调用show_str的代码,即如下代码所示

int main(void){
     std::string you="Hello,World!!";
     show_str(you);
     .....

     //show_str("Hello,World!!")会等价于如下代码
      std::string& x="Hello,World!!";//隐式创建
      show_str(x);
     ....
}

接下来回答第二个问题就非常简单,由于C++已经隐式地定义了

std::string& x="Hello,World!!";

那么后续调用任意的被调用函数的传参类型只要是const string&,那么传入同一个匿名的字符串字面量。自然打印的都是同一个隐式局部变量的内部字符串副本的地址。

另外比较蹊跷的是tmp每次调用show_str输出的地址是相同的,因为我们这里陆续调用的了相同show_str函数,那么show_str栈帧结构基本上一样的,如果你调用不同尺寸的函数,输出结果就会不一样。

堆中的string的内存分配

这次,我稍微做一下改动,现在我们在main中传入一个比之前更长的尺寸为33字节的字符串字面量,如下图

对应的输出

这次string对象的内存分配已经发生变化,show_str()函数中的他们的内部数据成员分别指向各自堆中分配的内存块,的字符副本分别存储这些堆中的内存块。如上图输出都分别调用了void* operator new(size_t)的重载版本。

到这里你就应该要思考两个问题

  • 为什么在处理“Hello,Word!!”只在栈中进行内存分配?
  • 为什么在处理“Hello,My name is peter!!”这样的字符串,就会在堆中进行内存分配?

没错,答案就是字符串字面量的长度决定的。这个我在前一编《对[C/C++]指针与字符串的总结》已经提到过,但当时我没有指出,触发string对象内部的new操作的准确阀值是多少。请看如下表

string对象内部约定:

  • 只要传入的字符串字面量小于上表的阀值,string内部实现在栈中分配内存,有个很骚的名字小型字符串优化(Small String Optimisation)。
  • 只要大于上述C++编译器指定阀值,string对象内部会隐式执行new操作在堆中根据指定的字符串尺寸分配初次内存
  • 如果后续任何字符串的push_back操作,string会根据“double方案”的内存分配方式对堆内存执行扩容操作,见前文《对[C/C++]指针与字符串的总结》
  • 还有根据RAII的约定,C++编译器会对string对象在其调用函数的生命周期结束之时自动执行垃圾回收。(见上图的输出)。

建议:到这里,如果还没搞懂如下代码背后的内存含义的话,建议还是去补补栈和堆内存管理的知识,再去深入了解string对象。这样会让你少走很多弯路。

string s=new string(....)

void my_app(const string &s){
      string tmp=s;
}

我们从内存地址的角度,分析了string对象在栈中和堆中的内存分配细节。从这篇文章你应该知道,在C++中掌握内存分析方法是多么地重要,本篇用到了以前我所写随笔的程序栈和堆内存管理的知识。

扩展阅读,如果关注我的读者应该了解我写软文的套路是一环扣一环的,可能在说string的话题,然后有跳到程序栈,这就是所谓的知识碎片整理。

后记

了解string对象的行为之后,接下来我们如何考虑使用什么方法来避免字符串频繁的拷贝,有些经验的“老油条”应该都领略过了const string&这类参数类型声明并不能从根本上解决问题(上例子的程序输出已经隐藏地说明了这一点)。于是C++17就有了string_view这个标准库的扩展,这个扩展极大地解决了string拷贝的空间成本和时间成本问题。我们后续文章会继续新的话题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342