初始化指针时的字符串文字与char数组

Eug*_*Sh. 19 c arrays gcc

灵感来自这个问题.

我们可以char用字符串文字初始化指针:

char *p = "ab";
Run Code Online (Sandbox Code Playgroud)

它完全没问题.人们可以认为它等同于以下内容:

char *p = {'a', 'b', '\0'};
Run Code Online (Sandbox Code Playgroud)

但显然事实并非如此.而且不仅因为字符串文字存储在只读内存中,而且即使通过字符串文字具有一种类型的char数组,并且初始化程序{...}具有char数组类型,两个声明的处理方式也不同,因为编译器是发出警告:

警告:标量初始化程序中的多余元素

在第二种情况下.这种行为的解释是什么?

更新:

此外,在后一种情况下,指针p将具有0x61(第一个数组元素的值'a')而不是存储器位置的值,使得编辑器在警告时仅采用初始化器的第一个元素并将其分配给p.

Min*_*s97 8

我觉得你很困惑,因为char *p = "ab";char p[] = "ab";有相似的语义,但意义不同.

我认为,后一种情况下(char p[] = "ab";)最好是作为一个视为缩写符号char p[] = {'a', 'b', '\0'};(初始化与由初始值设定确定的尺寸的阵列).实际上,在这种情况下,你可以说"ab"并没有真正用作字符串文字.

但是,前一种情况(char *p = "ab";)的不同之处在于它只是初始化指针p以指向只读字符串文字 的第一个元素"ab".

我希望你看到差异.虽然char p[] = "ab";可以像你所描述的那样表示初始化,但char *p = "ab";不是,因为指针是,而不是数组,并且用数组初始化器初始化它们会做一些完全不同的事情(即0x61在你的情况下给它们第一个元素的值).

简而言之,char如果适合这样做,C编译器只用数组初始值设定项"替换"字符串文字,即它用于初始化char数组.


Yu *_*Hao 7

第二个例子在语法上是不正确的.在C中,{'a', 'b', '\0'}可用于初始化数组,但不能用于指针.

相反,您可以使用C99复合文字(在某些编译器中也可用作扩展名,例如GCC),如下所示:

char *p = (char []){'a', 'b', '\0'};
Run Code Online (Sandbox Code Playgroud)

请注意,它更强大,因为初始化程序不一定是以null结尾的.


Dra*_*rgy 7

字符串文字在C中具有"神奇"状态.它们与其他任何东西都不同.要理解为什么,在内存管理方面考虑这一点是有用的.例如,问自己,"字符串文字在哪里存储在内存中?什么时候从内存中释放出来?" 事情将开始变得有意义.

它们与数字文字不同,后者可以轻松转换为机器指令.有关简化示例,请执行以下操作:

int x = 123;

...可能在机器级别转换为类似的东西:

mov ecx, 123

当我们做类似的事情:

const char* str = "hello";

......我们现在陷入两难境地:

mov ecx, ???

对于多字节,可变长度字符串实际上是什么,硬件并不一定是本地的理解.它主要了解位,字节和数字,并且具有用于存储这些内容的寄存器,但字符串是包含多个内存块的内存块.

因此编译器必须生成将字符串的内存块存储在某处的指令,因此它们通常在编译代码时生成指令,以便将该字符串存储在全局可访问的位置(通常是只读内存段或数据段).它们还可以合并多个文本字符串,这些字符串相同,存储在同一内存区域中以避免冗余.现在它可以生成一个mov/load指令来将地址加载到文字字符串,然后您可以通过指针间接处理它.

我们可能遇到的另一种情况是:

static const char* some_global_ptr = "blah";

int main()
{
    if (...)
    {
        const char* ptr = "hello";
        ...
        some_global_ptr = ptr;
    }
    printf("%s\n", some_global_ptr);
}
Run Code Online (Sandbox Code Playgroud)

自然ptr地超出了范围,但我们需要文字字符串的内存来徘徊,以使该程序具有明确定义的行为.因此,字面字符串不仅可以转换为全局可访问内存块的地址,而且只要您的二进制文件/程序被加载/运行,它们也不会被释放,因此您不必担心它们的内存管理.[编辑:排除潜在的优化:对于C程序员,我们永远不必担心文字字符串的内存管理,所以效果就像它总是在那里].

现在关于字符数组,文字字符串本身不一定是字符数组.在软件中没有任何一点我们可以将它们捕获到一个数组r值,它可以给我们分配的字节数sizeof.我们只能指向记忆char*/const char*

这段代码实际上为我们提供了这样一个数组的句柄而不涉及指针:

char str[] = "hello";
Run Code Online (Sandbox Code Playgroud)

有趣的事情发生在这里.生产编译器可能会应用各种优化,但在基本级别排除这些优化,这样的代码可能会创建两个独立的内存块.

第一个块将在程序的持续时间内持久化,并将包含该文字字符串"hello".第二个块将用于该实际str数组,并且它不一定是持久的.如果我们在函数中编写这样的代码,它将在堆栈上分配内存,将该文字字符串复制到堆栈,并在str超出范围时从堆栈中释放内存.地址str不会与文字字符串匹配,换句话说.

最后,当我们写这样的东西时:

char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};
Run Code Online (Sandbox Code Playgroud)

......它不一定等同,因为这里没有文字字符串.当然,允许优化器执行各种操作,但在这种情况下,我们可能只需创建一个内存块(在堆栈上分配,如果我们在函数内部则从堆栈中释放)并带有指令将您指定的所有这些数字(字符)移动到堆栈.

因此,就软件的逻辑而言,虽然我们有效地实现了与先前版本相同的效果,但是当我们没有指定文字字符串时,我们实际上做了一些微妙的不同.同样,优化器可以识别何时执行不同的操作可以具有相同的逻辑效果,因此它们可能在这里变得有趣并且使这两者在机器指令方面实际上是相同的.但除此之外,这与我们编写的代码略有不同.

最后但并非最不重要的是,当我们使用像{...}这样的初始化器时,编译器希望您将它分配给聚合l值,该聚合具有在事情超出范围时在某个时刻分配和释放的内存.这就是为什么你在尝试将这样的东西分配给标量(单个指针)时会出现错误的原因.


Gop*_*opi 6

从C99我们有

字符串文字是用双引号括起来的零个或多个多字节字符的序列

所以在第二个定义中没有字符串文字,因为它不在双引号内.指针应该在写入内容之前分配内存,或者如果你想通过初始化列表

char p[] = {'a','b','\0'};
Run Code Online (Sandbox Code Playgroud)

是你想要的.基本上两者都是不同的声明.