结构vs字符串文字? 只读与读写?
C99标准是否允许写复合文字(结构)? 它似乎不提供写文字字符串。 我问这个是因为它在C编程:现代方法,第 406页第2版中说 。
问:允许指向复合文字的指针似乎可以修改文字。 是这样的吗?
答:是的。 复合文字是可以修改的左值。
但是,我不知道它是如何工作的,以及它如何与字符串文字一起工作,你当然无法修改。
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-literals与string-literals有何不同? 为什么struct-literals也不会被放入内存的只读部分,并且这两个行为是否定义或未定义(标准问题)?
首先要做的是:你的struct literal有一个初始化为字符串文字的指针成员。 结构本身的成员是可写的,包括指针成员。 只有字符串文字的内容不可写。
字符串文字从一开始就是语言的一部分,而结构文字(官方称为复合文字 )是一个相对较新的添加,从C99开始。 到那时,存在许多实现将字符串文字放在只读存储器中,特别是在具有少量RAM的嵌入式系统上。 到那时,标准的设计者可以选择将字符串文字移动到可写位置,从而允许结构文字是只读的,或者保持原样。 这三种解决方案都不是理想的,所以看起来它们走的是阻力最小的路径,并且一切都按原样保留。
C99标准是否允许写复合文字(结构)?
C99标准没有明确禁止写入使用复合文字初始化的数据对象。 这与字符串文字不同,字符串文字的修改被标准视为未定义的行为。
该标准基本上定义了与字符串文字相同的特征,并使用在函数体外部使用的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
-qualified类型定义的对象,则行为是未定义的。
- 可以自由修改非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}; ;
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的类型数组,但不需要是可修改的; 当它们出现在函数体内时,最后两个具有自动存储持续时间 , 并且这两个中的第一个是可修改的 。