7

我正在试验 C11 和 VLA,试图在堆栈上声明一个结构变量,但声明不完整。目标是提供一种机制来创建某种结构类型的变量而不显示内部结构(如 PIMPL 习惯用法),但无需在堆上创建变量并返回指向它的指针。此外,如果结构布局发生变化,我不想重新编译每个使用该结构的文件。

我设法编写了以下内容:

私人.h:

#ifndef PRIVATE_H_
#define PRIVATE_H_

typedef struct A{
    int value;
}A;

#endif /* PRIVATE_H_ */

公共.h:

#ifndef PUBLIC_H_
#define PUBLIC_H_

typedef struct A A;

size_t A_getSizeOf(void);

void A_setValue(A * a, int value);

void A_printValue(A * a);

#endif /* PUBLIC_H_ */

实施.c:

#include "private.h"
#include "stdio.h"

size_t A_getSizeOf(void)
{
    return sizeof(A);
}

void A_setValue(A * a, int value)
{
    a->value = value;
}

void A_printValue(A * a)
{
    printf("%d\n", a->value);
}

主.c:

#include <stdalign.h>
#include <stddef.h>

#include "public.h"

#define createOnStack(type, variable) \
    alignas(max_align_t) char variable ## _stack[type ## _getSizeOf()]; \
    type * variable = (type *)&variable ## _stack

int main(int argc, char *argv[]) {
    createOnStack(A, var);

    A_setValue(var, 5335);
    A_printValue(var);
}

我已经测试了这段代码,它似乎可以工作。但是我不确定我是否忽略了一些可能危险或不可移植或可能损害性能的东西(如混叠、对齐或类似的东西)。另外我想知道C中是否有更好的(便携式)解决方案来解决这个问题。

4

2 回答 2

4

这当然违反了有效的类型规则(又名严格别名),因为 C 语言不允许char []通过不具有该类型(或兼容类型)的指针访问 tye 对象。

可以-fno-strict-aliasing通过编译器标志或属性来禁用严格的别名分析

#ifdef __GNUC__
#define MAY_ALIAS __attribute__((__may_alias__))
#else
#define MAY_ALIAS
#endif

(感谢 R.. 指出后者),但即使您不这样做,实际上只要您只使用变量的正确名称来初始化类型化指针,一切都应该正常工作。

就个人而言,我会将您的声明简化为以下内容

#define stackbuffer(NAME, SIZE) \
    _Alignas (max_align_t) char NAME[SIZE]

typedef struct Foo Foo;
extern const size_t SIZEOF_FOO;

stackbuffer(buffer, SIZEOF_FOO);
Foo *foo = (void *)buffer;

另一种方法是使用 non-standard alloca(),但该“功能”有其自身的一系列问题。

于 2014-08-28T00:24:43.470 回答
1

我正在考虑采用类似于以下的策略来解决基本相同的问题。尽管晚了一年,但也许它会引起人们的兴趣。

我希望防止结构的客户直接访问这些字段,以便更容易推理他们的状态并更容易编写可靠的设计合同。我还希望避免在堆上分配小结构。但是我买不起 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);
}

我可以看到自己的缺点:

  1. 手动更改尺寸定义会很烦人
  2. 铸造应该阻碍优化(我还没有检查过)
  3. 这可能违反严格的指针别名。需要重新阅读规范。

我担心意外调用未定义的行为。我也对上述内容的一般反馈感兴趣,或者它是否看起来像是问题中创造性 VLA 技术的可靠替代方案。

于 2015-06-25T23:44:55.410 回答