假设我在一个头文件中有一个结构声明:
event.h
struct event_t;
,在相应的C文件中,我想使用Linux专用的struct inotify_event
对它进行别名排序。问题是struct inotify_event
包含灵活的数组成员:
struct inotify_event {
int wd;
uint32_t mask;
uint32_t cookie;
uint32_t len;
char name[];
};
根据6.7.2.1(p3)
(强调我的):
结构或联合不得包含不完整或不完整的成员 函数类型(因此,结构不得包含的实例 本身,但可能包含指向其实例的指针),但 具有一个以上命名成员的结构的最后一个成员 可能具有不完整的数组类型;这样的结构(以及任何联盟 包含(可能是递归的)具有这种结构的成员) 不得是数组的结构或元素的成员。
不可能将struct event_t
定义为
struct event_t{
struct inotify_event base; //Non-conforming
};
所以我可以将struct event_t *
转换为struct inotify_event *
。由于6.7.2.1(p3)
仅关注结构,因此我看到的解决方案是将标签名称重新声明为
union event_t
,然后将其定义为单个元素联合。
union event_t{
struct inotify_event event; //Conforming?
};
我发现标准对工会施加的唯一要求是,工会成员的集合必须为非空6.2.5(p20)
(强调我的):
并集类型描述了重叠的非空成员对象集, 每个都有一个可选的指定名称,并且可能不同 类型。
问题: :通过union
隐藏某些特定数据结构的实现细节是一种一致/常见的方法吗?
答案 0 :(得分:3)
这就是我要做的:
event.h
struct event_t;
event_t *create_event(void);
void free_event(event_t *ev);
event.c
#include "event.h";
event_t *create_event(void)
{
inotify_event *iev = ...;
return (event_t *)iev;
}
void free_event(event_t *ev)
{
inotify_event *iev = (inotify_event *)ev;
// free the event
}
但是,如果您想在事件中存储其他数据,则:
event.h
struct event_t;
event_t *create_event(void);
void free_event(event_t *ev);
event.c
#include "event.h";
struct event_t
{
inotify_event *iev;
// additional data
};
event_t *create_event(void)
{
inotify_event *iev = ...;
event_t *ev = malloc(sizeof(event_t));
ev.iev = iev;
return ev;
}
void free_event(event_t *ev)
{
inotify_event *iev = (inotify_event *)ev.iev;
// free the event (iev) first
free(ev);
}
如果您需要在event_t
中隐藏多种实现,则:
enum event_type
{
EVENT_TYPE_INOTIFY,
EVENT_TYPE_INOTIFY2,
};
struct event_t
{
event_type type;
union {
inotify_event *iev; // you use this when type == EVENT_TYPE_INOTIFY
inotify_event2 *iev2; // you use this when type == EVENT_TYPE_INOTIFY2
}
// additional data
};
答案 1 :(得分:2)
单元素联合没有任何意义。联合的目的是充当一种多态结构。偏移量访问结构成员,这就是为什么不可能将不完整的结构或数组放在结构中间的原因。
例如
struct foo { int a; int b[]; int c; };
在此示例中,编译器无法确定c
的地址,因为b
的大小会在运行时变化。但是,如果将不完整的数组放在末尾,则所有结构成员的地址都可以由结构起始地址确定。请记住,指针只是地址,因此您可以具有指向任何结构的任何指针,并且可以确定所有偏移量,但是您将需要处理额外的alloc / free东西。
创建联合时,您要告诉编译器嘿!我有这个成员,请为我保留足够的空间,以便可以将此变量视为foo或bar 。换句话说,编译器将采用最大的并集成员,这将是并集的大小。联合的常见用途是代表多种值。
typedef union { int integer, float real, char *string } value_type;
通过这种方式,您可以将value_type
视为int
,float
或char
指针。您的代码需要知道如何对待每个成员,但是编译器将确保在执行malloc(sizeof value_type)
时,您有足够的空间来容纳树类型。
现在是您的问题。您想隐藏实施细节。通常,这是通过在标头中完全声明类型或结构来完成的,并且仅在目标文件上完全声明。因此,当用户包括您的标头时,编译器具有的所有信息都是struct my_struct;
。它无法确定my_struct
的大小,因此无法将其分配为malloc(sizeof struct my_struct)
。同样,由于用户没有成员定义,因此也无法弄乱结构内部。
要像这样工作,您将需要为用户提供分配和释放my_struct
的功能,例如struct my_struct *foo = my_struct_new()
和my_struct_destroy(foo)
。
您已经在执行此操作。要解决struct inotify
问题,我可以做其中之一。
(1)特定于#ifdef
的环绕声操作系统,因此event_t
仅具有根据操作系统定义的正确成员。您需要在函数上使用#ifdef
。这样的好处是可以将无用的代码排除在最终的二进制文件之外,从而减小了占用空间。
(2)具有指向特定于OS的结构的指针,并让运行时决定要做什么。这样更易于维护。
答案 2 :(得分:2)
到目前为止,最简单的方法是将其放入您的event.h
标头中:
typedef struct inotify_event event_t;
这声明存在结构类型struct inotify_event
,并为其声明别名event_t
。但是它根本没有定义struct inotify_event
的内容。
仅event.c
中的实现代码包括系统标题中的struct inotify_event
定义;除您定义的访问器API外,其他所有内容均不包含该标头,并且无法访问event_t
的元素。
您可以通过代码审查(或通过使用grep
或其他类似技术进行检查)来强制执行职责分离,以确保除事件类型的实现之外,没有任何代码使用{{1}的系统头}。而且,如果您移植到Linux以外的系统而不支持inotify,则只需在inotify_event
标头中提供替代的不透明结构类型来代替struct inotify_event
。
这避免了有关结构等中是否存在灵活数组成员的所有问题;都是非问题。
请注意有关What does a type followed by _t
(underscore t) represent?的问答。在使用event.h
后缀creating创建自己的类型时要谨慎-考虑在此类类型名称上使用前缀,这样可以使您的名称与系统提供的名称区分开。