如何在不公开C结构大小的情况下公开其结构大小?

时间:2019-02-20 08:38:46

标签: c

我在C语言中拥有以下内容(不是C ++!):

module.c
    struct Private {...};
    void foo(void* private, int param) {...}

module.h
    #define PRIVATE_SIZE ???;
    void foo(void* private, int param);

main.c
    char m1[PRIVATE_SIZE];
    char m2[PRIVATE_SIZE];

    int main()
    {
        foo(m1, 10);
        foo(m2, 20);
    }

如何在编译时公开sizeof(Private),以便应用程序可以 静态 分配其存储空间,而不公开私有类型?

请注意,这是一个非常有限的嵌入式系统,因此无法进行动态分配。

5 个答案:

答案 0 :(得分:4)

您不应向调用者公开该结构的大小,因为这破坏了首先具有私有封装的整个目的。分配您的私人数据与呼叫者无关。另外,请避免使用void*,因为它们完全缺乏类型安全性。

这是您在C中编写私有封装的方式:

  • 在module.h中,向前声明不完整的类型typedef struct module module;
  • 在module.c中,放置此结构的结构定义。它仅对module.c可见,而对调用者不可见。这称为不透明类型
  • 调用者只能分配指向该结构的指针,而不能分配对象。
  • 呼叫者代码可能类似于:

    #include "module.h"
    ...
    module* m;
    result = module_init(&m)
    
  • module_init函数充当“构造函数”,在module.h中声明并在module.c中定义:

    bool module_init (module** obj)
    {
      module* m = malloc(sizeof *m);
      ...
      m->something = ...; // init private variables if applicable
    
      *obj = m;
      return true;
    }
    
  • 如果调用者确实需要知道对象的大小,则仅用于硬拷贝等目的。如果有需要,请提供一个复制函数,该函数封装分配和复制(“复制构造函数”),例如:

    result module_copy (module** dst, const module* src);
    

编辑:

请注意,分配方式是一个单独的问题。您不必在上述设计中使用动态分配。例如,在嵌入式系统中,通常使用静态内存池代替。参见Static allocation of opaque data types

答案 1 :(得分:1)

在一致的C代码中,即使您在编译时知道其大小,也无法创建任意未知类型的静态实例(即使您知道对齐方式也是如此)。

假设您还是尝试这样做。给定宏或枚举PRIVATE_SIZE的大小,您将如何做?

unsigned char obj[PRIVATE_SIZE];

然后您将(void*)obj传递到需要的地方,对吗? 好吧,这违反了别名规则。尽管您可以合法地访问任何对象中的任何单个字符/字节,但您不能以相反的方式说这些字符不是字符,而是仅存储在其他类型之后。也就是说,在法律上,您不能通过聪明的裤子类型(例如short int)将obj[2]obj[3]叠加在((struct Private*)obj)->my_short = 2;memcpy()之上。进行此类操作的唯一合法方法是通过memcpy(&temp, obj, sizeof temp);,例如obj[],然后在修改后返回。或者,您需要使用unsigned char obj[PRIVATE_SIZE]的单个字符。

有两种可能的方式来做到这一点。在另一个答案中描述了一个,基本上定义了已知类型的实例,但只让外界拥有指向它的指针。

另一个非常相似,在汇编代码中对其进行定义,然后再次让外部世界找到它的指针。组装方式的“美”在于,您实际上只需要一个名称,一个对齐方式和一个大小即可为命名对象分配空间。

如果将实例放在特殊的数据段中(请参阅gcc的section属性和链接脚本),您甚至可以将所有实例放在同一位置(认为是数组),甚至可以找到它们的累积大小因此计数。

在不显着违反任何C规则的情况下,另一件事是仍然使用此// struct Private* launder(unsigned char*); .text .globl launder launder: move %first_param_reg, %return_reg ret 技巧,但是通过将其不变地传递给C编译器无法查看的汇编函数来清洗它,例如像

unsigned char obj[PRIVATE_SIZE]

