合法的数组赋值。 可能吗?
在阅读了关于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字节)分配给目标位置 ,这也是char [20]。 &c[0]
[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