1

我想创建一个不透明的类型,同时仍然允许该类型的用户通过键入来实例化它,例如在堆栈上

struct Foo obj;
/*...*/
Foo_init(&obj);
Foo_do_something(&obj, param);
/*...*/

惯用的不透明指针方法不允许此示例的第一行。作为一种解决方法,我将一个公共但不透明的“数据”数组放置在具有固定大小的公共头文件中。这似乎适用于一些例子,但我有点不确定两点:

  1. 像在 Foo_get_bar 和 Foo_set_bar 中那样转换数据数组的地址是否安全?这在我的测试中工作正常,但看起来有问题。

  2. 如果 FOO_DATA_SIZE 保持固定,那么期望用户代码中的 ABI 兼容性是否合理?


main.c(示例用户代码)

#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);
}

foo.h(公共标头)

#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); 

foo.c(实现)

#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;
}
4

1 回答 1

4

我查看了这些帖子中的信息:

为什么不应该以这种方式隐藏结构实现?

不透明数据类型的静态分配

以及评论。我想我有一个可行的答案:我切换到使用不透明指针类型,但现在公开一个函数调用,告诉用户它有多大,以便他可以调用 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);
}
于 2013-07-14T20:40:00.930 回答