使用字符串文字进行数组初始化的内存

问题描述

我正在浏览这个 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],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和字符串字面量是分开存储的?

克隆链接https://onlinegdb.com/HkJhdSHyd

解决方法

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','2'
mov     WORD  PTR [ebp-14],49           ; =  0x31        =  '1','\0'
,

完全误解了问题和答案。问题是关于 initializer 字符串除了实际数组之外是否还消耗内存。现在的问题是,你不能观察初始化字符串

就像有两张纸。柜子里有一张用圆珠笔写的123454321。桌子上的一个 - 最初是空的。然后另一个人来了,从壁橱里拿出床单,读上面的文字,然后用铅笔写在桌子上的床单上。然后把纸放回柜子里。

现在你正在看桌子上的那张纸说:“很明显,文本 123454321 没有在这张纸上写过两次,所以他们怎么说有两份?”

,

你不能证明有两个存储,因为你只有一个。

编译器认为你想要一个用一些字符和 '\0' 初始化的 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],a[i]);
    
    return 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);
    }
}

输出:

a = 123454321
a = 123x5y321
a = 123454321
a = 123x5y321

修改后我们得到了原来的字符串,所以原来的字符串一定是存放在我们修改的地方以外的地方。