我正在考虑采用类似于以下的策略来解决基本相同的问题。尽管晚了一年,但也许它会引起人们的兴趣。
我希望防止结构的客户直接访问这些字段,以便更容易推理他们的状态并更容易编写可靠的设计合同。我还希望避免在堆上分配小结构。但是我买不起 C11 公共接口——C 的大部分乐趣在于几乎所有代码都知道如何与 C89 对话。
为此,请考虑适当的应用程序代码:
#include "opaque.h"
int main(void)
{
opaque on_the_stack = create_opaque(42,3.14); // constructor
print_opaque(&on_the_stack);
delete_opaque(&on_the_stack); // destructor
return 0;
}
不透明的标题相当讨厌,但并不完全荒谬。提供创建和删除函数主要是为了与调用析构函数实际上很重要的结构保持一致。
/* opaque.h */
#ifndef OPAQUE_H
#define OPAQUE_H
/* max_align_t is not reliably available in stddef, esp. in c89 */
typedef union
{
int foo;
long long _longlong;
unsigned long long _ulonglong;
double _double;
void * _voidptr;
void (*_voidfuncptr)(void);
/* I believe the above types are sufficient */
} alignment_hack;
#define sizeof_opaque 16 /* Tedious to keep up to date */
typedef struct
{
union
{
char state [sizeof_opaque];
alignment_hack hack;
} private;
} opaque;
#undef sizeof_opaque /* minimise the scope of the macro */
void print_opaque(opaque * o);
opaque create_opaque(int foo, double bar);
void delete_opaque(opaque *);
#endif
最后是一个实现,欢迎使用 C11,因为它不是接口。_Static_assert(alignof...) 特别让人放心。几层静态函数用于指示生成包裹/展开层的明显改进。几乎整个混乱都可以使用代码生成。
#include "opaque.h"
#include <stdalign.h>
#include <stdio.h>
typedef struct
{
int foo;
double bar;
} opaque_impl;
/* Zero tolerance approach to letting the sizes drift */
_Static_assert(sizeof (opaque) == sizeof (opaque_impl), "Opaque size incorrect");
_Static_assert(alignof (opaque) == alignof (opaque_impl), "Opaque alignment incorrect");
static void print_opaque_impl(opaque_impl *o)
{
printf("Foo = %d and Bar = %g\n",o->foo,o->bar);
}
static void create_opaque_impl(opaque_impl * o, int foo, double bar)
{
o->foo = foo;
o->bar = bar;
}
static void create_opaque_hack(opaque * o, int foo, double bar)
{
opaque_impl * ptr = (opaque_impl*)o;
create_opaque_impl(ptr,foo,bar);
}
static void delete_opaque_impl(opaque_impl *o)
{
o->foo = 0;
o->bar = 0;
}
static void delete_opaque_hack(opaque * o)
{
opaque_impl * ptr = (opaque_impl*)o;
delete_opaque_impl(ptr);
}
void print_opaque(opaque * o)
{
return print_opaque_impl((opaque_impl*)o);
}
opaque create_opaque(int foo, double bar)
{
opaque tmp;
unsigned int i;
/* Useful to zero out padding */
for (i=0; i < sizeof (opaque_impl); i++)
{
tmp.private.state[i] = 0;
}
create_opaque_hack(&tmp,foo,bar);
return tmp;
}
void delete_opaque(opaque *o)
{
delete_opaque_hack(o);
}
我可以看到自己的缺点:
- 手动更改尺寸定义会很烦人
- 铸造应该阻碍优化(我还没有检查过)
- 这可能违反严格的指针别名。需要重新阅读规范。
我担心意外调用未定义的行为。我也对上述内容的一般反馈感兴趣,或者它是否看起来像是问题中创造性 VLA 技术的可靠替代方案。