Opaque types allocatable on stack in C

不羁岁月 提交于 2019-11-29 04:02:43

What you desire is some kind of equivalent of the C++ private access control in C. As you know, no such equivalent exists. The approach you give is approximately what I would do. However, I would make the opaqueType opaque to the inner components implementing the type, so I would be forced to cast it to the real type within the inner components. The forced cast should not generate the warning you are mentioning.

Although cumbersome to use, you can define an interface that provides "stack allocated" memory to an opaque type without exposing a sized structure. The idea is that the implementation code is in charge of the stack allocation, and the user passes in a callback function to get a pointer to the allocated type.

typedef struct opaqueType_raii_callback opqaueType_raii_callback;
struct opaqueType_raii_callback {
    void (*func)(opqaueType_raii_callback *, opqaueType *);
};
extern void opaqueType_raii (opaqueType_raii_callback *);
extern void opaqueType_raii_v (opaqueType_raii_callback *, size_t);


void opaqueType_raii (opaqueType_raii_callback *cb) {
    opaqueType_raii_v(cb, 1);
}

void opqaueType_raii_v (opaqueType_raii_callback *cb, size_t n) {
    opaqueType x[n];
    cb->func(cb, x);
}

The definitions above look a bit esoteric, but it is the way I normally implement a callback interface.

struct foo_callback_data {
    opaqueType_raii_callback cb;
    int my_data;
    /* other data ... */
};

void foo_callback_function (opaqueType_raii_callback *cb, opaqueType *x) {
    struct foo_callback_data *data = (void *)cb;
    /* use x ... */
}

void foo () {
    struct foo_callback_data data;
    data.cb.func = foo_callback_function;
    opaqueType_raii(&data.cb);
}

You can force the alignment with max_align_t and you can avoid the strict aliasing issues using an array of char since char is explicitly allowed to alias any other type.

Something along the lines of:

#include <stdint.h>
struct opaque
{
    union
    {
        max_align_t a;
        char b[32]; // or whatever size you need.
    } u;
};

If you want to support compiler that do not have the max_align_t, or if you know the alignment requirements of the real type, then you can use any other type for the a union member.

UPDATE: If you are targetting C11, then you may also use alignas():

#include <stdint.h>
#include <stdalign.h>
struct opaque
{
    alignas(max_align_t) char b[32];
};

Of course, you can replace the max_align_t with whatever type you think appropriate. Or even an integer.

UPDATE #2:

Then, the use of this type in the library would be something along the lines of:

void public_function(struct opaque *po)
{
    struct private *pp = (struct private *)po->b;
    //use pp->...
}

This way, since you are type-punning a pointer to char you are not breaking the strict aliasing rules.

For me this seems to be something which just shouldn't be done.

The point of having an opaque pointer is to hide the implementation details. The type and alignment of memory where the actual structure is allocated, or whether the library manages additional data beyond what's pointed to are also implementation details.

Of course not that you couldn't document that one or another thing was possible, but the C language uses this approach (strict aliasing), which you can only more or less hack around by Rodrigo's answer (using max_align_t). By the rule you can't know by the interface what kind of constraints the particular compiler would impose on the actual structure within the implementation (for some esoteric microcontrollers, even the type of memory may matter), so I don't think this can be done reliably in a truly cross platform manner.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!