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

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

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

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

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

 #define alignof(type) offsetof(struct { char c; type d; }, d) 

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

 #define alignof(type) ((size_t)&((struct { char c; type d; } *)0)->d) 

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

 #define alignof(type) ({ \ struct s { char c; type d; }; \ offsetof(struct s, d); \ }) 

C11中有_Alignof

 printf("Alignment of int: %zu\n", _Alignof(int)); 

包含通常更好的样式,并使用小写的alignof

 #include  printf("Alignment of int: %zu\n", alignof(int)); 

你可以这样检查C11:

 #if __STDC_VERSION__ >= 201112L /* C11 */ #else /* not C11 */ #endif 

如果您正在使用GCC或CLang,则可以通过添加-std=c11 (或-std=gnu11如果您还需要GNU扩展)来在C11模式下编译代码。 GCC的默认模式是gnu89gnu99默认模式是gnu99。


更新:

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

 // 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 

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

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


覆盖大多数情况

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

 // for both `signed` and `unsigned` sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long) sizeof(float) <= sizeof(double) <= sizeof(long double) 

稍微修改一下订单,在大多数情况下你应该可以获得无衬垫结构。 请注意,这并不能保证结构不会打开; 但它将出现在大多数现实世界中 ,应该是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 

整数类型

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

 long long, long, int, short, char 

浮点类型

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

long long始终至少为8字节(由于其最小范围,这是标准所要求的), long始终至少为4字节(但它通常是64位的8字节;有例外,例如视窗)。

我会做的是在这些之间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 

指针类型

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

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

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

 sizeof(long) <= sizeof(T*) <= sizeof(long long) 

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

 sizeof(int) <= sizeof(T*) <= sizeof(long) 

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

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

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

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