【问题标题】:Opaque structures with multiple definitions具有多个定义的不透明结构
【发布时间】:2016-12-26 11:39:39
【问题描述】:

我正在考虑用 C 语言实现一个简单的接口模式。

一个关键特性是它会为接口的公共标头提供的不透明结构提供多个定义,也就是说,不同的实现会为该结构提供不同的底层数据(因此在不同的翻译单元中,相同的结构会有不同的实施)。

我找不到任何关于这将是好的还是坏的设计模式的参考。至少它似乎没有违反严格的别名规则,只依赖于公共第一个元素的一致顺序和填充,这是由 C 标准保证的(据我所知)。当然,我知道 C 中面向对象的模式,但我看不到这个特定的模式。

这可以接受吗?甚至使用过这样的模式吗? (我找不到任何东西)

为了便于理解,以下是三个源文件的工作示例:

reader.h(公共接口定义)

#ifndef READER_H
#define READER_H

typedef struct reader_s reader_t;

char reader_read(reader_t* r);

#endif

reader.c(接口的粘合逻辑)

#include "reader.h"

typedef char (reader_f)(reader_t*);

struct reader_s{
 reader_f* rfunc;
};

char reader_read(reader_t* r)
{
 return r->rfunc(r);
}

reader1.h(接口的实现,头文件)

#ifndef READER1_H
#define READER1_H

#include "reader.h"

reader_t* reader1_get(void);

#endif

reader1.c(接口的实现,代码)

#include "reader1.h"

typedef char (reader_f)(reader_t*);

struct reader_s{
 reader_f* rfunc;
 int       pos;
};

static char reader1_read(reader_t* r);

reader_t reader1_obj = {&reader1_read, 0};

reader_t* reader1_get(void)
{
 return &reader1_obj;
}

static char reader1_read(reader_t* r)
{
 char rval = 'A' + (r->pos);
 (r->pos) = (r->pos) + 1;
 if ((r->pos) == 24){ (r->pos) = 0; }
 return rval;
}

main.c(使用示例)

#include "reader1.h"
#include <stdio.h>

int main(void)
{
 reader_t* rd = reader1_get();
 int       i;

 printf("\n");
 for (i = 0; i < 60; i++){
  printf("%c", reader_read(rd));
 }
 printf("\n");

 return 0;
}

接口可能应该为实现者提供另一个头文件,提供包含函数指针的结构头的定义。也许它甚至可以指向一个包含那些以最小化具有许多方法的类的对象大小的类结构。

【问题讨论】:

    标签: c struct opaque-pointers


    【解决方案1】:

    C 标准中没有任何内容会定义这种情况下的行为,但如果实现指定了数据类型的存储格式以及处理模块间调用的方式,则可以定义行为作为结果。但是请注意,某些具有“链接时优化”的实现可能并不总是以“正常”记录的方式执行模块间调用。

    我唯一能看到这种方法可行的情况是,如果与类型关联的公共 API 希望允许代码直接声明该类型的实例或对它们使用赋值运算符,但又不想承诺为任何其他目的公开字段布局。在这种情况下,使用包含 uint32[]uint64[] 之类的公共结构类型可能是一种合理的方法,即使处理该类型内部的所有代码都使用“真实”结构类型。如果内部结构类型发生变化,如果大小保持不变,这样的设计将避免重新编译客户端代码的需要。另一方面,对于可能需要启用 gcc 风格的基于类型的别名“优化”来处理的代码,我不会考虑任何此类设计,因为 gcc 故意对此类优化会破坏代码的地方视而不见。

    【讨论】:

    • 谢谢!对于我在不同编译单元之间推断的编译器,编译器没有记忆(关于它如何转换结构),所以为了保持工作,它需要与布局和填充保持一致。这也是相关的:stackoverflow.com/questions/2748995/c-struct-memory-layout C 标准强制维护秩序。这些一起意味着相同的结构头必须编译为相同的布局。当然,您提到的链接时间优化可能仍会发挥作用。
    • @Jubatian:我觉得如果 C89 的作者不打算让指向具有相同布局的事物的指针可以互换使用,他们会经历详细说明布局的各个方面的麻烦,这令人难以置信,但编译器作者反而认为标准的作者无缘无故地做了这些事情。
    • 如果编译器在规定的时间和地点没有按照标准运行,那就是编译器错误。那么那个错误跟踪器在哪里呢? (但可能有记录的例外情况)
    • @Jubatian:在编写 C89 时,许多东西都被定义为具有兼容的布局,作为一种指定指向一个的指针可以用来读取另一个的方式。不幸的是,添加了规则,说明通过其他类型的指针访问对象将产生未定义的行为,而不会添加允许代码使用除“memcpy”之类的其他地方定义的布局保证的异常。
    • @Jubatian:标准的作者几乎可以肯定地期望编译器编写者会使用常识并尝试识别至少最直接的别名模式,这些模式将利用布局保证,但它更流行编译器编写者声称试图利用此类保证的代码已“损坏”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-01
    相关资源
    最近更新 更多