具有柔性阵列成员的不透明结构

时间:2019-03-22 05:50:16

标签: c linux struct

假设我在一个头文件中有一个结构声明:

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隐藏某些特定数据结构的实现细节是一种一致/常见的方法吗?

3 个答案:

答案 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视为intfloatchar指针。您的代码需要知道如何对待每个成员,但是编译器将确保在执行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创建自己的类型时要谨慎-考虑在此类类型名称上使用前缀,这样可以使您的名称与系统提供的名称区分开。