结构vs字符串文字?只读还是读写?

时间:2018-08-23 22:41:43

标签: c c99 string-literals compound-literals

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

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

     

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

但是,我不太了解它是如何工作的,以及如何对您肯定无法修改的字符串文字进行工作。

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不会。为什么其中一个会引起段错误而不是另一个?结构文字与字符串文字有何不同?为什么不将struct-literal也放入内存的只读部分,并且为这两种行为(标准问题)都定义还是未定义?

3 个答案:

答案 0 :(得分:7)

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

自开始以来,

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

  

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

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

答案 1 :(得分:3)

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

终身

  • 字符串文字:始终为静态。

      

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

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

      

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

可能共享

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

第6.4.5p7节[为字符串文字创建的数组]是否唯一,只要它们的元素具有适当的值,则不确定。

     

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

可变性

  • 修改字符串文字 const限定的复合文字是未定义的行为。确实,尝试修改任何const合格的对象都是未定义的行为,尽管标准的措词可能会发散。
  

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

     

§6.7.3p6如果试图通过使用具有非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};
;

答案 2 :(得分:0)

  

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

如果您要修改复合文字的元素,那么通过写复合文字,那么可以,如果它不是只读的复合文字,则可以。

C99-6.5.2.5:

  

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

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

// 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类型数组,但不必进行修改; 后两个出现在函数主体中时具有自动存储期限这两个中的第一个是可修改的