【问题标题】:How do I typedef an implementation-defined struct in a generic header?如何在通用标头中键入定义实现定义的结构?
【发布时间】:2015-01-04 22:40:03
【问题描述】:

我有一个 C 项目,旨在移植到各种(PC 和嵌入式)平台。

应用程序代码将使用各种调用,这些调用具有特定于平台的实现,但共享一个通用(通用)API 以帮助实现可移植性。我正在尝试选择最合适的方式来声明函数原型和结构。

这是我到目前为止的想法:

ma​​in.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'd 别名之外访问过。这是一个小小的挑剔,但我想知道是否有办法使用匿名结构实现相同的效果?

我也在问后人,因为我搜索了一段时间,我找到的最接近的答案是:(Link)

在我的情况下,这不是正确的方法,因为应用程序不应该直接包含实现头文件——重点是将程序与平台分离。

我看到了其他一些不太理想的方法来解决这个问题,例如:

generic.h:

#ifdef PLATFORM_X
#include "platform_x/impl.h"
#endif

/* or */

int foo (struct impl_t *data);

这些似乎都不是特别吸引人,而且绝对不是我的风格。虽然我不想逆流而上,但当可能有更好的方法来实现我的想法时,我也不想要冲突的风格。所以我认为 typedef 解决方案是在正确的轨道上,它只是我留下的 struct tag 包袱。

想法?

【问题讨论】:

  • 您可以将gen_t 设为指针类型(句柄)。然后客户端将不得不使用您的 api 来初始化/使用/销毁它。或稍作变化,使您公开的结构包含指向实际实现的不透明指针。否则,您无能为力。必须创建客户端必须知道的对象或路径跟踪它。
  • 存在一个问题,即在 impl.h 之前包含 generic.h 但是,generic.h 引用了 impl.h I.E 中定义的结构。前向参考。 (可能不会编译)更好地让 generic.h 包含 impl.h(其中 impl.h 是特定于平台的)然后当 generic.h 尝试使用它时,结构将已经定义。
  • 最好将结构定义为: struct tagname { ;;; };然后始终将其引用为 'struct tagname myname;'造成这种情况的原因有很多:1)命名结构是一种已弃用的格式 2)调试器使用标签名称来引用字段等 3)在结构上使用 typedef(通常)是不好的编程习惯

标签: c portability


【解决方案1】:

你目前的技术是正确的。尝试使用匿名(未标记)struct 会破坏您尝试做的事情 - 您必须在任何地方公开struct 的定义细节,这意味着您不再拥有不透明的数据类型。


在comment,user3629249中说:

头文件包含的顺序意味着generic.h文件对结构的前向引用;也就是说,在定义结构之前,使用它。这不太可能编译。

对于问题中显示的标题,此观察结果不正确;它对于示例 main() 代码是准确的(在添加此响应之前我没有注意到)。

关键点是显示的接口函数获取或返回指向gen_t 类型的指针,而后者又映射到struct impl_t 指针。只要客户端代码不需要为结构分配空间,或者取消引用指向结构的指针来访问结构的成员,客户端代码就不需要知道结构的细节。将结构类型声明为存在就足够了。你可以使用其中任何一个来声明struct impl_t的存在:

struct impl_t;

typedef struct impl_t gen_t;

后者还为struct impl_t 类型引入了别名gen_t。另见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?

问题中的原始main() 程序是:

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 分配正确的空间,但编译器无法根据不透明类型的定义知道该大小。 (请参阅 Is it a good idea to typedef pointers? 了解类型定义指向结构的指针。)

因此,main() 代码应该更像:

#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 的指针。

对于始终使用struct tagname 还是使用typedef 作为名称更好,意见存在分歧。 Linux 内核是不使用typedef 机制的大量代码;所有结构都是明确的struct tagname。另一方面,C++ 不需要显式的typedef;写作:

struct impl_t;

在 C++ 程序中意味着名称 impl_t 现在是类型的名称。因为不透明的结构类型需要一个标签(或者你最终对所有东西都使用void *,这有很多原因是不好的,但主要原因是你失去了使用void *的所有类型安全性;记住,typedef为基础类型引入别名,而不是新的不同类型),我用 C 编写代码的方式模拟 C++:

typedef struct Generic Generic;

我避免在我的类型上使用 _t 后缀,因为 POSIX 保留了 _t 以供实现使用*(另请参阅 What does a type followed by _t represent?)。你可能很幸运并且侥幸逃脱。我曾在代码库上工作过,其中 dec_t 和 loc_t 等类型由代码库定义(这不是实现的一部分 - 其中“实现”是指 C 编译器及其支持代码或 C 库及其支持代码),并且这两种类型都引起了几十年的痛苦,因为移植代码的一些系统定义了这些类型,这是系统的特权。我设法摆脱的名字之一;另一个我没有。 '好痛苦!如果你必须使用_t(这是一种方便的方式来表明某事是一种类型),我建议也使用一个独特的前缀:pqr_typename_t 用于某些项目pqr,例如。

* 参见 POSIX 标准中The Name Space 中第二个表的底行。

【讨论】:

  • 头文件包含的顺序意味着generic.h文件对结构的前向引用,即在定义结构之前,使用它。这不太可能编译
  • 这是一个很好的答案,可以解决我担心的所有问题。谢谢!顺便说一句,您对 main() 中的 gen_t 用法是绝对正确的。我将其剥离到最少的相关代码,并在翻译中弄错了那部分。指针上使用的第一个函数调用是实现的一部分,它知道它的确切大小,并根据需要分配内存。最后,感谢您对 _t 后缀的提醒。我没有意识到这一点——也许是没有接受过正式培训的缺点之一。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多