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

时间:2015-05-29 15:26:44

标签: c arrays gcc

this question的启发。

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

char *p = "ab";

这很好。 人们可以认为它等同于以下内容:

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

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

  

警告:标量初始值设定项中的多余元素

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

更新

此外,在后一种情况下,指针char将具有值p(第一个数组元素0x61的值)而不是内存位置,这样编译器,正如所警告的那样,只取初始化器的第一个元素并将其分配给'a'

4 个答案:

答案 0 :(得分:7)

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

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

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

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

答案 1 :(得分:7)

我认为您感到困惑,因为char *p = "ab";char p[] = "ab";具有相似的语义,但意义不同。

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

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

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

长话短说,仅限C编译器"替换"如果适合这样做,则使用0x61数组初始值设定项的字符串文字,即它用于初始化char数组。

答案 2 :(得分: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);
}

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

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

指向内存

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

char str[] = "hello";

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

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

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

str

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

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

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

答案 3 :(得分:6)

从C99我们有

  

字符串文字是包含在其中的零个或多个多字节字符的序列       双引号

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

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

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