首页 > 解决方案 > 没有定义的结构是什么意思?

问题描述

最近,我在系统中遇到了以下代码stdio.h

struct _IO_FILE_plus;
extern struct _IO_FILE_plus _IO_2_1_stdin_;
extern struct _IO_FILE_plus _IO_2_1_stdout_;
extern struct _IO_FILE_plus _IO_2_1_stderr_;

我习惯于看到指向像这样向前声明的结构的指针:extern struct _IO_FILE *stdin;,但是拥有一个裸结构似乎很奇怪,因为您不能使用该结构或将其传递给函数。这只是一个无操作吗?

标签: cstructforward-declaration

解决方案


代码struct _IO_FILE_plus;是名称的声明,_IO_FILE_plus因此如果编译器看到它在某个地方被使用,它就知道在某个时候会有一个实际描述其成员的定义。

extern修饰符表示命名的符号是存在于某个其他编译单元中的外部符号。代码如:

extern struct _IO_FILE_plus _IO_2_1_stdin_;

也是符号的声明,在这种情况下_IO_2_1_stdin_,告诉编译器该符号是在其他编译单元(文件)中定义并存在的外部符号,以及符号的类型是什么,在这种情况下是 a struct _IO_FILE_plus.

但是,通常struct在其他声明中使用声明通常会使用指向 的指针,struct因为 的大小struct及其布局不能仅由诸如 的声明来确定struct _IO_FILE_plus;

但是在这种情况下,由于它是外部的,除非源代码包含一些要求struct编译器可以使用的大小和布局的语句,否则以这种方式使用声明的符号是有效的。

因此,如果您有诸如这些陈述之类的来源:

struct _IO_FILE_plus *myIo = malloc(sizeof(struct _IO_FILE_plus));

struct _IO_FILE_plus myIo = _IO_2_1_stdin_;  // no pointers here, struct assignment

这些会产生错误,因为编译器需要定义struct _IO_FILE_plus来确定在这些语句中分配的结果sizeof()或要复制的内存量。struct

但是,如果您有如下声明:

struct _IO_FILE_plus *myIO = &_IO_2_1_stdin_;

这将编译,因为编译器只需要知道如何找到外部变量的地址并将该地址放入指针变量中。当应用程序被加载并设置为运行时,外部变量的地址由加载器固定。

如果外部不存在,那么链接时您将收到“未解析的外部符号”错误。

API 库示例

这可能有用的一种方法是,如果您有多个由代理对象表示的不同对象或设备,并且您有一个函数库,您希望允许人们为其中的函数选择目标对象或设备。

因此,您所做的是在您的库中将这些对象或代理对象公开为外部对象,但您仅通过提供声明来保持其内部的秘密。

然后在函数接口中,您需要一个指向要与函数一起使用的适当对象或代理对象的指针。

这种方法的好处是可以访问您的库内部的其他方可以提供额外的代理对象,这些代理对象可以与您的库一起使用,但可以使用他们自己的代理对象。

struct定义包含指向您的库将调用以执行第三方知道但您不必这样做的设备特定操作的挂钩函数的指针时,这尤其有效。钩子函数有一个定义的接口,带有一组预期的结果,如何完成取决于钩子函数的提供者。

所以库源文件:

struct _IO_FILE_plus {
    unsigned char  buffer[1024];
    int bufptr1;
    //  …  other struct member definitions
    int (*hookOne)(struct _IO_FILE_plus *obj);   // third party hook function pointer
    int (*hookTwo)(struct _IO_FILE_plus *obj);   // third party hook function pointer
};

struct _IO_FILE_plus _IO_2_1_stdin_ = { {0}, 0, …. };
struct _IO_FILE_plus _IO_2_1_stdout_ = { {0}, 0, …. };
struct _IO_FILE_plus _IO_2_1_stderr_ = { {0}, 0, …. };

int  funcOne (struct _IO_FILE_plus *obj, int aThing)
{
    int  iResult;

    if (obj->hookOne) iResult = obj->hookOne(obj);

    // do other funcOne() stuff using the object, obj, provided

    return iResult;
}


int  funcTwo (struct _IO_FILE_plus *obj, double aThing)
{
    int  iResult;

    if (obj->hookTwo) iResult = obj->hookTwo(obj);

    // do other funcTwo() stuff using the object, obj, provided

    return iResult;
}

库源文件编译得很好,因为编译器具有struct可用的完整定义。然后在库提供的头文件中,您有以下语句:

struct _IO_FILE_plus ;

extern struct _IO_FILE_plus _IO_2_1_stdin_ ;
extern struct _IO_FILE_plus _IO_2_1_stdout_ ;
extern struct _IO_FILE_plus _IO_2_1_stderr_ ;

extern int  funcOne (struct _IO_FILE_plus *obj, int aThing);
extern int  funcTwo (struct _IO_FILE_plus *obj, double aThing);

这些都有效,因为这些源语句都不需要struct编译器可以使用的实际定义。编译器只需要知道某处定义了这样的符号。

在使用这些的源文件中,您可以有如下语句:

int k = funcOne(&_IO_2_1_stdin_, 5);

同样,这只需要编译器知道符号存在,并且在某个时候该符号的地址将可用。

作为库设计的一部分,很可能有 C 预处理器宏用于进一步隐藏这些管道。所以你可能有宏,例如:

#define DO_FUNCONE(io,iVal)  funcOne(&(io), (iVal))

#define DO_FUNCONE_STDIN(iVal)  funcOne(&_IO_2_1_stdin_,(iVal))

#define IO_STDIN  (&_IO_2_1_stdin)

然而,像下面这样的语句不会编译,因为编译器将提供一个函数的副本,struct该函数接受外部的值而不是指向它的指针:

int k = doFuncOne (_IO_2_1_stdin_);  // compiler error. definition of struct _IO_FILE_plus not available

其中函数的函数定义doFuncOne()如下所示:

// compiler error. definition of struct _IO_FILE_plus not available
int doFuncOne (struct _IO_FILE_plus obj)  // notice this is struct and not pointer to struct
{
    // do some setup then call funcOne().
    return funcOne(&obj, 33);
}

但是,对函数接口的更改doFuncOne()将允许它编译:

// following would compile as only declaration is needed by the compiler.
int doFuncOne (struct _IO_FILE_plus *obj)  // notice this is now pointer to struct
{
    // do some setup then call funcOne().
    return funcOne(obj, 33);
}

库可以提供一个版本的函数funcOne(),比如funcOneStruct(),它允许一个参数struct而不是指向的指针,struct因为编译器struct在编译库的源文件时有一个可用的定义。但是,使用该库的人将无法使用该功能,因为该库的用户只有对struct他们可用的声明,而没有struct.

这样的函数可能对定义了可用的第三方开发人员很有struct用,他们也许可以克隆库提供的现有对象之一。


推荐阅读