合法的数组赋值。 可能吗?

在阅读了关于K&R书中结构的章节之后,我决定做一些测试来更好地理解它们,所以我写了这段代码:

#include  #include  struct test func(char *c); struct test { int i ; int j ; char x[20]; }; main(void) { char c[20]; struct {int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b; c = func("Another string").x; printf("%s\n" , c); } struct test func(char *c) { struct test temp; strcpy(temp.x , c); return temp; } 

我的问题是:为什么c = func("Another string").x; 工作(我知道这是非法的,但为什么它有效)? 起初我使用strcpy()编写它(因为这似乎是最合乎逻辑的事情)但我一直有这个错误:

 structest.c: In function 'main': structest.c:16:2: error: invalid use of non-lvalue array 

  char c[20]; ... c = func("Another string").x; 

这不是有效的C代码。 不是在C89,不在C99,不在C11。

显然它在-std=c89模式下使用最新的gcc版本4.8进行编译,但没有诊断分配( clang发出诊断)。 在C89模式下使用时,这是gcc的错误。

C90标准的相关报价:

6.2.2.1“可修改的左值是一个左值,它没有数组类型,没有不完整的类型,没有const限定类型。如果是结构或联合。没有任何成员(包括。递归地,所有包含结构或联合的任何成员)具有const限定类型。“

6.3.16“赋值运算符应具有可修改的左值作为其左操作数。”

6.3.16是一个约束并且至少对gcc强制发出gcc没有的诊断,所以这是一个bug。

这是gcc中的一个错误。

在大多数情况下,数组类型的表达式被隐式转换为指向数组对象的第一个元素的指针。 例外情况是表达式是(a)一元sizeof运算符的操作数; (b)当它是一元&经营者的操作数时; (c)当初始化器中的字符串文字用于初始化数组对象时。 这些例外都不适用于此。

这种描述中存在各种各样的漏洞。 它假定,对于任何给定的数组类型表达式,都有一个它引用的数组对象 (即,所有数组表达式都是左值)。 这几乎是正确的,但是你遇到了一个角落的情况。 函数可以返回结构类型的结果。 该结果只是结构类型的值,而不是指任何对象。 (这同样适用于工会,但我会忽略它。)

这个:

 struct foo { int n; }; struct foo func(void) { struct foo result = { 42 }; return result; } 

原则上与此没有什么不同:

 int func(void) { int result = 42; return result; } 

在这两种情况下,都会返回result 的副本; 在对象result不再存在之后,可以使用该值。

但是如果返回的struct有一个数组类型的成员,那么你有一个非左值结构成员的数组 – 这意味着你可以拥有一个非左值数组表达式。

在C90和C99中,尝试引用这样的数组(除非它是sizeof的操作数)具有未定义的行为 – 不是因为标准这样说,而是因为它没有定义行为。

 struct weird { int arr[10]; }; struct weird func(void) { struct weird result = { 0 }; return result; } 

调用func()会给你一个类型为struct weird的表达式; 这没有什么不对,例如,您可以将它分配给类型为struct weird的对象。 但如果你写这样的东西:

 (void)func().arr; 

然后标准表示数组表达式func().arr被转换为指向它引用的不存在对象的第一个元素的指针。 这不仅仅是遗漏未定义行为的情况(标准明确指出仍然是未定义的行为)。 这是标准中的错误。 在任何情况下,标准都无法定义行为。

在2011年ISO C标准(C11)中,委员会终于认识到了这个角落的情况,并创造了临时寿命的概念。 N1570 6.2.4p8说:

具有结构或联合类型的非左值表达式,其中结构或联合包含具有数组类型的成员(包括,递归地,所有包含的结构和联合的成员)是指具有自动存储持续时间和临时生命周期的对象。表达式被计算,其初始值是表达式的值。 当包含完整表达式或完整声明符的评估结束时,它的生命周期结束。 任何使用临时生命周期修改对象的尝试都会导致未定义的行为。

用脚注:

访问数组成员时隐式采用此类对象的地址。

所以这个窘境的C11解决方案是创建一个临时对象,这样数组到指针的转换实际上会产生一些有意义的地址(一个具有临时生命周期的对象成员的元素)。

显然,gcc中处理这种情况的代码并不完全正确。 在C90模式下,它必须做一些事情来解决该版本标准的不一致性。 显然它将func().arr视为非左值数组表达式(在C90规则下可能被certificate是正确的) – 但是它错误地允许将该数组值赋给数组对象。 分配到数组对象的尝试,无论分配右侧的表达式是什么,都明显违反了C90 6.3.16.1中的约束部分,如果LHS不是算术,指针的左值,则需要进行诊断。结构或联合类型。 目前尚不清楚(根据C90和C99规则)编译器是否必须诊断像func().arr这样的表达式,但它显然必须诊断尝试将该表达式分配给数组对象,无论是在C90,C99还是C11中。

在C90模式下出现这个错误的原因仍然有点神秘,因为据我所知,在C90和C99之间标准的这个特定区域没有发生重大变化( 暂时生命周期才被引入)在C11)。 但由于它是一个错误,我不认为我们可以抱怨它出现不一致的太多。

解决方法:不要那样做。

这条线

 c = func("Another string").x; 

c被声明为

 char c[20]; 

在任何版本的C中都不是有效的C.如果它在您的情况下“有效”,则它是编译器错误或相当奇怪的编译器扩展。

如果是strcpy

 strcpy(c, func("Another string").x); 

相关细节是func("Another string").x的性质func("Another string").x 。x子表达式。 在“经典”C89 / 90中,此子表达式不能进行数组到指针的转换,因为在C89 / 90中,数组到指针的转换应用于左值数组 。 同时,你的数组是一个rvalue,它不能转换为strcpy的第二个参数所期望的const char *类型。 这正是错误消息告诉你的。

该部分语言在C99中已更改,允许rvalue数组的数组到指针转换。 所以在C99中,上面的strcpy会编译。

换句话说,如果编译器为上述strcpy发出错误,它必须是旧的C89 / 90编译器(或者在严格的C89 / 90模式下运行的新C编译器)。 你需要C99编译器来编译这样的strcpy调用。

代码中有两个错误:

 main(void) { char c[20]; struct { int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b; c = func("Another string").x;// here of course number one printf("%s\n" , c); } struct test func(char *c) { struct test temp; strcpy(temp.x , c); return temp; // here is number two , when the func finished the memory of function func was freed, temp is freed also. } 

写下这样的代码:

 main(void) { struct test *c; struct { int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b; c = func("Another string"); printf("%s\n" , c->x); free(c); //free memory } struct test * func(char *c) { struct test *temp = malloc(sizeof(struct test));//alloc memory strcpy(temp->x , c); return temp; } 

OP:但它为什么有效?
因为显然在复制结构的字段时,只有类型和大小很重要。
我会搜索doc来支持这个。

[编辑]回顾关于赋值的C11 6.3.2,LValue C ,因为它是一个数组,它是该数组的地址,成为存储赋值的位置(没有冲击)。 这是函数的结果是表达式值,子字段引用也是表达式 。 然后允许这个奇怪的代码,因为它简单地将表达式 (20字节)分配给目标位置&c[0] ,这也是char [20]。

[Edit2]要点是func().x是一个值( 表达式的值 ),并且是左侧匹配类型 char[20]的合法赋值。 而c = c对于右侧的c (char [20])失败,而成为数组的地址而不是整个数组,因此不能赋予char[20] 。 这太奇怪了。

[Edit3]这与gcc -std=c99. 失败 gcc -std=c99.

我试过一个简化的代码。 注意函数func返回一个结构。 典型的编码鼓励返回指向结构的指针,而不是一些大的坏字节集的整个副本。

ct = func("1 Another string")看起来很好。 一个结构被集体复制到另一个结构。

ct.x = func("2 Another string").x开始看起来很腥,但令人惊讶的是有效。 我希望右半部分没问题,但是将数组赋给数组看起来是错误的。

c = func("3 Another string").x 。x就像前一个一样。 如果以前是好的,这也过得很快。 有趣的是,如果c大小为21,则编译失败。

注意: c = ct.x无法编译。

 #include  #include  struct test { int i; char x[20]; }; struct test func(const char *c) { struct test temp; strcpy(temp.x, c); return temp; } int main(void) { char c[20]; c[1] = '\0'; struct test ct; ct = func("1 Another string"); printf("%s\n" , ct.x); ct.x = func("2 Another string").x; printf("%s\n" , ct.x); c = func("3 Another string").x; printf("%s\n" , c); return 0; } 

 1 Another string 2 Another string 3 Another string