没有定义的结构是什么意思?
What does it mean to have a struct without a definition?
最近,我 运行 在我的系统中检查了以下代码 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;
,但是有一个裸结构似乎很奇怪,因为你不能使用该结构或将它传递给函数.这只是空操作吗?
代码 struct _IO_FILE_plus;
是名称 _IO_FILE_plus
的声明,因此如果编译器看到它在某个地方被使用,它就知道在某个地方会有一个实际描述的定义它的成员。
extern
修饰符表示命名的符号是存在于其他编译单元中的外部符号。代码如:
extern struct _IO_FILE_plus _IO_2_1_stdin_;
也是符号的声明,在本例中为_IO_2_1_stdin_
,告诉编译器该符号是一个外部符号,它被定义并存在于其他某个编译单元(文件)中,它的类型是什么在这种情况下,符号是 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_;
这会编译,因为编译器只需要知道如何找到外部变量的地址并将该地址放入指针变量中。外部变量的地址在加载应用程序时由加载程序固定并设置为 运行.
如果外部不存在,那么您将在链接时收到 "unresolved external symbol" 错误。
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);
这些都有效,因为 none 这些源语句需要编译器可以使用 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
定义,或许可以克隆库提供的现有对象之一。
最近,我 运行 在我的系统中检查了以下代码 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;
,但是有一个裸结构似乎很奇怪,因为你不能使用该结构或将它传递给函数.这只是空操作吗?
代码 struct _IO_FILE_plus;
是名称 _IO_FILE_plus
的声明,因此如果编译器看到它在某个地方被使用,它就知道在某个地方会有一个实际描述的定义它的成员。
extern
修饰符表示命名的符号是存在于其他编译单元中的外部符号。代码如:
extern struct _IO_FILE_plus _IO_2_1_stdin_;
也是符号的声明,在本例中为_IO_2_1_stdin_
,告诉编译器该符号是一个外部符号,它被定义并存在于其他某个编译单元(文件)中,它的类型是什么在这种情况下,符号是 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_;
这会编译,因为编译器只需要知道如何找到外部变量的地址并将该地址放入指针变量中。外部变量的地址在加载应用程序时由加载程序固定并设置为 运行.
如果外部不存在,那么您将在链接时收到 "unresolved external symbol" 错误。
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);
这些都有效,因为 none 这些源语句需要编译器可以使用 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
定义,或许可以克隆库提供的现有对象之一。