但是您确实需要将double obj[PRIVATE_SIZE / sizeof(double)]更改为与您的架构具有适当一致性的内容,例如long long(如果您更喜欢这种方式,则与PRIVATE_SIZE相同)。

对于#include "mod.h" // mod.h defines PRIVATE_SIZE struct Private { ... }; extern char StAtIcAsSeRt[sizeof(struct Private) == PRIVATE_SIZE]; ,您可以在编译时检查它是否与类型的大小匹配,例如

 <div class="modal fade" id="myModal">
        <div class="modal-dialog">
            <div class="modal-content bmd-modalContent">
                <div class="modal-body">
                    <div class="close-button">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                    </div>
                    <div class="embed-responsive embed-responsive-16by9">
                        <iframe id="formIframe" class="embed-responsive-item" frameborder="0"></iframe>
                    </div>
                </div>
            </div><!-- /.modal-content -->
        </div><!-- /.modal-dialog -->
</div><!-- /.modal -->

答案 2 :(得分:1)

您不能为诸如此类的结构分配大小,因为在编译时尚不知道大小。即使您确实知道运行时的大小,但由于对齐问题,仍然会有问题。

有一个可能的解决方案,涉及定义一个与私有结构具有相同大小和对齐要求的单独结构。

例如:

module.h:

#include <inttypes.h>

struct Public {
    uint64_t opaque1;
    uint64_t opaque2;
    uint64_t opaque3;
};

void init(struct Public *p);

module.c:

#include <assert.h>
#include <stdalign.h>
#include "module.h"

struct Private {
    int a;
    double b;
    float c;
};

static_assert(sizeof(struct Private)==sizeof(struct Public), "sizes differ");
static_assert(alignof(struct Private)==alignof(struct Public), "alignments differ");

void init(struct Public *p)
{
    struct Private *pr = (struct Private *)p;
    pr->a = 2;
    pr->b = 2.5;
    pr->c = 2.4f;
}

保证PublicPrivate结构的大小相同,并且对齐方式相同。用户可能会编写公共结构的“不透明”字段,在这种情况下,您可能会遇到有关有效类型的别名问题,但是如果可以信任用户,则应该可以。


另一个更可靠的选择是,如果您对要支持的最大对象数有所了解。如果是这种情况,您可以在实现文件中包含这些对象的静态数组,并且init函数将返回指向此列表中的对象之一的指针。然后,您将拥有一个相关的清理功能,该功能可以释放实例。

例如:

module.c:

struct Private {
    int a;
    double b;
    float c;
};

struct PrivateAllocator {
    struct Private obj;
    int used;
};

struct PrivateAllocator list[5] = {
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 },
    { { 0, 0, 0}, 0 }
};

struct Private *private_init()
{
    int i;
    for (i=0; i<5; i++) {
        if (!list[i].used) {
            list[i].used = 1;
            return &list[i].obj;
        }
    }
    return NULL;
}

void private_free(struct Private *p)
{
    int i;
    for (i=0; i<5; i++) {
        if (&list[i].obj == p) {
            list[i].used = 0;
            return;
        }
    }
}

答案 3 :(得分:0)

  

如何在不公开C结构大小的情况下公开其C结构大小?

如果可以妥协一些:(静态-> main()本地)
对于可变长度数组(C99),请使用辅助函数并将该数组放入main()

module.h
  size_t foo_size(void);

main.c
  int main() {
    char m1[foo_size()];
    foo(m1, 10);
  }

解决对齐问题所需的其他工作。

考虑放松您的目标,成为suggested

答案 4 :(得分:0)

C99允许您使用可变长度数组。

private.h:

#include <stdio.h>
extern const size_t size;

private.c:

#include "private.h"
struct Private {
        int x;
        int y;
        int z;
};

const size_t size = sizeof(struct Private);

main.c:

#include <stdio.h>
#include "private.h"
int main(void) {
        char m1[size]; //variable length array
        printf("Size of m1 = %ld\n", sizeof(m1));
}