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的默认模式是gnu89
, gnu99
默认模式是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