在阅读了关于K& R书中结构的章节之后,我决定做一些测试来更好地理解它们,所以我写了这段代码:
#include <stdio.h>
#include <string.h>
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
答案 0 :(得分:6)
char c[20];
...
c = func("Another string").x;
这不是有效的C代码。不是C89,不是C99,不是C11。
显然,它会在gcc
模式下使用最新的4.8
版本-std=c89
进行编译,而无需对作业进行诊断(clang
发出诊断)。在C89模式下使用时,这是gcc
中的错误。
C90标准的相关引用:
6.2.2.1“可修改的左值是一个左值,它没有数组类型,没有不完整的类型,没有const限定类型。如果它是结构或联合。没有任何成员(包括。递归地,所有包含的结构或联合的任何成员)具有const限定类型。“
和
6.3.16“赋值运算符的左操作数应具有可修改的左值。”
6.3.16是一个约束,并且至少强制gcc
发出gcc
没有的诊断,所以这是一个错误。
答案 1 :(得分:2)
这是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说:
具有结构或联合类型的非左值表达式,其中 结构或联合包含一个数组类型的成员(包括, 递归地,所有包含的结构和联合的成员)指的是 具有自动存储持续时间和临时生存期的对象 它的生命周期从评估表达式及其初始值开始 value是表达式的值。它的生命终结了 对包含完整表达式或完整声明符的评估结束。 任何使用临时生命周期修改对象的尝试都会导致 未定义的行为。
脚注:
访问数组成员时隐式采用此类对象的地址。
所以这个窘境的C11解决方案是创建一个临时对象,这样数组到指针的转换实际上会产生一些有意义的地址(一个对象成员的元素)临时寿命。)
显然gcc中处理这种情况的代码并不完全正确。在C90模式下,它必须执行某些来解决该版本标准中的不一致问题。显然它将func().arr
视为非左值数组表达式(在C90规则下可能可以说是正确的) - 但是它错误地允许将数组值赋给数组对象。分配到数组对象的尝试,无论分配右侧的表达式是什么,都明显违反了C90 6.3.16.1中的约束部分,如果LHS不是算术,指针的左值,则需要进行诊断。结构或联合类型。目前尚不清楚(根据C90和C99规则)编译器是否必须诊断类似func().arr
的表达式,但它显然必须诊断尝试将该表达式分配给数组对象,无论是在C90,C99还是C11中。
为什么这个错误出现在C90模式,而它在C99模式下被正确诊断仍然有点神秘,因为据我所知,C90和C99之间标准的这个特定区域没有重大变化(临时生命仅在C11中引入。但由于这是一个错误,我不认为我们可以抱怨它出现的不一致。
解决方法:不要这样做。
答案 2 :(得分:1)
这一行
c = func("Another string").x;
将c
声明为
char c[20];
在任何版本的C中都不是有效的C.如果它在您的情况下“有效”,则它是编译器错误或相当奇怪的编译器扩展。
如果是strcpy
strcpy(c, func("Another string").x);
相关细节是func("Another string").x
子表达式的本质。在“经典”C89 / 90中,此子表达式不能进行数组到指针转换,因为在C89 / 90数组到指针转换中,仅应用于lvalue数组。同时,您的数组是一个右值,它无法转换为const char *
的第二个参数所期望的strcpy
类型。这正是错误消息告诉你的。
该语言的一部分在C99中已更改,允许对rvalue数组进行数组到指针的转换。所以在C99中,上面的strcpy
将被编译。
换句话说,如果编译器发出上述strcpy
的错误,它必须是旧的C89 / 90编译器(或者在严格的C89 / 90模式下运行的新C编译器)。您需要C99编译器来编译此类strcpy
调用。
答案 3 :(得分:0)
[编辑]回顾关于赋值的C11 6.3.2,LValue C
,因为它是一个数组,它是该数组的地址,成为存储赋值的位置(没有冲击)。这是函数的结果是表达式的值,子字段引用也是表达式的值。然后允许这个奇怪的代码,因为它简单地将表达式(20字节)的值分配给目标位置,这也是一个char [ 20。&c[0]
[Edit2]要点是func().x
的结果是一个值(表达式的值),这是匹配类型的合法分配 char[20]
在左侧。虽然c = c
在右侧(char [20])的c
失败,但是成为数组的地址而不是整个数组,因此不能分配给char[20]
。这太奇怪了。
[Edit3]此失败 gcc -std=c99.
我尝试了简化代码。注意函数func
返回一个结构。典型的编码鼓励返回一个指向结构的指针,而不是一些大坏字节集的整个副本。
ct = func("1 Another string")
看起来不错。一个结构被 en masse 复制到另一个结构。
ct.x = func("2 Another string").x
开始看起来很腥,但令人惊讶的是有效。我希望右半部分没问题,但是数组到数组的分配看起来不对。
c = func("3 Another string").x
就像以前一样。如果以前是好的,这也过得很快。有趣的是,如果c的大小为21,则编译失败。
注意:c = ct.x
无法编译。
#include <stdio.h>
#include <string.h>
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
答案 4 :(得分:0)
您的代码中有两个错误:
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;
}