8

C99 标准是否允许写入复合文字(结构)?似乎它不提供对文字字符串的写入。我问这个是因为它在C Programming: A Modern Approach, 2nd Edition on page 406 中说。

问:允许指向复合文字的指针似乎可以修改文字。是这样吗?

答:是的。复合文字是可以修改的左值。

但是,我不太明白它是如何工作的,以及它如何与你当然无法修改的字符串文字一起工作。

char *foo = "foo bar";
struct bar { char *a; int g; };
struct bar *baz = &(struct bar){.a = "foo bar", .g = 5};

int main () {
  // Segfaults
  // (baz->a)[0] = 'X';
  // printf( "%s", baz->a );

  // Segfaults
  // foo[0] = 'a';
  // printf("%s", foo);

  baz->g = 9;
  printf("%d", baz->g);

  return 0;
}

您可以在我的段错误列表中看到,写入会baz->a导致段错误。但是,写入baz->g不会。为什么其中一个会导致段错误而不是另一个?结构文字与字符串文字有何不同?为什么不将结构文字也放入内存的只读部分,并且为这两者定义或未定义行为(标准问题)?

4

3 回答 3

7

首先要做的事情:您的结构文字有一个指针成员初始化为字符串文字。结构本身的成员是可写的,包括指针成员。只有字符串文字的内容是不可写的。

字符串文字从一开始就是该语言的一部分,而结构文字(官方称为复合文字)是相对较新的添加,截至 C99。到那时,存在许多将字符串文字放在只读内存中的实现,尤其是在具有少量 RAM 的嵌入式系统上。到那时,标准的设计者可以选择要求将字符串文字移动到可写的位置,允许结构文字是只读的,或者保持原样。这三种解决方案都不是理想的,所以看起来他们走的是阻力最小的道路,并让一切保持原样。

C99 标准是否允许写入复合文字(结构)?

C99 标准没有明确禁止写入使用复合文字初始化的数据对象。这与字符串文字不同,后者的修改被标准视为未定义的行为。

于 2018-08-23T23:18:57.837 回答
3

该标准本质上为字符串文字和在const函数体外部使用的具有 - 限定类型的复合文字定义了相同的特征。

寿命

  • 字符串文字:始终是静态的。

    §6.4.5p6 在翻译阶段 7 中,将一个字节或值为零的代码附加到由一个或多个字符串文字产生的每个多字节字符序列。然后使用多字节字符序列来初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。

  • 复合文字:如果在函数体内使用,则为自动,否则为静态。

    §6.5.2.5p5 复合文字的值是由初始化列表初始化的未命名对象的值。如果复合文字出现在函数体之外,则该对象具有静态存储持续时间;否则,它具有与封闭块关联的自动存储持续时间。

可能共享

  • 字符串文字const- 限定的复合文字都可以共享。你应该为这种可能性做好准备,但不能指望它发生。

§6.4.5p7 未指定 [为字符串文字创建的数组] 是否不同,只要它们的元素具有适当的值。

§6.5.2.5p7 字符串文字和具有 const 限定类型的复合文字不需要指定不同的对象。

可变性

  • 修改字符串文字或 - 限定的const复合文字是未定义的行为。确实,尝试修改任何const-qualified 对象都是未定义的行为,尽管标准的措辞可能会令人毛骨悚然。

§6.4.5p7 如果程序尝试修改[包含字符串文字的数组],则行为未定义。

§6.7.3p6 如果尝试const通过使用具有非 const 限定类型的左值来修改使用限定类型定义的对象,则行为未定义。

  • 可以自由修改非 const 限定的复合文字。我对此没有引用,但在我看来,没有明确禁止修改这一事实似乎是确定的。没有必要明确说可变对象可能会发生变异。

函数体内复合文字的生命周期是自动的,这一事实可能会导致一些细微的错误:

/* This is fine */
const char* foo(void) {
  return "abcde";
}

/* This is not OK */
const int* oops(void) {
  return (const int[]){1, 2, 3, 4, 5};
;
于 2018-08-24T01:29:46.047 回答
0

C99 标准是否允许写入复合文字(结构)?

如果您的意思是修改复合文字的元素,则通过写入复合文字,那么是的,如果它不是只读复合文字,它会这样做。

C99-6.5.2.5:

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

这意味着,复合文字是像数组一样的值,并且可以修改复合文字的元素,就像您可以修改聚合类型一样。例如

// 1
((int []) {1,2,3})[0] = 100;  // OK

// 2
(char[]){"Hello World"}[0] = 'Y';  // OK. This is not a string literal!

// 3
char* str = (char[]){"Hello World"};
*str = 'Y';  // OK. Writing to a compound literal via pointer. 

// 4
(const float []){1e0, 1e1, 1e2}[0] = 1e7 // ERROR. Read only compound literal 

在您的代码中,您尝试做的是修改一个复合文字元素,该元素指向一个不可修改的字符串文字。如果该元素使用复合文字初始化,则可以对其进行修改。

struct bar *baz = &(struct bar){.a = (char[]){"foo bar"}, .g = 5};

这个片段现在可以工作了

Segfaults
(baz->a)[0] = 'X';
printf( "%s", baz->a );

进一步的标准还在上面提到的同一部分中给出了一个示例,并区分了字符串文字、复合文字和只读复合文字:

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

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

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

于 2018-08-24T10:58:19.007 回答