我有一个C项目,可以移植到各种(PC和嵌入式)平台。
应用程序代码将使用具有特定于平台的实现的各种调用,但共享一个通用(通用)API以帮助实现可移植性。我试图找到最合适的方式来声明函数原型和结构。
这是我到目前为止所提出的:
main.c中:
#include "generic.h"
int main (int argc, char *argv[]) {
int ret;
gen_t *data;
ret = foo(data);
...
}
generic.h:(平台无关的包括)
typedef struct impl_t gen_t;
int foo (gen_t *data);
impl.h:(特定于平台的声明)
#include "generic.h"
typedef struct impl_t {
/* ... */
} gen_t;
impl.c:(特定于平台的实施)
int foo (gen_t *data) {
...
}
构建
gcc -c -fPIC -o platform.o impl.c
gcc -o app main.c platform.o
现在,这似乎有效......因为它编译好了。但是,我通常不会标记我的结构,因为它们永远不会在typedef
别名之外访问。这是一个很小的挑选,但我想知道是否有办法与匿名结构达到同样的效果?
我也要求后代,因为我搜索了一段时间,我找到的最接近的答案是:(Link)
在我的情况下,这不是正确的方法,因为应用程序特别不应该直接包含实现头 - 整点是将程序与平台分离。
我看到了其他一些不太理想的方法来解决这个问题,例如:
generic.h:
#ifdef PLATFORM_X
#include "platform_x/impl.h"
#endif
/* or */
int foo (struct impl_t *data);
这些都不是特别有吸引力,绝对不是我的风格。虽然我不想上游游泳,但是当我可能有一种更好的方式来实现我的想法时,我也不想要相互冲突的风格。所以我认为typedef解决方案是正确的,它只是我留下的结构标记包。
思想?
答案 0 :(得分:4)
您当前的技术是正确的。尝试使用匿名(未标记)struct
会使您尝试做的事情失败 - 您必须在任何地方公开struct
定义的详细信息,这意味着您不再拥有不透明的数据类型。
在comment中,user3629249说:
头文件包含的顺序意味着
generic.h
文件存在对结构的前向引用;也就是说,在定义结构之前,使用它。这不太可能编译。
对于问题中显示的标题,此观察结果不正确;它对于样本main()
代码是准确的(在添加此响应之前我没有注意到)。
关键是所显示的接口函数接收或返回指向gen_t
类型的指针,而struct impl_t
类型又指向struct impl_t
指针。只要客户端代码不需要为结构分配空间,或者取消引用指向结构的指针来访问结构的成员,客户端代码就不需要知道结构的细节。将结构类型声明为现有就足够了。您可以使用其中任何一个来声明struct impl_t;
typedef struct impl_t gen_t;
:
gen_t
后者还为类型struct impl_t
引入了别名main()
。另请参阅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?
问题中的原始int main (int argc, char *argv[]) {
int ret;
gen_t data;
ret = foo(&data);
…
}
计划是:
gen_t
此代码无法使用typedef struct impl_t *gen_t;
作为不透明(非指针)类型进行编译。它适用于:
typedef struct impl_t gen_t;
它无法编译:
data
因为编译器必须知道为main()
分配正确空间的结构有多大,但编译器无法通过定义opaque类型来知道该大小。 (有关typedefing指向结构的指针,请参阅Is it a good idea to typedef
pointers?。)
因此,#include "generic.h"
int main(int argc, char **argv)
{
gen_t *data = bar(argc, argv);
int ret = foo(data);
...
}
代码应该更像:
bar()
其中(对于此示例)extern gen_t *bar(int argc, char **argv);
定义为gen_t
,因此它返回指向opaque类型struct tagname
的指针。
对于是否总是使用typedef
或使用typedef
作为名称是否更好,意见分歧。 Linux内核是一个不使用struct tagname
机制的实体代码;所有结构都明确typedef
。另一方面,C ++不需要显式struct impl_t;
;写作:
impl_t
在C ++程序中意味着名称void *
现在是一个类型的名称。因为不透明的结构类型需要一个标签(或者你最终使用void *
来处理所有事情,这对于一大堆原因都是不好的,但主要原因是你使用typedef
失去了所有类型的安全性;记得,typedef struct Generic Generic;
引入了基础类型的别名,而不是新的不同类型),我在C中编码的方式模拟C ++:
_t
我避免在我的类型上使用_t
后缀,因为POSIX为实现保留dec_t
以使用 * (另请参阅What does a type followed by _t
represent?)。你可能很幸运,可以逃脱它。我曾经在代码库中工作,其中loc_t
和_t
等类型是由代码库定义的(它不是实现的一部分 - 其中'实现'是指C编译器及其支持代码,或者C库及其支持代码),这两种类型都引起了数十年的痛苦,因为代码被移植的一些系统定义了这些类型,系统的特权也是如此。我设法摆脱的一个名字;另一个我没有。 'Twas痛苦!如果您必须使用pqr_typename_t
(这是指示某些内容类型的便捷方式),我建议您使用一个独特的前缀:pqr
,例如,某些项目{{1}}。< / p>
*请参阅POSIX标准中The Name Space中第二个表格的最后一行。