是否可以在堆栈上分配结构并将其定义隐藏在源文件中?

Is it possible to allocate structs on the stack with its definition hidden in a source file?

我有以下头文件:

struct StackList_s;
typedef struct StackList_s StackList_t;
// From here I add in the method signatures

以及以下源文件:

struct StackList_s
{
    integer_t count;
    struct StackListNode_s *top; // Here begins the linked list
    // Some other members that store information about the stack
    integer_t version_id;
};
// From here I define StackListNode_s and implement the StackList_s functions
// Note that the user will never manipulate directly a StackListNode_s
// There are functions that will handle the free() of each node correctly

我在源文件中隐藏了结构定义,这样使用这个堆栈的任何人都不能直接修改它的成员,因为更改它们需要一些输入处理或检查某些无效状态。

目前,要获得新的堆栈,您必须使用以下方法:

// malloc(sizeof(StackList_t)) and set members to default
StackList_t *stack = stl_new(/* Some info parameters */);

但我只能在堆中分配一个StackList_t。我想要做的是在堆栈上分配 StackList_t ,然后可以在堆中分配它的节点以及它们的数据和指向其他节点的指针。这样我就可以给用户一个选择,是在本地使用该结构,还是将其作为分配的资源传递给函数。

StackList_t stack;
stl_init(&stack, /* Info parameters */); // No malloc, only setting members to 0

但是我当然不能这样做,因为struct StackList_s的定义在源文件中。所以这是我的问题:

可以使用 VLA 或 alloca 在 Linux:

中做到这一点

图书馆header:

struct StackList_s;
typedef struct StackList_s StackList_t;
extern const size_t StackList_size;

// If you're using VLAs
extern const size_t StackList_align;
StackList_t* stl_init_inline(char stack_source[], ...);

图书馆来源:

#include "header.h"

struct StackList_s {
    // ...
};

const size_t StackList_size = sizeof(StackList_t);

// If you're using VLAs
#include <stdalign.h>
#include <stdint.h>

const size_t StackList_align = alignof(StackList_t);
StackList_t* stl_init_inline(char stack_source[], ...) {
    // align the address to the nearest multiple of StackList_align
    uintptr_t address = (uintptr_t) ((void*) stack_source);
    if (address % StackList_align != 0) {
        address += StackList_align - address % StackList_align;
    }
    StackList_t* stack = (StackList_t*) ((void*) address);
    stl_init(stack, ...);
    return stack;
}

主要来源

#include <header.h>

StackList_t* stack = alloca(Stacklist_size);
stl_init(stack, ...);

char stack_source[StackList_size + StackList_align - 1];  // Not compile time.
StackList_t* stack = stl_init_inline(stack_source, ...);

这个 在堆栈上分配它,你不需要释放它,但它比 StackList_t stack_source; 更慢也更冗长。 (而且 alloca 只是 Linux)

对于第二个问题,您需要一个结构的完整定义来获取它的大小。常见的陷阱包括 sizeof(struct { int a; }) == sizeof(struct { int a; }) 可能为假。虽然可能不会,所以您可以 #define StackList_size sizeof(struct { integer_t count; struct StackListNode_s *top; integer_t version_id; }) 但这也会导致大量代码重复。

我个人只是将结构定义放在 header 文件中,然后在某处的注释中声明 "don't mess with the members or my methods won't work" (也许使名称以 _ 开头以给出提示他们是私人的)

你可以在不使用 VLA 的情况下通过使用 #define 来做类似于 Artyer 的回答的事情

Header:

#define STACKLISTSIZE 32
typedef uint8_t stl_storage[STACKLISTSIZE];
typedef struct stacklist_s stacklist_t;

stacklist_t* stl_create_from_stack(stl_storage b);  //user provides memory
stacklist_t* stl_allocate(void);  //library allocates memory, user must free.

来源:

int myfunction()
{
  stl_storage x;
  stacklist_t* sp = stl_create_from_stack(x);
  //do something with sp.
}

确保在实现文件中有一个 compile-time 断言 sizeof(stack_s) == STACKSTRUCTSIZE

一些实现保证编译单元之间的调用将以与平台的应用程序二进制接口 (ABI) 一致的方式处理,而不考虑被调用的函数将如何处理它接收到的地址的存储,或者是什么调用者将完成其提供的地址的存储,或者一旦函数 returns 将处理此类存储。在这样的实现中,给出类似的东西:

// In header
typedef union FOO_PUBLIC_UNION { 
  uint64_t dat[4]; // Allocate space
  double dummy_align1; // Force alignment
  void *dummy_align2; // Force alignment
} FOO;
void act_on_foo(FOO_PUBLIC_UNION*);

// In code
FOO x = {0};
act_on_foo(&x);

在一个编译单元中,类似于:

struct FOO_PRIVATE {
  int this; float that; double whatever;
};

typedef union FOO_PUBLIC_UNION { uint64_t dat[4]; struct FOO_PRIVATE priv; } FOOPP;

void act_on_foo(FOO *p)
{
  FOOPP *pp = (FOOPP*)p;
  pp->priv.whatever = 1234.567;
}

假设 FOOFOOPP 的大小匹配,从第一个编译单元调用外部函数的行为将被定义为分配 sizeof(FOO) 字节,将它们归零,并将它们的地址传递给 act_on_foo,其行为将被定义为作用于它接收到地址的字节,而不考虑它们如何获得它们的值或调用者稍后将如何处理它们。

不幸的是,尽管几乎每个实现都应该能够产生与调用它一无所知的函数一致的行为,但没有标准的方式向编译器指示特定的函数调用应该被视为 "opaque".用于有用目的的实现可以并且通常确实通过 "ordinary" 函数调用支持这种语义,无论标准是否要求,并且这种语义对仅用于它们不会的目的的实现几乎没有价值有用。不幸的是,这导致了第 22 条军规:标准没有理由强制执行可以自由执行的事情,无论是否强制执行,在它们有用的情况下,但一些编译器作者认为标准缺乏授权作为拒绝支持的鼓励。