如何在通用标头中typedef实现定义的结构? [英] How do I typedef an implementation-defined struct in a generic header?

查看:131
本文介绍了如何在通用标头中typedef实现定义的结构?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个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别名之外进行访问,因此我通常不标记结构.这是一个小巧的选择,但我想知道是否可以通过匿名结构实现相同的效果?

我也在询问后代,因为我搜索了一段时间,发现的最接近答案是:(

这些似乎都没有特别吸引人,而且绝对不是我的风格.虽然我不想游刃有余,但是当可能有更好的方法来准确实现我的想法时,我也不想出现冲突的风格.因此,我认为typedef解决方案是在正确的轨道上,而这只是我剩下的struct标记.

有想法吗?

解决方案

您当前的技术是正确的.尝试使用匿名的(未标记的)struct会破坏您要执行的操作-您必须在任何地方公开struct的定义的详细信息,这意味着您不再拥有不透明的数据类型.


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.另请参见 C标准的哪个部分允许使用typedef指针好吗?类型定义指向结构的指针.)

因此,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供实施使用 * (另请参见_t(这是一种表示某种类型的简便方法),我建议也使用一个独特的前缀:例如,对于某些项目pqr,使用pqr_typename_t.

*请参见 POSIX标准中的名称空间.

I have a C project that is designed to be portable to various (PC and embedded) platforms.

Application code will use various calls that will have platform-specific implementations, but share a common (generic) API to aid in portability. I'm trying to settle on the most appropriate way to declare the function prototypes and structures.

Here's what I've come up with so far:

main.c:

#include "generic.h"

int main (int argc, char *argv[]) {
    int  ret;
    gen_t  *data;

    ret = foo(data);
    ...
}

generic.h: (platform-agnostic include)

typedef struct impl_t gen_t;

int foo (gen_t *data);

impl.h: (platform-specific declaration)

#include "generic.h"

typedef struct impl_t {
    /* ... */
} gen_t;

impl.c: (platform-specific implementation)

int foo (gen_t *data) {
    ...
}

Build:

gcc -c -fPIC -o platform.o impl.c
gcc -o app  main.c platform.o

Now, this appears to work... in that it compiles OK. However, I don't usually tag my structures since they're never accessed outside of the typedef'd alias. It's a small nit-pick, but I'm wondering if there's a way to achieve the same effect with anonymous structs?

I'm also asking for posterity, since I searched for a while and the closest answer I found was this: (Link)

In my case, that wouldn't be the right approach, as the application specifically shouldn't ever include the implementation headers directly -- the whole point is to decouple the program from the platform.

I see a couple of other less-than-ideal ways to resolve this, for example:

generic.h:

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

/* or */

int foo (struct impl_t *data);

Neither of these seems particularly appealing, and definitely not my style. While I don't want to swim upstream, I also don't want conflicting style when there might be a nicer way to implement exactly what I had in mind. So I think the typedef solution is on the right track, and it's just the struct tag baggage I'm left with.

Thoughts?

解决方案

Your current technique is correct. Trying to use an anonymous (untagged) struct defeats what you're trying to do — you'd have to expose the details of definition of the struct everywhere, which means you no longer have an opaque data type.


In a comment, user3629249 said:

The order of the header file inclusions means there is a forward reference to the struct by the generic.h file; that is, before the struct is defined, it is used. It is unlikely this would compile.

This observation is incorrect for the headers shown in the question; it is accurate for the sample main() code (which I hadn't noticed until adding this response).

The key point is that the interface functions shown take or return pointers to the type gen_t, which in turn maps to a struct impl_t pointer. As long as the client code does not need to allocate space for the structure, or dereference a pointer to a structure to access a member of the structure, the client code does not need to know the details of the structure. It is sufficient to have the structure type declared as existing. You could use either of these to declare the existence of struct impl_t:

struct impl_t;

typedef struct impl_t gen_t;

The latter also introduces the alias gen_t for the type struct impl_t. See also Which part of the C standard allows this code to compile? and Does the C standard consider that there are one or two struct uperms entry types in this header?

The original main() program in the question was:

int main (int argc, char *argv[]) {
    int  ret;
    gen_t  data;

    ret = foo(&data);
    …
}

This code cannot be compiled with gen_t as an opaque (non-pointer) type. It would work OK with:

typedef struct impl_t *gen_t;

It would not compile with:

typedef struct impl_t gen_t;

because the compiler must know how big the structure is to allocate the correct space for data, but the compiler cannot know that size by definition of what an opaque type is. (See Is it a good idea to typedef pointers? for typedefing pointers to structures.)

Thus, the main() code should be more like:

#include "generic.h"

int main(int argc, char **argv)
{
    gen_t *data = bar(argc, argv);
    int ret = foo(data);
    ...
}

where (for this example) bar() is defined as extern gen_t *bar(int argc, char **argv);, so it returns a pointer to the opaque type gen_t.

Opinion is split over whether it is better to always use struct tagname or to use a typedef for the name. The Linux kernel is one substantial body of code that does not use the typedef mechanism; all structures are explicitly struct tagname. On the other hand, C++ does away with the need for the explicit typedef; writing:

struct impl_t;

in a C++ program means that the name impl_t is now the name of a type. Since opaque structure types require a tag (or you end up using void * for everything, which is bad for a whole legion of reasons, but the primary reason is that you lose all type safety using void *; remember, typedef introduces an alias for an underlying type, not a new distinct type), the way I code in C simulates C++:

typedef struct Generic Generic;

I avoid using the _t suffix on my types because POSIX reserves the _t for the implementation to use* (see also What does a type followed by _t represent?). You may be lucky and get away with it. I've worked on code bases where types like dec_t and loc_t were defined by the code base (which was not part of the implementation — where 'the implementation' means the C compiler and its supporting code, or the C library and its supporting code), and both those types caused pain for decades because some of the systems where the code was ported defined those types, as is the system's prerogative. One of the names I managed to get rid of; the other I didn't. 'Twas painful! If you must use _t (it is a convenient way to indicate that something is a type), I recommend using a distinctive prefix too: pqr_typename_t for some project pqr, for example.

* See the bottom line of the second table in The Name Space in the POSIX standard.

这篇关于如何在通用标头中typedef实现定义的结构?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