在C中的堆栈上可分配的不透明类型

时间:2021-02-21 17:00:42

When designing a C interface, it is common to let into the public interface (.h) only what needs to be known by the user program.

在设计C接口时,通常只允许公共接口(.h)进入用户程序需要知道的内容。

Hence for example, the inner components of structures should remain hidden if the user program does not need to know them. This is indeed good practice, as the content and behavior of the struct could change in the future, without affecting the interface.

因此,例如,如果用户程序不需要知道它们,则结构的内部组件应保持隐藏。这确实是一种很好的做法,因为结构的内容和行为在将来可能会发生变化,而不会影响界面。

A great way to achieve that objective is to use incomplete types.

实现该目标的一个好方法是使用不完整的类型。

typedef struct foo opaqueType;

typedef struct foo opaqueType;

Now an interface using only pointers to opaqueType can be built, without the user program ever needing to know the inner working of struct foo.

现在可以构建仅使用指向opaqueType的指针的接口,而用户程序不需要知道struct foo的内部工作。

But sometimes, it can be required to allocate such structure statically, typically on stack, for performance and memory fragmentation issues. Obviously, with above construction, opaqueType is incomplete, so its size is unknown, so it cannot be statically allocated.

但有时,可能需要静态地(通常在堆栈上)分配此类结构,以解决性能和内存碎片问题。显然,在上面的构造中,opaqueType是不完整的,因此它的大小是未知的,所以它不能静态分配。

A work around is to allocate a "shell type", such as :

解决方法是分配“shell类型”,例如:

typedef struct { int faketable[8]; } opaqueType;

typedef struct {int faketable [8]; } opaqueType;

Above construction enforces a size and an alignment, but doesn't go farther into describing what the structure really contains. So it matches the objective of keeping the type "opaque".

上面的构造强制执行大小和对齐,但不会进一步描述结构真正包含的内容。因此它符合保持“不透明”类型的目标。

It mostly works. But in one circumstance (GCC 4.4), the compiler complains that it breaks strict-aliasing, and it generates buggy binary.

它主要起作用。但在一种情况下(GCC 4.4),编译器抱怨它打破了严格别名,并且它生成了错误的二进制文件。

Now, I've read a ton of things about strict aliasing, so I guess I understand now what it means.

现在,我已经阅读了大量关于严格混叠的内容,所以我想我现在明白这意味着什么。

The question is : is there a way to define an opaque type which can nonetheless be allocated on stack, and without breaking strict aliasing rule ?

问题是:有没有办法定义一个opaque类型,它仍然可以在堆栈上分配,而不会破坏严格的别名规则?

Note that I've attempted the union method described in this excellent article but it still generates the same warning.

请注意,我已经尝试了这篇优秀文章中描述的union方法,但它仍然会生成相同的警告。

Note also that visual, clang and gcc 4.6 and later don't complain and work fine with this construction.

另请注意,visual,clang和gcc 4.6及更高版本不会抱怨并且可以正常使用这种结构。

[Edit] Information complement :

[编辑]信息补充:

According to tests, the problem only happens in the following circumstances :

根据测试,问题只发生在以下情况:

  • Private and public type different. I'm casting the public type to private inside the .c file. It doesn't matter apparently if they are part of the same union. It doesn't matter if the public type contains char.
  • 私人和公共类型不同。我在.c文件中将公共类型转换为私有。如果他们是同一个联盟的一部分,那显然无关紧要。公共类型是否包含char无关紧要。

  • If all operations on private type are just reads, there's no problem. Only writes cause problems.
  • 如果私有类型的所有操作都只是读取,则没有问题。只有写入会导致问题。

  • I also suspect that only functions which are automatically inlined get into trouble.
  • 我还怀疑只有自动内联的函数才会遇到麻烦。

  • Problem only happens on gcc 4.4 at -O3 setting. -O2 is fine.
  • 问题仅发生在gcc 4.4 at -O3设置上。 -O2很好。

Finally, my target is C90. Maybe C99 if there really is no choice.

最后,我的目标是C90。也许C99如果真的没有选择。

3 个解决方案

#1


1  

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.

你想要的是C语言中C ++私有访问控制的某种等价物。如你所知,没有这样的等价物存在。你给出的方法大致就是我要做的。但是,我会使opaqueType对实现该类型的内部组件不透明,因此我将*将其强制转换为内部组件中的实际类型。强制施法不应该产生你提到的警告。

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);
}

#2


1  

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.

您可以使用max_align_t强制对齐,并且可以使用char数组避免严格的别名问题,因为明确允许char为任何其他类型的别名。

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.

如果要支持没有max_align_t的编译器,或者您知道实际类型的对齐要求,则可以使用任何其他类型的联合成员。

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

更新:如果你的目标是C11,那么你也可以使用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.

当然,您可以使用您认为合适的任何类型替换max_align_t。甚至是整数。

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.

这样,既然你是一个指向char的指针,你就不会违反严格的别名规则。

#3


0  

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.

当然不是你不能记录这样或那样的事情是可能的,但C语言使用这种方法(严格别名),你只能或多或少地被罗德里戈的回答(使用max_align_t)破解。根据规则,您无法通过接口知道特定编译器会对实现中的实际结构施加什么样的约束(对于某些深奥的微控制器,甚至内存类型可能很重要),所以我不认为这可以以真正的跨平台方式可靠地完成。

#1


1  

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.

你想要的是C语言中C ++私有访问控制的某种等价物。如你所知,没有这样的等价物存在。你给出的方法大致就是我要做的。但是,我会使opaqueType对实现该类型的内部组件不透明,因此我将*将其强制转换为内部组件中的实际类型。强制施法不应该产生你提到的警告。

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);
}

#2


1  

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.

您可以使用max_align_t强制对齐,并且可以使用char数组避免严格的别名问题,因为明确允许char为任何其他类型的别名。

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.

如果要支持没有max_align_t的编译器,或者您知道实际类型的对齐要求,则可以使用任何其他类型的联合成员。

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

更新:如果你的目标是C11,那么你也可以使用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.

当然,您可以使用您认为合适的任何类型替换max_align_t。甚至是整数。

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.

这样,既然你是一个指向char的指针,你就不会违反严格的别名规则。

#3


0  

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.

当然不是你不能记录这样或那样的事情是可能的,但C语言使用这种方法(严格别名),你只能或多或少地被罗德里戈的回答(使用max_align_t)破解。根据规则,您无法通过接口知道特定编译器会对实现中的实际结构施加什么样的约束(对于某些深奥的微控制器,甚至内存类型可能很重要),所以我不认为这可以以真正的跨平台方式可靠地完成。