我想创建一个opaque类型,同时仍然允许该类型的用户实例化它,例如通过键入
在堆栈上struct Foo obj;
/*...*/
Foo_init(&obj);
Foo_do_something(&obj, param);
/*...*/
通常的不透明指针方法不允许此示例的第一行。 作为一种解决方法,我正在公开但不透明的数据"公共头文件中的数组 具有固定的大小。这似乎适用于一些例子,但我有点不确定两点:
像Foo_get_bar和Foo_set_bar一样,转换数据数组的地址是否安全?这在我的测试中没问题,但看起来有问题。
如果FOO_DATA_SIZE保持不变,那么在用户代码中期望ABI兼容性是否合理?
#include <stdio.h>
#include <limits.h>
#include "foo.h"
int main() {
struct Foo foo;
Foo_init(&foo);
Foo_set_bar(&foo, INT_MAX);
int bar = Foo_get_bar(&foo);
printf("Got bar: %d\n", bar);
}
#pragma once
#include <inttypes.h>
#define FOO_DATA_SIZE (64)
struct Foo {
uint8_t data[FOO_DATA_SIZE];
};
void Foo_init(struct Foo *f);
void Foo_set_bar(struct Foo *f, int barval);
int Foo_get_bar(struct Foo *f);
#include "foo.h"
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
typedef int64_t bar_t;
struct Foo_private {
bar_t bar;
};
_Static_assert(sizeof(struct Foo_private) <= FOO_DATA_SIZE,
"FOO_DATA_SIZE is insufficient for struct Foo_private");
void Foo_init(struct Foo *foo) {
struct Foo_private foodata;
foodata.bar = (bar_t)0;
memcpy(foo->data, &foodata, sizeof(struct Foo_private));
}
void Foo_set_bar(struct Foo *foo, int barval) {
struct Foo_private *foodata = (void*)&(foo->data);
foodata->bar = (bar_t)barval;
int stored = (int)foodata->bar;
if (stored != barval) {
fprintf(stderr, "Foo_set_bar(%"PRId64"): warning: bar rounded to %"PRId64"\n",
(int64_t)barval, (int64_t)stored);
}
}
int Foo_get_bar(struct Foo *foo) {
struct Foo_private *foodata = (void*)&(foo->data);
bar_t bar = foodata->bar;
return (int)bar;
}
答案 0 :(得分:4)
我已经查看了这些帖子中的信息:
Why one should not hide a structure implementation that way?
Static allocation of opaque data types
以及评论。我认为我有一个有效的答案:我改用了一个不透明的指针类型,但我现在正在暴露一个函数调用,告诉用户它有多大,以便他可以调用alloca或malloc或其他任何东西来分配空间。基本上要求是分配由用户执行,而不是执行。
修改标题:
#pragma once
#include <inttypes.h>
struct Foo;
typedef struct Foo Foo;
void Foo_init(Foo *f);
void Foo_set_bar(Foo *f, int barval);
int Foo_get_bar(Foo *f);
size_t Foo_data_size(void);
#define Foo_alloca() alloca(Foo_data_size())
#define Foo_new() malloc(Foo_data_size())
#define Foo_delete(PTR) do { \
free(PTR); \
PTR = NULL; \
} while(0)
修改后的实施定义:
typedef int64_t bar_t;
struct Foo {
volatile bar_t bar;
};
void Foo_init(struct Foo *foo) {
struct Foo foodata;
foodata.bar = (bar_t)0;
memcpy(foo, &foodata, Foo_data_size());
}
size_t Foo_data_size() {
return sizeof(struct Foo);
}
//...
然后在用户代码中,使用具有Foo_data_size()提供的大小的alloca或使用便利宏。
这种方法消除了固定大小的限制,并希望解决上面提到的对齐问题。
私有结构声明中的单词volatile是不相关的。如果不以这种方式声明,gcc至少在win32上试图优化我在不兼容的表示中存储某些值的约束检查。
使用示例:
#include "foo.h"
//...
{
Foo *foo = Foo_alloca();
Foo_init(foo);
Foo_set_bar(foo, INT_MAX);
int bar = Foo_get_bar(foo);
printf("Got bar: %d\n", bar);
}