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

时间:2021-09-29 16:46:51

Very often malloc() is absolutely not allowed when programming for embedded systems. Most of the time I'm pretty able to deal with this, but one thing irritates me: it keeps me from using so called 'opaque types' to enable data hiding. Normally I'd do something like this:

在为嵌入式系统编程时,malloc()通常是绝对不允许的。大多数时候,我都能处理这个问题,但有一件事让我很恼火:它阻止我使用所谓的“不透明类型”来支持数据隐藏。通常我会这样做:

// In file module.h
typedef struct handle_t handle_t;

handle_t *create_handle();
void operation_on_handle(handle_t *handle, int an_argument);
void another_operation_on_handle(handle_t *handle, char etcetera);
void close_handle(handle_t *handle);


// In file module.c
struct handle_t {
    int foo;
    void *something;
    int another_implementation_detail;
};

handle_t *create_handle() {
    handle_t *handle = malloc(sizeof(struct handle_t));
    // other initialization
    return handle;
}

There you go: create_handle() performs a malloc() to create an 'instance'. A construction often used to prevent having to malloc() is to change the prototype of create_handle() like this:

这里有:create_handle()执行malloc()来创建一个“实例”。通常用来防止malloc()的构造是更改create_handle()的原型:

void create_handle(handle_t *handle);

And then the caller could create the handle this way:

然后调用者可以这样创建句柄:

// In file caller.c
void i_am_the_caller() {
    handle_t a_handle;    // Allocate a handle on the stack instead of malloc()
    create_handle(&a_handle);
    // ... a_handle is ready to go!
}

But unfortunately this code is obviously invalid, the size of handle_t isn't known!

但是不幸的是,这段代码显然是无效的,handle_t的大小是未知的!

I never really found a solution to solve this in a proper way. I'd very like to know if anyone has a proper way of doing this, or maybe a complete different approach to enable data hiding in C (not using static globals in the module.c of course, one must be able to create multiple instances).

我从来没有找到一个合适的方法来解决这个问题。我很想知道是否有人有合适的方法,或者可能有一种完全不同的方法来启用隐藏在C中的数据(而不是在模块中使用静态全局变量)。当然,必须能够创建多个实例)。

9 个解决方案

#1


15  

You can use the _alloca function. I believe that it's not exactly Standard, but as far as I know, nearly all common compilers implement it. When you use it as a default argument, it allocates off the caller's stack.

可以使用_alloca函数。我认为它并不完全是标准的,但据我所知,几乎所有的公共编译器都实现了它。当您使用它作为默认参数时,它会分配调用者的堆栈。

// Header
typedef struct {} something;
int get_size();
something* create_something(void* mem);

