8

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

5 回答 5

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

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

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

C90标准的相关引用:

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

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

6.3.16 是一个约束,至少强制 forgcc发出一个gcc没有的诊断,所以这是一个错误。

于 2013-08-23T21:47:18.383 回答
2

这是 gcc 中的一个错误。

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

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

这:

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

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

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

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

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

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

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

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

(void)func().arr;

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

在 2011 ISO C 标准 (C11) 中,委员会终于承认了这种极端情况,并创造了临时寿命的概念。 N1570 6.2.4p8 说:

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

带脚注:

当访问数组成员时,会隐式获取此类对象的地址。

因此,C11 解决这个难题的方法是创建一个临时对象,以便数组到指针的转换实际上会产生有意义的地址(具有临时生命周期的对象成员的元素)。

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

为什么这个错误出现在 C90 模式下而在 C99 模式下被正确诊断,这仍然有点神秘,因为据我所知,在 C90 和 C99 之间标准的这个特定区域没有显着变化(仅引入了临时生命周期在 C11 中)。但由于这是一个错误,我认为我们不能过多抱怨它出现的不一致。

解决方法:不要那样做。

于 2013-10-03T18:29:01.280 回答
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 中,数组到指针的转换仅适用于左值数组。同时,您的数组是一个右值,它不能转换为const char *第二个参数所期望的类型strcpy。这正是错误消息告诉您的内容。

这部分语言在 C99 中进行了更改,也允许右值数组进行数组到指针的转换。因此,在 C99 中,上述strcpy内容将编译。

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

于 2013-08-23T23:44:59.310 回答
0

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

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

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

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

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

ct = func("1 Another string")看起来不错。一个结构被大量复制到另一个结构。

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
于 2013-08-23T22:45:43.010 回答
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;
}
于 2013-08-24T00:05:58.390 回答