C - 可移植地获取类型对齐

Jęd*_*icz 7 c portability types alignment

我正在为一个非常简单的语言编写一个非常小的解释器,它允许简单的结构定义(由其他结构和简单类型组成,如int,char,float,double等).我希望字段尽可能少地使用字段,因此使用max_align_t或类似的东西是不可能的.现在,我想知道是否有更好的方法来获得除此之外的任何单一类型的对齐:

#include <stdio.h>
#include <stddef.h>

#define GA(type, name) struct GA_##name { char c; type d; }; \
    const unsigned int alignment_for_##name = offsetof(struct GA_##name, d);

GA(int, int);
GA(short, short);
GA(char, char);
GA(float, float);
GA(double, double);
GA(char*, char_ptr);
GA(void*, void_ptr);

#define GP(type, name) printf("alignment of "#name" is: %dn", alignment_for_##name);

int main() {
GP(int, int);
GP(short, short);
GP(char, char);
GP(float, float);
GP(double, double);
GP(char*, char_ptr);
GP(void*, void_ptr);
}
Run Code Online (Sandbox Code Playgroud)

这有效,但也许有更好的东西?

Tim*_*Čas 8

在C11中,它增加了_Alignof:

printf("Alignment of int: %zu\n", _Alignof(int));
Run Code Online (Sandbox Code Playgroud)

包含通常更好的样式<stdalign.h>,并使用小写alignof:

#include <stdalign.h>

printf("Alignment of int: %zu\n", alignof(int));
Run Code Online (Sandbox Code Playgroud)

你可以这样检查C11:

#if __STDC_VERSION__ >= 201112L
    /* C11 */
#else
    /* not C11 */
#endif
Run Code Online (Sandbox Code Playgroud)

如果您正在使用GCC或CLang,则可以通过添加-std=c11(或者-std=gnu11如果您还需要GNU扩展)在C11模式下编译代码.默认模式适用gnu89于GCC和gnu99CLang.


更新:

如果您进行一些有根据的猜测,您可能根本不需要检查系统的对齐情况.我建议使用以下两种顺序之一:

// non-embedded use
long double, long long, void (*)(void), void*, double, long, float, int, short, char

// embedded use (microcontrollers)
long double, long long, double, long, float, void (*)(void), void*, int, short, char
Run Code Online (Sandbox Code Playgroud)

这种排序是完全可移植的(但并不总是最佳的),因为最坏的情况只是你获得了更多的填充.

接下来是一个(公认的冗长)理由.如果你不关心我如何得出这个订单的结论,请随意跳过这一点.


覆盖大多数情况

这在C中都适用(无论实现如何):

// for both `signed` and `unsigned`
sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
sizeof(float) <= sizeof(double) <= sizeof(long double)
Run Code Online (Sandbox Code Playgroud)

稍微修改一下订单,在大多数情况下你应该可以获得无衬垫结构.请注意,这并不能保证结构不会打开; 但它应该是大多数真实世界的情况,应该是GoodEnough™.

以下是特定于实现的建议,但在大多数情况下应涵盖一致性.

然而,这在任何地方都是完全可移植的(即使不是最佳的) - 只要你接受可能有一些填充(换句话说,不要假设结构没有任何填充).如果你弄错了,你所得到的只是一个更大的结构,所以不存在任何类型的未定义行为的危险.

您应该做的是从最大到最小订购这些,因为它们的对齐也将按此顺序排列.假设一个典型的amd64编译器:

long long a; // 8-byte
long b;      // 8-byte or 4-byte; already aligned in both cases
int c;       // 4-byte; already aligned
short d, e;  // 2-byte; both already aligned
char f;      // 1-byte; always aligned
Run Code Online (Sandbox Code Playgroud)

整数类型

所以让我们从整数类型开始搞清楚我们的顺序:

long long, long, int, short, char
Run Code Online (Sandbox Code Playgroud)

浮点类型

现在,浮点类型.你做什么的double?它的对齐在64位体系结构上通常为 8个字节,在32位上为4个字节(但在某些情况下可能为8个字节).

long long始终至少为8字节(由于其最小范围,这是标准所要求的),并且long始终至少为4字节(但通常在64位中 8字节;存在例如Windows的异常).

我会做的是放在double那些之间.注意,double大小可以是4个字节(通常在嵌入式系统中,例如AVR/Arduino),但实际上总是有4个字节long.

long double是一个复杂的案例.它的对齐范围可以从4字节(例如,x86 Linux)到16字节(amd64 Linux).然而,4字节对齐是一个历史人工制品并且不是最理想的; 所以我认为它至少是8字节,并把它放在上面long long.当它的对齐为16字节时,这也将使其最佳.

这留下了float,实际上总是一个4字节的数量,具有4字节对齐; 我将它介于两者之间long,保证至少为4字节,并且int可以(通常)为4或2字节.

所有这些结合起来给我们下一个订单:

long double, long long, double, long, float, int, short, char
Run Code Online (Sandbox Code Playgroud)

指针类型

我们现在剩下的都是指针类型.不同的非函数指针的大小不一定相同,但我会假设它是(并且在绝大多数情况下,如果不是全部的情况下都是如此).我假设函数指针可以更大(认为ROM比RAM大的硬件架构),所以我会把它们放在其他的上面.

最糟糕的实际情况是它们是相同的,所以我什么都没有做到; 最好的情况是我已经消除了一些填充.

但是尺寸呢?这通常适用于非嵌入式系统:

sizeof(long) <= sizeof(T*) <= sizeof(long long)
Run Code Online (Sandbox Code Playgroud)

在大多数系统中,sizeof(long)并且sizeof(T*)是相同的; 但是例如64位Windows有32位long和64位T*.但是,在嵌入式系统中,它是不同的; 指针可以是16位,这意味着:

sizeof(int) <= sizeof(T*) <= sizeof(long)
Run Code Online (Sandbox Code Playgroud)

在这做什么取决于你--- 是谁知道这通常会在哪里运行.一方面,针对主要用途非嵌入的嵌入式进行优化,意味着针对不常见的情况进行优化.另一方面,嵌入式系统中的内存比例更受限制.就个人而言,我建议优化桌面使用,除非您专门制作嵌入式应用程序.由于对齐double通常与指针大小相同但可以更大,我将其放在下面double.

// non-embedded
long double, long long, void (*)(void), void*, double, long, float, int, short, char
Run Code Online (Sandbox Code Playgroud)

对于嵌入式用途,我把它放在下面float,因为对齐float通常是4字节,但是T*是2字节或4字节:

// embedded
long double, long long, double, long, float, void (*)(void), void*, int, short, char
Run Code Online (Sandbox Code Playgroud)

  • @JędrzejDudkiewicz:我现在已经大大扩展了我的答案,订单应该*通常*工作.大多数更新只是理由,所以你可以看到我背后的推理. (2认同)

nwe*_*hof 5

这可能不是很便携,但GCC 接受以下内容:

#define alignof(type) offsetof(struct { char c; type d; }, d)
Run Code Online (Sandbox Code Playgroud)

编辑:根据这个答案,C 允许转换为匿名结构类型(尽管我希望看到这个语句得到备份)。所以以下内容应该是可移植的:

#define alignof(type) ((size_t)&((struct { char c; type d; } *)0)->d)
Run Code Online (Sandbox Code Playgroud)

另一种使用GNU 语句表达式的方法:

#define alignof(type) ({ \
    struct s { char c; type d; }; \
    offsetof(struct s, d); \
})
Run Code Online (Sandbox Code Playgroud)