我正在为一个非常简单的语言编写一个非常小的解释器,它允许简单的结构定义(由其他结构和简单类型组成,如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);
}
这有效,但也许有更好的东西?
答案 0 :(得分:8)
C11中有_Alignof
:
printf("Alignment of int: %zu\n", _Alignof(int));
包含<stdalign.h>
通常更好的风格,并使用小写alignof
:
#include <stdalign.h>
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
,对于CLang,默认模式为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字节(但它是&#39; s 通常 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
我们现在剩下的都是指针类型。不同非函数指针的大小不一定相同,但我会假设它是(并且在绝大多数(如果不是全部)情况下都是如此)。我假设函数指针可以更大(认为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
答案 1 :(得分:4)
这可能不太便于携带,但 GCC接受以下内容:
#define alignof(type) offsetof(struct { char c; type d; }, d)
编辑:根据this answer,C允许转换为匿名结构类型(尽管我希望看到此语句备份)。所以以下内容应该是可移植的:
#define alignof(type) ((size_t)&((struct { char c; type d; } *)0)->d)
使用GNU statement expressions的另一种方法:
#define alignof(type) ({ \
struct s { char c; type d; }; \
offsetof(struct s, d); \
})