【问题标题】:Why are compound literals in C modifiable为什么 C 中的复合文字是可修改的
【发布时间】:2016-04-17 11:55:53
【问题描述】:

人们通常会将“不可修改”与字面量相关联

char* str = "Hello World!";
*str = 'B';  // Bus Error!

但是,当使用复合文字时,我很快发现它们是完全可修改的(查看生成的机器代码,您会看到它们被压入堆栈):

char* str = (char[]){"Hello World"};
*str = 'B';  // A-Okay!

我正在使用clang-703.0.29 进行编译。这两个示例不应该生成完全相同的机器代码吗?如果复合文字是可修改的,它真的是文字吗?

编辑:一个更短的例子是:

"Hello World"[0] = 'B';  // Bus Error!
(char[]){"Hello World"}[0] = 'B';  // Okay!

【问题讨论】:

  • 我什至不确定它是 UB,我从未真正看过官方的 language standard,但它在第 12 点的 6.5.2.5 中说,它说 (char[]){"abc"} 旨在可以修改。
  • 请注意,上面的示例实际上并未展示 lvalue-literal 行为(更清晰的示例类似于 (int){1} = 2;) - 您的两个 sn-ps 之间的主要区别在于第一个你有一个真正的字符串文字,而在第二个中你用字符串文字初始化一个本地数组 - 如果你只是修改第一个来读取char str[] = ...,你会有相同的行为。
  • @Leushenko 我是吗?在复合文字的情况下,我用指向它的第一个字符的指针初始化 str。当我说char str[] = … 时,我初始化了一个不可修改的字符文字并将其内容复制到堆栈上的数组str 中。
  • @hgiesel:你说得对,它实际上是一个“匿名对象”。您可以使用 const 限定符明确告诉编译器您的意图。请注意,在 C 语言中,程序员有责任不违反此约定。即使对于字符串文字,也不能保证写入将工作(并且C明确允许将其作为实现扩展)。因此,没有收到错误并不意味着它是已定义的行为。如有疑问,请阅读标准。
  • @hgiesel:没有。1)C 语言甚至不强制使用堆栈(也不是堆,顺便说一句。)并且有些实现不强制。 2)在某处分配了一个对象,加上初始化它的字符串文字。但是应该有相同的对象分配两次吗?您使用字符串的示例不是一个好示例,因为您可以直接使用文字,但仍然是有效的。

标签: c c99 literals lvalue compound-literals


【解决方案1】:

复合文字是左值,其元素的值是可修改的。如果是

char* str = (char[]){"Hello World"};
*str = 'B';  // A-Okay!  

您正在修改合法的复合文字。

C11-§6.5.2.5/4:

如果类型名称指定一个未知大小的数组,则大小由 6.7.9 中指定的初始化列表确定,复合字面量的类型是完整数组类型的类型 .否则(当类型名称指定对象类型时),类型 复合字面量是由类型名称指定的字面量。 无论哪种情况,结果都是左值

可以看出复合字面量的类型是完整的数组类型,是左值,因此它不像字符串字面量

那样是可修改的

标准中也提到了

§6.5.2.5/7:

字符串字面量和具有 const 限定类型的复合字面量不需要指定不同的对象。101

进一步说:

11 示例 4 可以通过以下结构指定只读复合文字:

(const float []){1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6}   

12 示例 5 以下三个表达式具有不同的含义:

"/tmp/fileXXXXXX"
(char []){"/tmp/fileXXXXXX"}
(const char []){"/tmp/fileXXXXXX"}

第一个总是静态存储时长,类型数组char,但不需要修改;最后两个在函数体内出现时具有自动存储持续时间,其中的第一个 二是可修改的

13 示例 6 与字符串文字一样,const 限定的复合文字可以放入只读内存,甚至可以共享。例如,

(const char []){"abc"} == "abc"

如果字面量的存储是共享的,则可能会产生 1。

【讨论】:

  • 请注意,这个问题是用 C99 标记的
  • @sunqingyao;是的。但是对于这种情况,规则几乎与 C11 相同。
  • 我觉得令人讨厌的是(据我所知)没有用于 const-static 复合文字的语法,因为字符串文字几乎不是唯一一种静态数据,通常会有一个单点使用。
【解决方案2】:

复合字面量语法是一种简写表达式,相当于带有初始化器的局部声明,后跟对由此声明的未命名对象的引用:

char *str = (char[]){ "Hello World" };

相当于:

char __unnamed__[] = { "Hello world" };
char *str = __unnamed__;

__unnamed__具有自动存储功能,定义为可修改,可以通过初始化指向它的指针str进行修改。

char *str = "Hello World!"; 的情况下,str 指向的对象不应该被修改。事实上,试图修改它有未定义的行为。

C 标准可以将此类字符串文字定义为具有类型 const char[] 而不是 char[],但这会在遗留代码中产生许多警告和错误。

但建议将标志传递给编译器以使此类字符串文字隐式const 并使整个项目const 正确,即:将所有不用于修改其对象的指针参数定义为@987654331 @。对于gccclang,命令行选项为-Wwrite-strings。我还强烈建议使用-Wall -W -Werror 启用更多警告并使其致命。

【讨论】:

  • 不幸的是,对于我正在编写的代码,不,“复合文字语法是等同于本地声明的简写表达式”是不正确的。例如,您可以在当前范围内执行 <type> foo[<n>]; 并获取 <n> * sizeof(<type>) 字节的未初始化堆栈内存,但没有办法拥有仅分配的未初始化复合文字,(<type> [<n>]) {} 是非标准的,大多数编译器会将其用于是{0}。如果有办法,请告诉我。这将非常有用。
  • @user426:这是一个不同的问题。顺便说一句,你从我的回答中引用的内容是不完整的:我写了 复合文字语法是一个简写表达式,相当于一个本地声明 带有初始化器 [...]。如果你想分配一些堆栈未初始化的空间,你可以在可用的系统上使用alloca()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-11
  • 1970-01-01
  • 1970-01-01
  • 2021-08-12
  • 1970-01-01
相关资源
最近更新 更多