【C字符串与内存布局探索实验】
C字符串与内存布局探索实验
一、C字符串
int main(){char a[10] = "asd\0asd";char b[5] = "abce";b[4] = 'e';cout << b << endl;cout << a << endl;cout << "&a is " << &a << "\n&b is " << &b << endl;return 0;
}/*输出结果为:
abceeasd
asd
&a is 0xd3623ffaf6
&b is 0xd3623ffaf1*/
首先其中字符数组a在中间塞了一个’\0’,对于C风格字符串,一个字符串的结束标志就是‘\0’,所以在输出a时,就会只输出asd。
然后就是数组b,对于字符串"abce"其中最末尾的不是e而是’\0’,字符串常量"abcd"会被编译器编成c风格字符串"abcd\0";
然后人为地将b数组最后一个’\0’替换掉,破坏掉这样的C风格,在进行cout << b时,它就不会被识别出停止的位置,从而一直往后输出下去
然后这里因为内存布局的原因,a和b是相邻的,且a在b的后面,输出完之后b就接着输出a,然后遇到a中\0,停止输出。
好的,现在再来看看这样一个案例:
void testString(){char a[5] = "abcd";a[4] = 'e';cout << a << endl;cout << "testString() endl" << endl;cout << "in function &a is " << &a << endl;
}/*
输出结果为:
abcdep?.
*/
运行上面的函数,由于该函数中局部变量只有它一个,没有其他的\0和靠的近,这一次就没有正常地结束输出,而是发生了一些不可控制的。
二、内存布局
参考连接C/C++:内存分配,详解内存分布(P:图解及代码示例)_全局变量内存分布图-CSDN博客

以32位机为例,上图中从上往下,地址一次减小,对于内核空间最低地址是0xC000 0000 , 到最高地址是0xffff ffff 共1个GB 的空间,代码段下方还存在一个 reserved / 零页 地址为 0x0000 0000。
其中最核心的一点就是栈是从高地址往低地址增长,上面的代码例子中,main函数中a先被压入栈,然后b被压入栈,这样相邻就刚好有一个\0
而光对void testString()函数来做,内部就没有专门的\0。
最后再来贴上各个段详解(Linux/x86视角)
- 代码段(.text)
只读、可执行。多次运行同一可执行文件时,物理页共享。 - 只读数据段(.rodata)
字符串常量、const 全局变量。试图写入会触发段错误(SIGSEGV)。 - 已初始化数据段(.data)
全局int g = 5;存在这里;程序启动时从可执行文件拷初值。 - BSS 段(.bss)
全局int h;/static int k;存在这里;可执行文件里只记录大小,不占磁盘空间,内核在加载时一次性清零。 - 堆(heap)
malloc/calloc/new从这儿拿内存。
向高地址增长:通过调整程序中断点 brk 或匿名 mmap 实现。
需要手动free/delete,否则泄漏。 - 内存映射段(mmap)
动态库、POSIX 共享内存、大页、mmap文件映射都落在这一带。
地址比堆高,但比栈低;增长方向与堆无关,由内核随便找一块空闲区。 - 栈(stack)
每个线程独立一块,默认 8 MB(ulimit -s 可改)。
向低地址增长:函数调用一次,栈指针减一帧;返回时加回。
存放:- 局部变量(含
char buf[5]这种数组) - 函数参数、返回值
- 保存的寄存器、返回地址
若无限递归或超大局部数组,会踩到最下方“守护页”→ 触发 stack overflow(Linux 发 SIGSEGV)。
- 局部变量(含
