没有定义的结构意味着什么?

Jos*_*son 5 c struct forward-declaration

最近,我在系统中遇到了以下代码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_;
Run Code Online (Sandbox Code Playgroud)

我曾经看到过这样的指针,这些指针是这样向前声明的:extern struct _IO_FILE *stdin;,但是拥有裸露的结构似乎很奇怪,因为您无法使用该结构或将其传递给函数。这只是一个禁忌症吗?

Ric*_*ers 5

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

所述extern改性剂指示名为符号是存在于某些其它编译单元的外部符号。代码如:

extern struct _IO_FILE_plus _IO_2_1_stdin_;
Run Code Online (Sandbox Code Playgroud)

也是符号的声明,在这种情况下_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
Run Code Online (Sandbox Code Playgroud)

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

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

struct _IO_FILE_plus *myIO = &_IO_2_1_stdin_;
Run Code Online (Sandbox Code Playgroud)

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

如果外部不存在,则链接时会出现“无法解析的外部符号”错误。

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;
}
Run Code Online (Sandbox Code Playgroud)

库源文件可以很好地编译,因为编译器具有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);
Run Code Online (Sandbox Code Playgroud)

所有这些都起作用,因为这些源语句都不要求struct编译器可以使用的实际定义。编译器仅需要知道在某处定义了此类符号。

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

int k = funcOne(&_IO_2_1_stdin_, 5);
Run Code Online (Sandbox Code Playgroud)

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

作为库设计的一部分,很可能会使用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)
Run Code Online (Sandbox Code Playgroud)

但是,如下所示的语句将无法编译,因为编译器将提供struct函数的副本,该副本采用外部值而不是指向外部函数的指针:

int k = doFuncOne (_IO_2_1_stdin_);  // compiler error. definition of struct _IO_FILE_plus not available
Run Code Online (Sandbox Code Playgroud)

该函数的函数定义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);
}
Run Code Online (Sandbox Code Playgroud)

但是,对函数接口的更改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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • @JoshuaNelson欢迎来到C!大声笑。其实不是那么奇怪。有一个很好的逻辑。对于某些类型的利基应用程序,它可能非常有用。不透明的指针就是这样一回事。 (2认同)