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

灵感来自这个问题 。

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

 char *p = "ab"; 

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

 char *p = {'a', 'b', '\0'}; 

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

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

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

更新:

此外,在后一种情况下,指针p将具有值0x61 (第一个数组元素'a' )而不是存储器位置,这样编译器就像警告一样,仅占用初始化器的第一个元素,将其分配给p

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

我相信后一种情况( char p[] = "ab"; )最好被视为char p[] = {'a', 'b', '\0'};简写符号 char p[] = {'a', 'b', '\0'}; (初始化具有初始化程序确定的大小的数组)。 实际上,在这种情况下,你可以说"ab"并没有真正用作字符串文字

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

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

简而言之,如果适合这样做,C编译器只用char数组初始化器“替换”字符串文字,即它用于初始化char数组。

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

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

 char *p = (char []){'a', 'b', '\0'}; 

请注意,它更强大,因为初始化程序不一定是以空值终止的。

字符串文字在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); } 

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

现在关于字符数组,文字字符串本身不一定是字符数组。 在软件中,我们无法将它们捕获到一个数组r值,该值可以为我们提供使用sizeof分配的字节数。 我们只能通过char*/const char*指向内存

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

 char str[] = "hello"; 

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

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

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

 char str[] = {'h', 'e', 'l', 'l', 'o', '\0'}; 

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

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

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

从C99我们有

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

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

 char p[] = {'a','b','\0'}; 

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