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

时间:2013-08-23 21:24:04

标签: c arrays gcc lvalue c89

在阅读了关于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

5 个答案:

答案 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)

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

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

[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;
}