如何在通用标头中键入实现定义的结构?

时间:2014-11-08 02:33:08

标签: c portability

我有一个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解决方案是正确的,它只是我留下的结构标记包。

思想?

1 个答案:

答案 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中第二个表格的最后一行。