【问题标题】:Declaration of struct twice结构声明两次
【发布时间】:2018-04-03 13:59:55
【问题描述】:

我多次遇到过这样的代码,但似乎无法理解其背后的想法或必要性。我指的是为什么有些代码声明了一个所述名称的结构然后他们使用 typedef 然后再次声明该结构并且只添加该结构的内容?下面是一个例子:

struct foo;
typedef struct foo *foobar;

struct foo {
    void *data;
    foobar example[];
};

这个声明是不是有点多余,不能是这样吗?

typedef struct foo *foobar;

struct foo {
    void *data;
    foobar example[];
};

【问题讨论】:

  • 您是否尝试编译您的备选方案中的一个或两个?他们都工作了吗?
  • @Secernere 我不相信你。你的两个选择都是错误的。
  • 第一种选择有什么问题?第二个是错误编译器在我编译时从头文件中读取了替代项。
  • @Secernere 他们都尝试在定义之前将foobar用作类型。
  • 好的,最后一次编辑完全改变了您的问题,使所有答案无效。叹息。

标签: c struct


【解决方案1】:

您需要先定义 typedef 才能引用它。 (此外,有一种观点不鼓励使用指针类型定义,但这是严格的约定。)

通常,如果结构本身包含指向自身其他实例的指针,或者更一般地说是相互引用的结构集,则会看到结构的“前向”类型定义。例如:

typedef struct foo_s foo;
struct foo_s {
    int val;
    foo *next;
};

当然,您可以始终在结构定义中使用结构标记,即struct foo_s,但如果希望始终使用typedef foo,则需要在定义结构之前对其进行定义。

【讨论】:

  • 为什么“更常见”? OP 的问题正是关于这种情况:包含指向自身的指针的结构。
  • @melpomene 好的,我改写了。
【解决方案2】:

它们不一样。您的第一个建议,

struct foo {
    void *data;
    foobar example[];
};

typedef struct foo *foobar;

产量:

error: unknown type name 'foobar'
     foobar example[];
     ^

你的第二个,

typedef struct foo {
    void *data;
    foobar example[];
} *foobar;

产量:

error: unknown type name 'foobar'
     foobar example[];
     ^

您必须在定义之前声明。原来的方式是可行的,因为两者都

struct foo;

typedef struct foo *foobar;

是不是定义的声明。需要使用这种“非定义声明”来使相互递归的名称引用起作用。

正如 cmets 中所指出的(感谢 @melpomene),struct foo; 声明实际上不是必需的。

【讨论】:

  • 原始版本有效,因为typedef struct foo *foobar; 出现在结构定义之前。 struct foo; 行实际上是多余的。
【解决方案3】:

冗长的符号很重要的一次是序列在函数内部的位置,并且在函数外部也定义了一个类型struct foo。然后普通的struct foo; 行会影响功能外定义。如果不包括该行,则最终可能会在看起来相同的结构中使用不兼容的指针类型,两者都称为foobar,从而导致可怕的混乱。 (有关更多示例,另请参阅 Which part of the C standard allows this code to compile?Does the C standard consider that there are one or two 'struct uperms_entry' types in this header?。)

什么?你被我说的糊涂了?好的;这里有一个例子。请注意,这是人为的;在函数中重新定义结构类型是不好的风格(问题的例子取决于你这样做)。

#include <stdlib.h>

struct foo;      // This line is optional; it is harmless if omitted
typedef struct foo *foobar;

struct foo
{
    void *data;
    foobar example[];  // Flexible array member; array of pointers.
};

extern void some_function(void);

void some_function(void)
{
    struct foo;     // This line is crucial!
    typedef struct foo *foobar;

    struct foo
    {
        foobar *array[3];
        void *pointer;
    };
    foobar x = calloc(sizeof(*x), 1);
    foobar y = malloc(sizeof(*y));
    y->array[0] = x->array[1];
    y->array[1] = x->array[2];
    y->array[2] = x->array[0];
    y->pointer  = x->array[1];

    free(x);
    free(y);
}

使用函数中的struct foo; 行,代码编译干净:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
>     -Wstrict-prototypes -pedantic -c st97.c
$

函数内的struct foo; 行表示“忘记任何以前的struct foo 类型;在此范围内有一个新类型”。当然,健忘只会持续范围。

在函数中注释掉struct foo;行,代码编译失败:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
>     -Wstrict-prototypes -pedantic -c st97.c
st97.c: In function ‘some_function’:
st97.c:28:6: error: ‘struct foo’ has no member named ‘array’
     y->array[0] = x->array[1];
      ^~
st97.c:28:20: error: ‘struct foo’ has no member named ‘array’
     y->array[0] = x->array[1];
                    ^~
st97.c:29:6: error: ‘struct foo’ has no member named ‘array’
     y->array[1] = x->array[2];
      ^~
st97.c:29:20: error: ‘struct foo’ has no member named ‘array’
     y->array[1] = x->array[2];
                    ^~
st97.c:30:6: error: ‘struct foo’ has no member named ‘array’
     y->array[2] = x->array[0];
      ^~
st97.c:30:20: error: ‘struct foo’ has no member named ‘array’
     y->array[2] = x->array[0];
                    ^~
st97.c:31:6: error: ‘struct foo’ has no member named ‘pointer’
     y->pointer  = x->array[1];
      ^~
st97.c:31:20: error: ‘struct foo’ has no member named ‘array’
     y->pointer  = x->array[1];
                    ^~
$

在函数内部定义类型foobar时,名​​称struct foo指的是函数外部定义的类型,因此该结构包含指向外部struct foo指针的指针数组,并且该结构有成员 dataexample,而不是成员 arraypointer

正如我所说,这是令人震惊的风格。所以,你说“如果我在函数的结构定义中使用struct foo 会怎样:

#include <stdlib.h>

struct foo;
typedef struct foo *foobar;

struct foo
{
    void *data;
    foobar example[];  // Flexible array member; array of pointers.
};

extern void some_function(void);

void some_function(void)
{
    //struct foo;
    typedef struct foo *foobar;

    struct foo
    {
        struct foo *array[3];
        void *pointer;
    };
    foobar x = calloc(sizeof(*x), 1);
    foobar y = malloc(sizeof(*y));
    y->array[0] = x->array[1];
    y->array[1] = x->array[2];
    y->array[2] = x->array[0];
    y->pointer  = x->array[1];

    free(x);
    free(y);
}

这仍然是一个问题。内部struct foo 直到} 才完成,因此struct foo *example[3] 仍然指代外部struct foo — 您会收到相同的错误消息。

简短的道德是“不要在函数内部重新定义结构类型”。

如果必须这样做,请注意它只能在函数中真正使用;很难将它传递给其他函数 — 并非完全不可能,但您确实必须知道自己在做什么。

更长的道德是“偶尔,在可疑的情况下,struct foo; 行本身可能是至关重要的”。

请注意,即使结构体定义实际上相同,但从技术上讲,它们是不同的类型(尽管两者都称为struct foo)。这让每个人都感到困惑——请不要这样做!实际上,让我们更加迫切:

别这样!

【讨论】: