第1篇:对C/C++指针与字符串的总结

字符串是以ASCII字符并且以NUL(即'\0')结尾 表示的字符序列

C中的字符串机制

以字符串字面量定义字符串时会将其分配到字面量池中,这个内存区域通常保存组成字符串的字符序列,该内存区域通常被认为是全局/静态的。字符字面量在池中通常只有一份副本并且是只读的,这样可以减少程序的内存占用率。

首先,理解C的字符串运行机制,下面一段简单的代码可以得出关于字符串的不同结论

#include <stdio.h>

char *g="Hello";

int main(int argc, char const *argv[])
{
    char s[]="Hello";
    char *c="Hello";
    printf("字符指针c的内存地址:%p\n",&c);
    printf("数组s的内存地址:%p\n",&s);
    printf("字符指针g的内存地址:%p\n",&g);
    printf("字符指针c指向Hello的的内存地址: %p\n",&c[0]);
    printf("字符指针g指向Hello的的内存地址: %p\n",&g[0]);
    printf("变量s数组的Hello副本的内存地址:%p\n",&s[0]);
    printf("Hello字面量的尺寸 %lu\n",sizeof("Hello"));
    
    return 0;
}

从示例代码中,全局区声明并以字符串字面量初始化了字符指针g,g的内存地址是0x108993018,这个地址位于全局数据区内,而指针g它指向的“Hello”字面量地址是0x108992e9e.这个地址位于字面量池内. 同时,我们也从main函数内部定义了两个局部变量字符指针变量c,c指针指向的字面量的内存地址和全局字符指针指向的"Hello"字面量是一样的,那么这里可以得出以下关于声明char指针并以字面量初始化的时候,可以得出以下特性。

  1. 将同样一份字符串字面量的地址直接赋给不同的字符指针,不会产生额外字符串的副本,这些字符指针指向同一份字符串字面量。

另外,在main函数内部的数组s,它内部持有字符串数组的地址和字面量池的字符串的地址是不一样的。换句话说,

2.以字符串数组初始化字符串字面量会在对应的函数栈内生成另外一份的字面量副本。char s[]="HELLO"等价于strcpY(s,"HELLO")

3.再次,如果你在全局区以字符串数组初始化会,会在全局数据区产生同样的字符串副本。

在不同的作用域字符串初始化-内存分布

以下是运行的随机结果:


屏幕快照 2019-08-15 下午4.09.34.png

C++中的字符串的机制

string是一个包含多个数据成员的字符串对象,这里只是补充《C++ Primer》这本书关于string对象内部机制没详细阐述,做个笔录,这里不会罗列所有的string对象的api,不熟悉的同学可以看《C++ Primer》相关内容。

  • c_str()是一个指针指向动态分配空间中的字符数组中第一个字符的地址。

  • size()包含字符串的长度。

  • capacity()包含当前可能存储在数组中的有效字符数(额外的NUL字符不计算在内)。

  • malloc内存分配
    一个涉及到malloc内存管理程序的实现需要3个字段,每个字段都是三个不同指针:

    • 指向已分配内存的指针;
    • 字符串的逻辑大小(该字符串末尾是NUL字符);
    • 分配的内存大小(必须大于或等于逻辑大小);


      C++中的string对象内部基本原理
#include <iostream>
#include <cstdlib>
#include <string>

using std::string;
using std::cout;
using std::endl;

//重写string类的new操作符,添加一个可以识别malloc操作的输出
void* operator new(std::size_t n){
    cout<<"分配"<<n<<"字节"<<endl;
    return malloc(n);
}

void operator delete(void *p) throw(){
    free(p);
}

int main(int argc, char const *argv[])
{
    string s("HELLO"); //直接初始化
    cout<<"初始化时的状态:"<<endl;
    cout<<"sizeof:"<<sizeof(s)<<endl;
    cout<<"size:"<<s.size()<<endl;
    cout<<"分配的内存尺寸(capacity):"<<s.capacity()<<endl;

    for(size_t i=6;i<24;++i){
        s.push_back('+');
        cout<<i<<":"<<s<<endl;
    }

    cout<<"push_back('+')之后的内存尺寸是"<<endl;
    cout<<"sizeof:"<<sizeof(s)<<endl;
    cout<<"size:"<<s.size()<<endl;
    cout<<"分配的内存尺寸(capacity):"<<s.capacity()<<endl;

运行结果


屏幕快照 2019-08-16 上午5.50.35.png

在for循环前:我们通过调用string三个提到三个基本方法,起初分配的内存是24字节,但允许容纳有效的字符是22个,为什么呢?

因为HELLO后的第6个位置(索引5)包含一个NUL字符(即'\0'),而malloc初始化分配的24个字节里的最后一个字节位置也包含一个界定符,我认为也是NUL字符。有效字符的长度是不将NUL字符计算在内的,所有capacity方法才显示22.初始化的状态如下图所示(重申:另外不同的计算机硬件,不同的OS和编译器环境,malloc初始化时,申请的内存空间是不一样的):

c++ string对象初始化的状态

在for循环过程中,我们向malloc剩余的备用空间塞入'+'字符(覆盖了索引5的NUL字符算起),直到索引22的位置也就是最后一个NUL字符的前一个字节位置),string对象内部就触发malloc申请扩容的操作,而申请的内存总数是之前的2倍。
屏幕快照 2019-08-16 上午6.23.08.png

上面的代码,for的循环的长度加大,总之大于24的任意一个正整数,多实验几次。会得到如下基本特征。

  • 当size()方法得出字符数达到capacity()得出的有效容纳的字符数,string对象内部就会触发malloc的内存重新扩容。
  • 每次malloc扩容后的申请的内存空间尺寸是之前的内存空间尺寸的2倍。

基于这两点,C++的string对象内部封装了涉及malloc操作的指针操作,这大大减轻了程序猿对指针操作不当,带来程序不可预测的可能性。同时使用双倍扩容的方法也最大限度减少了因字符串长度后续增加的频繁malloc操作带来的系统消耗。但是它是以内存空间为代价的,堆里和字面量池总有着相同一份相同的字符串副本。

后记:
理解C++的string对象底层其实就是malloc动态分配堆内存的机制之后,后面关于字符串的拼接,复制,查找等基本原理,你心里就有底了.要彻底理解字符串的话,推荐阅读《深入理解c指针》这本书,里面关于字符串的描述比《征服C指针》讲得更加深入。

额外问题:

调试C++程序的时候,有时你需要查看string对象内部的指针,虽然c_str()可以输出字符串首个字符的内存地址,但标准库cout操作会自动对c_str()的内部指针做解引操作,因此cout不能直接得出字符串的地址,而是打印对应的字符串。不需要折腾cout,直接简单的粗暴方法是用C的printf函数

#include <stdio.h>
#include <string>
int main(int argc, char const *argv[])
{
    string s("HELLO"); //直接初始化
    printf("变量的s内存地址:%p\n",&s);
    printf("变量的s2内存地址:%p\n",&s2);
    printf("字符串对象s的地址:%p\n",&s[0]);
    printf("字符串对象s2的地址:%p\n",s2.c_str());

    printf("字符串字面量:%p\n",&"HELLO");
    return 0;
}

输出

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

推荐阅读更多精彩内容