我正在经历这个 QA ,据说
char
数组在使用字符串文字初始化时会导致两次内存分配,一个用于变量,另一个用于字符串文字。
我写了下面的程序来看看内存是如何分配的。
#include <stdio.h>
#include <string.h>
int main()
{
char a[] = "123454321";
printf("a =%p and &a = %p\n", a, &a);
for(int i = 0; i< strlen(a); i++)
printf("&a[%d] =%p and a[%d] = %c\n",i,&a[i],i,a[i]);
return 0;
}
输出为:
a =0x7ffdae87858e and &a = 0x7ffdae87858e
&a[0] =0x7ffdae87858e and a[0] = 1
&a[1] =0x7ffdae87858f and a[1] = 2
&a[2] =0x7ffdae878590 and a[2] = 3
&a[3] =0x7ffdae878591 and a[3] = 4
&a[4] =0x7ffdae878592 and a[4] = 5
&a[5] =0x7ffdae878593 and a[5] = 4
&a[6] =0x7ffdae878594 and a[6] = 3
&a[7] =0x7ffdae878595 and a[7] = 2
&a[8] =0x7ffdae878596 and a[8] = 1
从输出来看,我们似乎没有两个单独的内存位置用于数组和字符串文字。
如果我们对数组和字符串文字有单独的内存,有什么方法可以证明在这种情况下数组
a
和字符串文字是分开存储的?
char a[] = "123454321";
从技术上讲,字符串文字
"123454321"
不需要存储在任何地方。所需要的只是在输入 a[]
时用正确的值初始化 main
。无论是通过从某个静态只读内存位置复制字符串还是运行以其他方式填充字符串的代码来完成此操作,都不是标准所强制的。
就标准而言,编译器发出相当于以下内容的代码来初始化是完全可以接受的
a[]
:
char a[10];
for(int n = 0; n <= 4; n++)
a[n] = a[8-n] = '1' + n;
a[9] = '\0';
事实上,至少有一个编译器 (gcc) 通过自定义代码初始化
a[]
,而不是存储和复制文字字符串。
mov DWORD PTR [ebp-22], 875770417 ; = 0x34333231 = '1', '2', '3', '4'
mov DWORD PTR [ebp-18], 842216501 ; = 0x32333435 = '5`, '4', '3', '2'
mov WORD PTR [ebp-14], 49 ; = 0x31 = '1', '\0'
可以通过修改代码来证明:
int main()
{
for (int i = 0; i < 2; ++i)
{
char a[] = "123454321";
printf("a = %s\n", a);
a[3] = 'x';
a[5] = 'y';
printf("a = %s\n", a);
}
}
输出:
=123454321
a = 123x5y321
=123454321
a = 123x5y321
我们修改后得到了原来的字符串,所以原来的字符串一定是存储在我们修改的地方以外的地方。
您完全误解了问题和答案。问题是除了实际数组之外,initializer 字符串是否还消耗内存。现在的问题是,您无法观察初始化字符串。
就像有两张纸。衣柜里有一张用圆珠笔写的123454321。桌子上有一张——一开始是空的。然后有人过来,从衣柜里拿出那张纸,读上面的文字,然后用铅笔把它写在桌子上的纸上。然后把纸放回衣柜里。
现在你看着桌子上的那张纸,上面写着:“显然,文本 123454321 没有在这张纸上写两次,因此他们怎么说有两份副本?”
你无法证明有两个存储,因为你只有一个。
编译器发现您想要一个用一些字符和 ' ' 初始化的 char 数组,因此它会这样做。它不需要将字符串文字存储在其他地方。
由于这个原因,这将不会编译。
#include <stdio.h>
#include <string.h>
char *p = "123454321";
int main()
{
char a[] = p;
printf("a =%p and &a = %p\n", a, &a);
for(int i = 0; i< strlen(a); i++)
printf("&a[%d] =%p and a[%d] = %c\n",i,&a[i],i,a[i]);
return 0;
}
是的,您的字符串存储在两个位置 - 一个位置位于可执行文件的预初始化数据部分,然后当您的程序运行时,它被复制到程序的工作内存部分中的第二个位置。
注意 - 在 Linux 系统上,您可以在二进制文件上运行程序字符串,以找出隐藏在代码中的任何字符串。
% cc myprogram.c -o myprogram
% strings myprogram
可执行文件的内存布局由编译器构建系统明确定义,并通过在开头使用代码数字(顺便说一下,称为幻数)向操作系统指示。
典型的布局有:
您的编程代码(c、c++、fortran...)将转换为适合您要运行代码的机器的汇编语言,并存储在文本部分。编译时已知值的数据(例如“123454321”)被分配一个地址,并由编译器存储在初始化数据段中。执行期间创建的数据会在未初始化数据段中分配一个地址,并在启动程序时由操作系统的 exec 函数初始化为 0。一个称为块起始符号(BSS)的变量被分配给该未初始化数据开始的地址,并且操作系统使用它来知道从哪里开始写入零。接下来,堆是动态内存分配(malloc)获取内存的地方;当程序调用子例程为调用函数执行工作时,堆栈是保存程序状态的地方。
请参阅https://www.geeksforgeeks.org/memory-layout-of-c-program/(如果它仍然存在!)以获得更深入的解释。