// Usage
handle* ptr = create_something(_alloca(get_size()); // or define a macro.

// Implementation
int get_size() {
    return sizeof(real_handle_type);
}
something* create_something(void* mem) {
    real_type* ptr = (real_type_ptr*)mem;
    // Fill out real_type
    return (something*)mem;
}

You could also use some kind of object pool semi-heap - if you have a maximum number of currently available objects, then you could allocate all memory for them statically, and just bit-shift for which ones are currently in use.

您还可以使用某种对象池半堆——如果您有当前可用对象的最大数量,那么您可以静态地为它们分配所有内存,并且只对当前正在使用的对象进行位移。

#define MAX_OBJECTS 32
real_type objects[MAX_OBJECTS];
unsigned int in_use; // Make sure this is large enough
something* create_something() {
     for(int i = 0; i < MAX_OBJECTS; i++) {
         if (!(in_use & (1 << i))) {
             in_use &= (1 << i);
             return &objects[i];
         }
     }
     return NULL;
}

My bit-shifting is a little off, been a long time since I've done it, but I hope that you get the point.

我的位移有点不太对劲,我已经很久没这么做了,但我希望你能明白我的意思。

#2


8  

One way would be to add something like

一种方法是添加一些类似的东西

#define MODULE_HANDLE_SIZE (4711)

to the public module.h header. Since that creates a worrying requirement of keeping this in sync with the actual size, the line is of course best auto-generated by the build process.

公共模块。h头。由于这产生了一个令人担忧的需求,即保持与实际大小保持同步,因此最好是由构建过程自动生成代码行。

The other option is of course to actually expose the structure, but document it as being opaque and forbidding access through any other means than through the defined API. This can be made more clear by doing something like:

另一种选择当然是公开结构,但将其记录为不透明的,并禁止通过定义的API以外的任何其他方式进行访问。这一点可以通过以下方法来更明确:

#include "module_private.h"

typedef struct
{
  handle_private_t private;
} handle_t;

Here, the actual declaration of the module's handle has been moved into a separate header, to make it less obviously visible. A type declared in that header is then simply wrapped in the desired typedef name, making sure to indicate that it is private.

在这里,模块句柄的实际声明被移动到一个单独的标题中,以使其不明显可见。然后将在该头中声明的类型封装到所需的typedef名称中,确保它是私有的。

Functions inside the module that take handle_t * can safely access private as a handle_private_t value, since it's the first member of the public struct.

使用handle_t *的模块内部的函数可以安全地作为handle_private_t值访问private,因为它是公共结构的第一个成员。

#3


4  

One solution if to create a static pool of struct handle_t objects, and provide then as neceessary. There are many ways to achieve that, but a simple illustrative example follows:

一个解决方案是创建一个静态的struct handle_t对象池,然后作为必要条件提供。实现这一目标的方法有很多,但一个简单的示例如下:

// In file module.c
struct handle_t 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_t handle_pool[MAX_HANDLES] ;

handle_t* create_handle() 
{
    int h ;
    handle_t* handle = 0 ;
    for( h = 0; handle == 0 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = &handle_pool[h] ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t* handle ) 
{
    handle->in_use = 0 ;
}

There are faster faster ways of finding an unused handle, you could for example keep a static index that increments each time a handle is allocated and 'wraps-around' when it reaches MAX_HANDLES; this would be faster for the typical situation where several handles are allocated before releasing any one. For a small number of handles however, this brute-force search is probably adequate.

找到未使用的句柄有更快的方法,例如,您可以保持一个静态索引,每次分配一个句柄时都递增,当它到达max_handle时“包装”;对于典型的情况,在释放任何一个句柄之前,这将会更快。然而,对于少量的句柄来说,这种蛮力搜索可能就足够了。

Of course the handle itself need no longer be a pointer but could be a simple index into the hidden pool. This would enhance data hiding and protection of the pool from external access.

当然,句柄本身不再需要是指针,而是可以是隐藏池中的简单索引。这将增强数据隐藏和保护池的外部访问。

So the header would have:

标题应该是:

typedef int handle_t ;

and the code would change as follows:

代码的变化如下:

// In file module.c
struct handle_s 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_s handle_pool[MAX_HANDLES] ;

handle_t create_handle() 
{
    int h ;
    handle_t handle = -1 ;
    for( h = 0; handle != -1 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = h ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t handle ) 
{
    handle_pool[handle].in_use = 0 ;
}

Because the handle returned is no longer a pointer to the internal data, and inquisitive or malicious user cannnot gain access to it through the handle.

因为返回的句柄不再是指向内部数据的指针,而且好奇或恶意的用户无法通过句柄访问它。

Note that you may need to add some thread-safety mechanisms if you are getting handles in multiple threads.

注意,如果您在多个线程中获得了句柄,您可能需要添加一些线程安全机制。

#4


3  

Unfortunately, I think the typical way to deal with this problem is by simply having the programmer treat the object as opaque - the full structure implementation is in the header and available, it's just the responsibility of the programmer to not use the internals directly, only through the APIs defined for the object.

不幸的是,我认为典型的处理这个问题的方法是简单的程序员把对象作为不透明——完整的结构实现在头和可用,这是程序员的责任直接不使用内部,只有通过为对象定义的api。

If this isn't good enough, a few options might be:

如果这还不够好,有几个选择可能是:

  • use C++ as a 'better C' and declare the internals of the structure as private.
  • 使用c++作为“更好的C”,并将结构的内部声明为private。
  • run some sort of pre-processor on the headers so that the internals of the structure are declared, but with unusable names. The original header, with good names, will be available to the implementation of the APIs that manage the structure. I've never seen this technique used - it's just an idea off the top of my head that might be possible, but seems like far more trouble than it's worth.
  • 在页眉上运行某种预处理器,以便声明结构的内部结构,但使用不可用的名称。最初的标头具有良好的名称,可用于管理结构的api的实现。我从未见过这种技术的使用——它只是我脑海中一个想法,可能是可能的,但似乎比它的价值要麻烦得多。
  • have your code that uses opaque pointers declare the statically allocated objects as extern (ie., globals) Then have a special module that has access to the full definition of the object actually declare these objects. Since only the 'special' module has access to the full definition, the normal use of the opaque object remains opaque. However, now you have to rely on your programmers to not abuse the fact that thee objects are global. You have also increased the change of naming collisions, so that need to be managed (probably not a big problem, except that it might occur unintentionally - ouch!).
  • 让使用不透明指针的代码将静态分配的对象声明为extern(即extern)。然后有一个特殊的模块,该模块可以访问对象的完整定义,实际声明这些对象。因为只有“特殊”模块可以访问完整的定义,所以不透明对象的正常使用仍然是不透明的。然而,现在您必须依赖您的程序员不要滥用您的对象是全局的这一事实。您还增加了命名冲突的更改,因此需要对其进行管理(可能不是什么大问题,除非它可能无意中发生——哎哟!)

I think overall, just relying on your programmers to follow the rules for the use of these objects might be the best solution (though using a subset of C++ isn't bad either in my opinion). Depending on your programmers to follow the rules of not using the structure internals isn't perfect, but it's a workable solution that is in common use.

我认为,总的来说,仅仅依靠程序员来遵循这些对象的使用规则可能是最好的解决方案(尽管在我看来,使用c++的一个子集也不错)。依赖于您的程序员遵循不使用结构内部的规则并不是完美的,但是这是一个通用的可行的解决方案。

#5


1  

I faced a similar problem in implementing a data structure in which the header of the data structure, which is opaque, holds all the various data that needs to be carried over from operation to operation.

在实现数据结构的过程中,我遇到了类似的问题,数据结构的头部是不透明的,它包含了所有需要从操作转移到操作的数据。

Since re-initialization might cause a memory leak, I wanted to make sure that data structure implementation itself never actually overwrite a point to heap allocated memory.

由于重新初始化可能会导致内存泄漏,所以我希望确保数据结构实现本身不会实际覆盖到堆分配的内存。

What I did is the following:

我所做的是:

/** 
 * In order to allow the client to place the data structure header on the
 * stack we need data structure header size. [1/4]
**/
#define CT_HEADER_SIZE  ( (sizeof(void*) * 2)           \
                        + (sizeof(int) * 2)             \
                        + (sizeof(unsigned long) * 1)   \
                        )

/**
 * After the size has been produced, a type which is a size *alias* of the
 * header can be created. [2/4] 
**/        
struct header { char h_sz[CT_HEADER_SIZE]; };
typedef struct header data_structure_header;

/* In all the public interfaces the size alias is used. [3/4] */
bool ds_init_new(data_structure_header *ds /* , ...*/);

In the implementation file:

实现文件:

struct imp_header {
    void *ptr1, 
         *ptr2;
    int  i, 
         max;
    unsigned long total;
};

/* implementation proper */
static bool imp_init_new(struct imp_header *head /* , ...*/)
{
    return false; 
}

/* public interface */
bool ds_init_new(data_structure_header *ds /* , ...*/) 
{
    int i;

    /* only accept a zero init'ed header */
    for(i = 0; i < CT_HEADER_SIZE; ++i) {
        if(ds->h_sz[i] != 0) {
            return false;
        }
    }

    /* just in case we forgot something */
    assert(sizeof(data_structure_header) == sizeof(struct imp_header));

    /* Explicit conversion is used from the public interface to the
     * implementation proper.  [4/4]
     */
    return imp_init_new( (struct imp_header *)ds /* , ...*/); 
}

client side:

客户端:

int foo() 
{
    data_structure_header ds = { 0 };

    ds_init_new(&ds /*, ...*/);
}

#6


0  

I'm a little confused why you say you can't use malloc(). Obviously on an embedded system you have limited memory and the usual solution is to have your own memory manager which mallocs a large memory pool and then allocates chunks of this out as needed. I've seen various different implementations of this idea in my time.

我有点搞不懂你为什么说不能使用malloc()。显然,在嵌入式系统中,内存是有限的,通常的解决方案是拥有自己的内存管理器,该管理器会错误地分配一个大的内存池,然后根据需要分配内存块。在我的时间里,我见过各种不同的实现方法。

To answer your question though, why don't you simply statically allocate a fixed size array of them in module.c add an "in-use" flag, and then have create_handle() simply return the pointer to the first free element.

要回答您的问题,为什么不简单地在模块中静态分配一个固定大小的数组呢?c添加一个“in-use”标志,然后使用create_handle()只返回指向第一个空闲元素的指针。

As an extension to this idea, the "handle" could then be an integer index rather than the actual pointer which avoids any chance of the user trying to abuse it by casting it to their own definition of the object.

作为这种思想的扩展,“句柄”可以是一个整数索引,而不是实际的指针,这样用户就可以通过将其强制转换为自己的对象定义来避免滥用它。

#7


0  

The least grim solution I've seen to this has been to provide an opaque struct for the caller's use, which is large enough, plus maybe a bit, along with a mention of the types used in the real struct, to ensure that the opaque struct will be aligned well enough compared to the real one:

最残酷的解决方案我看过这是为调用者的使用提供一个不透明的结构,这是足够大的,再加上也许有点,以及提到真正的结构中使用的类型,以确保不透明的结构将对齐充分相比,真正的一个:

struct Thing {
    union {
        char data[16];
        uint32_t b;
        uint8_t a;
    } opaque;
};
typedef struct Thing Thing;

Then functions take a pointer to one of those:

然后函数将指针指向其中一个:

void InitThing(Thing *thing);
void DoThingy(Thing *thing,float whatever);

Internally, not exposed as part of the API, there is a struct that has the true internals:

在内部,没有作为API的一部分公开,有一个结构具有真正的内部:

struct RealThing {
    uint32_t private1,private2,private3;
    uint8_t private4;
};
typedef struct RealThing RealThing;

(This one just has uint32_t' anduint8_t' -- that's the reason for the appearance of these two types in the union above.)

(这一个是uint32_t' anduint8_t'——这就是上面这两种类型出现的原因。)

Plus probably a compile-time assert to make sure that RealThing's size doesn't exceed that of Thing:

再加上一个编译时断言,以确保RealThing的大小不会超过它的大小:

typedef char CheckRealThingSize[sizeof(RealThing)<=sizeof(Thing)?1:-1];

Then each function in the library does a cast on its argument when it's going to use it:

然后库中的每个函数在它将要使用它的时候对它的参数做一个转换:

void InitThing(Thing *thing) {
    RealThing *t=(RealThing *)thing;

    /* stuff with *t */
}

With this in place, the caller can create objects of the right size on the stack, and call functions against them, the struct is still opaque, and there's some checking that the opaque version is large enough.

有了这一点,调用者就可以在堆栈上创建大小合适的对象,并对它们调用函数,结构仍然是不透明的,并且可以检查不透明版本是否足够大。

One potential issue is that fields could be inserted into the real struct that mean it requires an alignment that the opaque struct doesn't, and this won't necessarily trip the size check. Many such changes will change the struct's size, so they'll get caught, but not all. I'm not sure of any solution to this.

一个潜在的问题是,可以将字段插入到真正的结构中,这意味着它需要一个不透明结构不需要的对齐,而这并不一定会导致大小检查。许多这样的更改将改变结构体的大小,因此它们将被捕获,但不是全部。我不确定有什么解决办法。

Alternatively, if you have a special public-facing header(s) that the library never includes itself, then you can probably (subject to testing against the compilers you support...) just write your public prototypes with one type and your internal ones with the other. It would still be a good idea to structure the headers so that the library sees the public-facing Thing struct somehow, though, so that its size can be checked.

或者,如果您有一个特殊的面向公众的头(s),而这个库从来没有包含它自己,那么您可能(需要测试您支持的编译器),只需将您的公共原型与一个类型和您的内部原型一起编写。尽管如此,构造头部结构使库以某种方式看到面向公共的东西结构仍然是一个好主意,以便检查它的大小。

#8


0  

It is simple, simply put the structs in a privateTypes.h header file. It will not be opaque anymore, still, it will be private to the programmer, since it is inside a private file.

很简单,只需将结构体放在私有类型中。h头文件。它将不再是不透明的,但是,它将是程序员的私有,因为它在一个私有文件中。

An example here: Hiding members in a C struct

这里的一个示例:将成员隐藏在C结构体中

#9


0  

This is an old question, but since it's also biting me, I wanted to provide here a possible answer (which I'm using).

这是一个老问题,但由于它也让我感到困扰,我想在这里提供一个可能的答案(我正在使用)。

So here is an example :

这里有一个例子

// file.h
typedef struct { size_t space[3]; } publicType;
int doSomething(publicType* object);

// file.c
typedef struct { unsigned var1; int var2; size_t var3; } privateType;

int doSomething(publicType* object)
{
    privateType* obPtr  = (privateType*) object;
    (...)
}

Advantages : publicType can be allocated on stack.

优点:可以在堆栈上分配publicType。

Note that correct underlying type must be selected in order to ensure proper alignment (i.e. don't use char). Note also that sizeof(publicType) >= sizeof(privateType). I suggest a static assert to make sure this condition is always checked. As a final note, if you believe your structure may evolve later on, don't hesitate to make the public type a bit bigger, to keep room for future expansions without breaking ABI.

注意,必须选择正确的底层类型,以确保正确的对齐(即不使用char)。还要注意sizeof(publicType) >= sizeof(privateType)。我建议使用静态断言,以确保始终检查此条件。最后要注意的是,如果你相信你的结构以后可能会发展,那么请毫不犹豫地让公共类型变得更大一点,在不破坏ABI的情况下为未来的扩展留出空间。

Disadvantage : The casting from public to private type can trigger strict aliasing warnings.

缺点:从public到private类型的转换可以触发严格的别名警告。

I discovered later on that this method has similarities with struct sockaddr within BSD socket, which meets basically the same problem with strict aliasing warnings.

后来我发现该方法与BSD套接字中的struct sockaddr有相似之处,它基本上满足了严格的别名警告的相同问题。

#1


15  

You can use the _alloca function. I believe that it's not exactly Standard, but as far as I know, nearly all common compilers implement it. When you use it as a default argument, it allocates off the caller's stack.

可以使用_alloca函数。我认为它并不完全是标准的,但据我所知,几乎所有的公共编译器都实现了它。当您使用它作为默认参数时,它会分配调用者的堆栈。

// Header
typedef struct {} something;
int get_size();
something* create_something(void* mem);

// Usage
handle* ptr = create_something(_alloca(get_size()); // or define a macro.

// Implementation
int get_size() {
    return sizeof(real_handle_type);
}
something* create_something(void* mem) {
    real_type* ptr = (real_type_ptr*)mem;
    // Fill out real_type
    return (something*)mem;
}

You could also use some kind of object pool semi-heap - if you have a maximum number of currently available objects, then you could allocate all memory for them statically, and just bit-shift for which ones are currently in use.

您还可以使用某种对象池半堆——如果您有当前可用对象的最大数量,那么您可以静态地为它们分配所有内存,并且只对当前正在使用的对象进行位移。

#define MAX_OBJECTS 32
real_type objects[MAX_OBJECTS];
unsigned int in_use; // Make sure this is large enough
something* create_something() {
     for(int i = 0; i < MAX_OBJECTS; i++) {
         if (!(in_use & (1 << i))) {
             in_use &= (1 << i);
             return &objects[i];
         }
     }
     return NULL;
}

My bit-shifting is a little off, been a long time since I've done it, but I hope that you get the point.

我的位移有点不太对劲,我已经很久没这么做了,但我希望你能明白我的意思。

#2


8  

One way would be to add something like

一种方法是添加一些类似的东西

#define MODULE_HANDLE_SIZE (4711)

to the public module.h header. Since that creates a worrying requirement of keeping this in sync with the actual size, the line is of course best auto-generated by the build process.

公共模块。h头。由于这产生了一个令人担忧的需求,即保持与实际大小保持同步,因此最好是由构建过程自动生成代码行。

The other option is of course to actually expose the structure, but document it as being opaque and forbidding access through any other means than through the defined API. This can be made more clear by doing something like:

另一种选择当然是公开结构,但将其记录为不透明的,并禁止通过定义的API以外的任何其他方式进行访问。这一点可以通过以下方法来更明确:

#include "module_private.h"

typedef struct
{
  handle_private_t private;
} handle_t;

Here, the actual declaration of the module's handle has been moved into a separate header, to make it less obviously visible. A type declared in that header is then simply wrapped in the desired typedef name, making sure to indicate that it is private.

在这里,模块句柄的实际声明被移动到一个单独的标题中,以使其不明显可见。然后将在该头中声明的类型封装到所需的typedef名称中,确保它是私有的。

Functions inside the module that take handle_t * can safely access private as a handle_private_t value, since it's the first member of the public struct.

使用handle_t *的模块内部的函数可以安全地作为handle_private_t值访问private,因为它是公共结构的第一个成员。

#3


4  

One solution if to create a static pool of struct handle_t objects, and provide then as neceessary. There are many ways to achieve that, but a simple illustrative example follows:

一个解决方案是创建一个静态的struct handle_t对象池,然后作为必要条件提供。实现这一目标的方法有很多,但一个简单的示例如下:

// In file module.c
struct handle_t 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_t handle_pool[MAX_HANDLES] ;

handle_t* create_handle() 
{
    int h ;
    handle_t* handle = 0 ;
    for( h = 0; handle == 0 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = &handle_pool[h] ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t* handle ) 
{
    handle->in_use = 0 ;
}

There are faster faster ways of finding an unused handle, you could for example keep a static index that increments each time a handle is allocated and 'wraps-around' when it reaches MAX_HANDLES; this would be faster for the typical situation where several handles are allocated before releasing any one. For a small number of handles however, this brute-force search is probably adequate.

找到未使用的句柄有更快的方法,例如,您可以保持一个静态索引,每次分配一个句柄时都递增,当它到达max_handle时“包装”;对于典型的情况,在释放任何一个句柄之前,这将会更快。然而,对于少量的句柄来说,这种蛮力搜索可能就足够了。

Of course the handle itself need no longer be a pointer but could be a simple index into the hidden pool. This would enhance data hiding and protection of the pool from external access.

当然,句柄本身不再需要是指针,而是可以是隐藏池中的简单索引。这将增强数据隐藏和保护池的外部访问。

So the header would have:

标题应该是:

typedef int handle_t ;

and the code would change as follows:

代码的变化如下:

// In file module.c
struct handle_s 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_s handle_pool[MAX_HANDLES] ;

handle_t create_handle() 
{
    int h ;
    handle_t handle = -1 ;
    for( h = 0; handle != -1 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = h ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t handle ) 
{
    handle_pool[handle].in_use = 0 ;
}

Because the handle returned is no longer a pointer to the internal data, and inquisitive or malicious user cannnot gain access to it through the handle.

因为返回的句柄不再是指向内部数据的指针,而且好奇或恶意的用户无法通过句柄访问它。

Note that you may need to add some thread-safety mechanisms if you are getting handles in multiple threads.

注意,如果您在多个线程中获得了句柄,您可能需要添加一些线程安全机制。

#4


3  

Unfortunately, I think the typical way to deal with this problem is by simply having the programmer treat the object as opaque - the full structure implementation is in the header and available, it's just the responsibility of the programmer to not use the internals directly, only through the APIs defined for the object.

不幸的是,我认为典型的处理这个问题的方法是简单的程序员把对象作为不透明——完整的结构实现在头和可用,这是程序员的责任直接不使用内部,只有通过为对象定义的api。

If this isn't good enough, a few options might be:

如果这还不够好,有几个选择可能是:

  • use C++ as a 'better C' and declare the internals of the structure as private.
  • 使用c++作为“更好的C”,并将结构的内部声明为private。
  • run some sort of pre-processor on the headers so that the internals of the structure are declared, but with unusable names. The original header, with good names, will be available to the implementation of the APIs that manage the structure. I've never seen this technique used - it's just an idea off the top of my head that might be possible, but seems like far more trouble than it's worth.
  • 在页眉上运行某种预处理器,以便声明结构的内部结构,但使用不可用的名称。最初的标头具有良好的名称,可用于管理结构的api的实现。我从未见过这种技术的使用——它只是我脑海中一个想法,可能是可能的,但似乎比它的价值要麻烦得多。
  • have your code that uses opaque pointers declare the statically allocated objects as extern (ie., globals) Then have a special module that has access to the full definition of the object actually declare these objects. Since only the 'special' module has access to the full definition, the normal use of the opaque object remains opaque. However, now you have to rely on your programmers to not abuse the fact that thee objects are global. You have also increased the change of naming collisions, so that need to be managed (probably not a big problem, except that it might occur unintentionally - ouch!).
  • 让使用不透明指针的代码将静态分配的对象声明为extern(即extern)。然后有一个特殊的模块,该模块可以访问对象的完整定义,实际声明这些对象。因为只有“特殊”模块可以访问完整的定义,所以不透明对象的正常使用仍然是不透明的。然而,现在您必须依赖您的程序员不要滥用您的对象是全局的这一事实。您还增加了命名冲突的更改,因此需要对其进行管理(可能不是什么大问题,除非它可能无意中发生——哎哟!)

I think overall, just relying on your programmers to follow the rules for the use of these objects might be the best solution (though using a subset of C++ isn't bad either in my opinion). Depending on your programmers to follow the rules of not using the structure internals isn't perfect, but it's a workable solution that is in common use.

我认为,总的来说,仅仅依靠程序员来遵循这些对象的使用规则可能是最好的解决方案(尽管在我看来,使用c++的一个子集也不错)。依赖于您的程序员遵循不使用结构内部的规则并不是完美的,但是这是一个通用的可行的解决方案。

#5


1  

I faced a similar problem in implementing a data structure in which the header of the data structure, which is opaque, holds all the various data that needs to be carried over from operation to operation.

在实现数据结构的过程中,我遇到了类似的问题,数据结构的头部是不透明的,它包含了所有需要从操作转移到操作的数据。

Since re-initialization might cause a memory leak, I wanted to make sure that data structure implementation itself never actually overwrite a point to heap allocated memory.

由于重新初始化可能会导致内存泄漏,所以我希望确保数据结构实现本身不会实际覆盖到堆分配的内存。

What I did is the following:

我所做的是:

/** 
 * In order to allow the client to place the data structure header on the
 * stack we need data structure header size. [1/4]
**/
#define CT_HEADER_SIZE  ( (sizeof(void*) * 2)           \
                        + (sizeof(int) * 2)             \
                        + (sizeof(unsigned long) * 1)   \
                        )

/**
 * After the size has been produced, a type which is a size *alias* of the
 * header can be created. [2/4] 
**/        
struct header { char h_sz[CT_HEADER_SIZE]; };
typedef struct header data_structure_header;

/* In all the public interfaces the size alias is used. [3/4] */
bool ds_init_new(data_structure_header *ds /* , ...*/);

In the implementation file:

实现文件:

struct imp_header {
    void *ptr1, 
         *ptr2;
    int  i, 
         max;
    unsigned long total;
};

/* implementation proper */
static bool imp_init_new(struct imp_header *head /* , ...*/)
{
    return false; 
}

/* public interface */
bool ds_init_new(data_structure_header *ds /* , ...*/) 
{
    int i;

    /* only accept a zero init'ed header */
    for(i = 0; i < CT_HEADER_SIZE; ++i) {
        if(ds->h_sz[i] != 0) {
            return false;
        }
    }

    /* just in case we forgot something */
    assert(sizeof(data_structure_header) == sizeof(struct imp_header));

    /* Explicit conversion is used from the public interface to the
     * implementation proper.  [4/4]
     */
    return imp_init_new( (struct imp_header *)ds /* , ...*/); 
}

client side:

客户端:

int foo() 
{
    data_structure_header ds = { 0 };

    ds_init_new(&ds /*, ...*/);
}

#6


0  

I'm a little confused why you say you can't use malloc(). Obviously on an embedded system you have limited memory and the usual solution is to have your own memory manager which mallocs a large memory pool and then allocates chunks of this out as needed. I've seen various different implementations of this idea in my time.

我有点搞不懂你为什么说不能使用malloc()。显然,在嵌入式系统中,内存是有限的,通常的解决方案是拥有自己的内存管理器,该管理器会错误地分配一个大的内存池,然后根据需要分配内存块。在我的时间里,我见过各种不同的实现方法。

To answer your question though, why don't you simply statically allocate a fixed size array of them in module.c add an "in-use" flag, and then have create_handle() simply return the pointer to the first free element.

要回答您的问题,为什么不简单地在模块中静态分配一个固定大小的数组呢?c添加一个“in-use”标志,然后使用create_handle()只返回指向第一个空闲元素的指针。

As an extension to this idea, the "handle" could then be an integer index rather than the actual pointer which avoids any chance of the user trying to abuse it by casting it to their own definition of the object.

作为这种思想的扩展,“句柄”可以是一个整数索引,而不是实际的指针,这样用户就可以通过将其强制转换为自己的对象定义来避免滥用它。

#7


0  

The least grim solution I've seen to this has been to provide an opaque struct for the caller's use, which is large enough, plus maybe a bit, along with a mention of the types used in the real struct, to ensure that the opaque struct will be aligned well enough compared to the real one:

最残酷的解决方案我看过这是为调用者的使用提供一个不透明的结构,这是足够大的,再加上也许有点,以及提到真正的结构中使用的类型,以确保不透明的结构将对齐充分相比,真正的一个:

struct Thing {
    union {
        char data[16];
        uint32_t b;
        uint8_t a;
    } opaque;
};
typedef struct Thing Thing;

Then functions take a pointer to one of those:

然后函数将指针指向其中一个:

void InitThing(Thing *thing);
void DoThingy(Thing *thing,float whatever);

Internally, not exposed as part of the API, there is a struct that has the true internals:

在内部,没有作为API的一部分公开,有一个结构具有真正的内部:

struct RealThing {
    uint32_t private1,private2,private3;
    uint8_t private4;
};
typedef struct RealThing RealThing;

(This one just has uint32_t' anduint8_t' -- that's the reason for the appearance of these two types in the union above.)

(这一个是uint32_t' anduint8_t'——这就是上面这两种类型出现的原因。)

Plus probably a compile-time assert to make sure that RealThing's size doesn't exceed that of Thing:

再加上一个编译时断言,以确保RealThing的大小不会超过它的大小:

typedef char CheckRealThingSize[sizeof(RealThing)<=sizeof(Thing)?1:-1];

Then each function in the library does a cast on its argument when it's going to use it:

然后库中的每个函数在它将要使用它的时候对它的参数做一个转换:

void InitThing(Thing *thing) {
    RealThing *t=(RealThing *)thing;

    /* stuff with *t */
}

With this in place, the caller can create objects of the right size on the stack, and call functions against them, the struct is still opaque, and there's some checking that the opaque version is large enough.

有了这一点,调用者就可以在堆栈上创建大小合适的对象,并对它们调用函数,结构仍然是不透明的,并且可以检查不透明版本是否足够大。

One potential issue is that fields could be inserted into the real struct that mean it requires an alignment that the opaque struct doesn't, and this won't necessarily trip the size check. Many such changes will change the struct's size, so they'll get caught, but not all. I'm not sure of any solution to this.

一个潜在的问题是,可以将字段插入到真正的结构中,这意味着它需要一个不透明结构不需要的对齐,而这并不一定会导致大小检查。许多这样的更改将改变结构体的大小,因此它们将被捕获,但不是全部。我不确定有什么解决办法。

Alternatively, if you have a special public-facing header(s) that the library never includes itself, then you can probably (subject to testing against the compilers you support...) just write your public prototypes with one type and your internal ones with the other. It would still be a good idea to structure the headers so that the library sees the public-facing Thing struct somehow, though, so that its size can be checked.

或者,如果您有一个特殊的面向公众的头(s),而这个库从来没有包含它自己,那么您可能(需要测试您支持的编译器),只需将您的公共原型与一个类型和您的内部原型一起编写。尽管如此,构造头部结构使库以某种方式看到面向公共的东西结构仍然是一个好主意,以便检查它的大小。

#8


0  

It is simple, simply put the structs in a privateTypes.h header file. It will not be opaque anymore, still, it will be private to the programmer, since it is inside a private file.

很简单,只需将结构体放在私有类型中。h头文件。它将不再是不透明的,但是,它将是程序员的私有,因为它在一个私有文件中。

An example here: Hiding members in a C struct

这里的一个示例:将成员隐藏在C结构体中

#9


0  

This is an old question, but since it's also biting me, I wanted to provide here a possible answer (which I'm using).

这是一个老问题,但由于它也让我感到困扰,我想在这里提供一个可能的答案(我正在使用)。

So here is an example :

这里有一个例子

// file.h
typedef struct { size_t space[3]; } publicType;
int doSomething(publicType* object);

// file.c
typedef struct { unsigned var1; int var2; size_t var3; } privateType;

int doSomething(publicType* object)
{
    privateType* obPtr  = (privateType*) object;
    (...)
}

Advantages : publicType can be allocated on stack.

优点:可以在堆栈上分配publicType。

Note that correct underlying type must be selected in order to ensure proper alignment (i.e. don't use char). Note also that sizeof(publicType) >= sizeof(privateType). I suggest a static assert to make sure this condition is always checked. As a final note, if you believe your structure may evolve later on, don't hesitate to make the public type a bit bigger, to keep room for future expansions without breaking ABI.

注意,必须选择正确的底层类型,以确保正确的对齐(即不使用char)。还要注意sizeof(publicType) >= sizeof(privateType)。我建议使用静态断言,以确保始终检查此条件。最后要注意的是,如果你相信你的结构以后可能会发展,那么请毫不犹豫地让公共类型变得更大一点,在不破坏ABI的情况下为未来的扩展留出空间。

Disadvantage : The casting from public to private type can trigger strict aliasing warnings.

缺点:从public到private类型的转换可以触发严格的别名警告。

I discovered later on that this method has similarities with struct sockaddr within BSD socket, which meets basically the same problem with strict aliasing warnings.

后来我发现该方法与BSD套接字中的struct sockaddr有相似之处,它基本上满足了严格的别名警告的相同问题。