开始
下面提到的变量均是在 main 函数中申请的变量。
1. 二维数组
首先讨论二维数组, 他在内存中的布局是最简单的, 考虑如下变量
char str[][5] = {
"jiao",
"_shi",
"_jie",
};
可以通过如下代码打印各个部分的地址
printf("str addr: %p\n\n", &str);
for (int i = 0; i < 3; i++)
{
printf("str[%d] addr: %p\t", i, &str[i]);
}
printf("\n\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("str[%d][%d] addr: %p\n", i, j, &str[i][j]);
}
printf("\n");
}
可以看到地址从str[0][0]
开始到结束是从低到高连续的。因为是在main函数中申请的
这个二维数组,因此它被存储在栈空间中(栈空间地址是高地址到低地址走的),存储布局
大致如下:
2. 指针的指针
使用如下代码申请指针的指针的空间:
char **d_ptr = (char **)malloc(sizeof(char *) * 3);
for (int i = 0; i < 3; i++)
{
d_ptr[i] = (char *)malloc(sizeof(char) * 5);
}
memcpy(d_ptr[0], "jiao", 5);
memcpy(d_ptr[1], "_shi", 5);
memcpy(d_ptr[2], "_jie", 5);
使用如下代码输出各个部分的地址:
printf("d_ptr addr: %p\n\n", &d_ptr);
for (int i = 0; i < 3; i++)
{
printf("d_ptr[%d] addr: %p\n", i, &d_ptr[i]);
}
printf("\n\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("d_ptr[%d][%d] addr: %p\n", i, j, &d_ptr[i][j]);
}
printf("\n");
}
可以看到只有d_ptr的地址是在栈空间中的, 其余部分均是在堆空间中.
因此指针的指针的内存布局如下:
3. 指针数组
对于指针数组情况比较复杂,首先需要清楚一个C程序在内存中的布局,大致布局可以参考下图
同时考虑以下简单的代码
char *str1 = "hello";
char str2[] = "hello";
其中 "hello"
被称作 string literal,根据 C 标准的规定其会被放到一个静态的
存储空间中,而 gcc 会将其放到 .rodata
段中,这个段没有出现在上方的图中但也是
Static Memory Layout 中的一部分, 而且该区域的数据是不可以修改的。
In translation phase 7, a byte or code of value zero is appended to each multibyte character sequence that results from a string literal or literals. The multibyte character sequence is then used to initialize an array of static storage duration and length just sufficient to contain the sequence. (C99 [6.4.5])
str1
被声明为一个指针,因此 str1
当中会存储一个地址而这个地址就是指向 "hello"
。
因此 str1
实际指向了一个不允许被修改的地址(比如 str1[0] = 'H'
),当尝试修改
该位置的地址是为发生 Segment Falut Error。
而当对 str2
做同样的事情时(str2[0] = 'H'
),却有不同的结果,你会发现修改成功了。
这是因为 str2
被声明为了一个数组,它的数据应该被放置在栈空间。当程序每次执行时,
都会将 .rodata
中的内容,复制到栈空间中,因此你修改它没有任何问题。
而向下面一样申请的 str
是一个指针数组,它是一个数组然后每个元素都是一个指针,
如果要声明数组指针要 char (*str)[N]
这样 str
就是一个指向数组大小为 N 的
数据的指针。
基于上面的讨论我们可以知道"jiao"
,"_shi"
和 _jie
,都是在 .rodata
中的,而 str
又是一个指针数组,因此 str[0]
,str[1]
和 str[2]
都是指向
了 .rodata
中的 string literal。
char *str[] = {
"jiao",
"_shi",
"_jie",
};
通过如下代码打印各个部分的地址:
printf("str addr: %p\n\n", &str);
for (int i = 0; i < 3; i++)
{
printf("str[%d] addr: %p\n", i, &str[i]);
}
printf("\n\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("str[%d][%d] addr: %p\n", i, j, &str[i][j]);
}
printf("\n");
}
可以看到 str[0]
,str[1]
,str[2]
地址空间连续(偏移了一个指针的大小),
但是和 str[0][0]
地址相差很多,而 str[0][0]
,str[0][1]
到 str[2][4]
地址空间都是连续的.
因此指针数组的内存布局如下:
读到这里,可以在返回去思考一下最开始提到的二维数组的内存布局,是不是会有不一样的想法。