小心对齐!
public_t
本机对齐为 1,因为char
对齐到 1 个字节。
private_t
对齐设置为其成员的最高对齐要求,这肯定不是 1。它可能与指针 ( void *
) 的大小对齐,但double
在子结构内部有一个可能需要 8 个字节对齐。根据 ABI,您可能会看到各种对齐方式。
让我们尝试一个示例程序,使用 gcc 在 i386/i686 上编译和测试(代码源如下):
kind name address size alignment required
type | foo_t | N/A | 48 | N/A | 4
type | priv_t | N/A | 56 | N/A | 4
type | pub_t | N/A | 56 | N/A | 1
object | u8_0 | 0xfff72caf | 1 | 1 | 1
object | u8_1 | 0xfff72cae | 1 | 2 | 1
object | u8_2 | 0xfff72cad | 1 | 1 | 1
object | pub0 | 0xfff72c75 | 56 | 1 | 1
object | u8_3 | 0xfff72c74 | 1 | 4 | 1
object | pub1 | 0xfff72c3c | 56 | 4 | 1
object | u8_4 | 0xfff72c3b | 1 | 1 | 1
object | priv0 | 0xfff72c00 | 56 | 1024 | 4
object | u8_5 | 0xfff72bff | 1 | 1 | 1
object | priv1 | 0xfff72bc4 | 56 | 4 | 4
object | u8_6 | 0xfff72bc3 | 1 | 1 | 1
pointer | pubp | 0xfff72c75 | 56 | 1 | 1
pointer | privp | 0xfff72c75 | 56 | 1 | 4 **UNALIGNED**
object | privp->val | 0xfff72c75 | 4 | 1 | 4 **UNALIGNED**
object | privp->ptr | 0xfff72c79 | 4 | 1 | 4 **UNALIGNED**
object | privp->f | 0xfff72c7d | 48 | 1 | 4 **UNALIGNED**
测试源代码:
#include <stdalign.h>
#ifdef __cplusplus
/* you will need to pass -std=gnu++11 to g++ */
#include <cstdint>
#endif
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#ifdef __cplusplus
#define alignof __alignof__
#endif
#define PRINTHEADER() printheader()
#define PRINTSPACE() printspace()
#define PRINTALIGN(obj) printobjalign("object", #obj, &obj, sizeof(obj), alignof(obj))
#define PRINTALIGNP(ptr) printobjalign("pointer", #ptr, ptr, sizeof(*ptr), alignof(*ptr))
#define PRINTALIGNT(type) printtypealign(#type, sizeof(type), alignof(type))
static void
printheader(void)
{
printf(" %8s %10s %18s %4s %9s %8s\n", "kind", "name", "address", "size", "alignment", "required");
}
static void
printspace(void)
{
printf(" %8s %10s %18s %4s %9s %8s\n", "", "", "", "", "", "");
}
static void
printtypealign(const char *name, size_t szof, size_t alof)
{
printf(" %8s | %10s | %18s | %4zu | %9s | %8zu \n", "type", name, "N/A", szof, "N/A", alof);
}
static void
printobjalign(const char *tag, const char *name, const void * ptr, size_t szof, size_t alof)
{
const uintptr_t uintptr = (uintptr_t)ptr;
uintptr_t mask = 1;
size_t align = 0;
/* get current alignment of the pointer */
while(mask != UINTPTR_MAX) {
if ((uintptr & mask) != 0) {
align = (mask + 1) / 2;
break;
}
mask <<= 1;
mask |= 1;
}
printf(" %8s | %10s | %18p | %4zu | %9zu | %8zu%s\n",
tag, name, ptr, szof, align, alof, (align < alof) ? " **UNALIGNED**" : "");
}
/* a foo struct with various fields */
typedef struct foo
{
uint8_t f8_0;
uint16_t f16;
uint8_t f8_1;
uint32_t f32;
uint8_t f8_2;
uint64_t f64;
uint8_t f8_3;
double d;
uint8_t f8_4;
void *p;
uint8_t f8_5;
} foo_t;
/* the implementation struct */
typedef struct priv
{
uint32_t val;
void *ptr;
struct foo f;
} priv_t;
/* the opaque struct */
typedef struct pub
{
uint8_t padding[sizeof(priv_t)];
} pub_t;
static int
test(pub_t *pubp)
{
priv_t *privp = (priv_t *)pubp;
PRINTALIGNP(pubp);
PRINTALIGNP(privp);
PRINTALIGN(privp->val);
PRINTALIGN(privp->ptr);
PRINTALIGN(privp->f);
PRINTSPACE();
return privp->val;
}
int
main(void)
{
uint8_t u8_0;
uint8_t u8_1;
uint8_t u8_2;
pub_t pub0;
uint8_t u8_3;
pub_t pub1;
uint8_t u8_4;
priv_t priv0;
uint8_t u8_5;
priv_t priv1;
uint8_t u8_6;
PRINTHEADER();
PRINTSPACE();
PRINTALIGNT(foo_t);
PRINTALIGNT(priv_t);
PRINTALIGNT(pub_t);
PRINTSPACE();
PRINTALIGN(u8_0);
PRINTALIGN(u8_1);
PRINTALIGN(u8_2);
PRINTALIGN(pub0);
PRINTALIGN(u8_3);
PRINTALIGN(pub1);
PRINTALIGN(u8_4);
PRINTALIGN(priv0);
PRINTALIGN(u8_5);
PRINTALIGN(priv1);
PRINTALIGN(u8_6);
PRINTSPACE();
return test(&pub0);
}
分析:
pub0
在堆栈上分配并作为参数传递给 function test
。它在 1 个字节上对齐,因此,当转换为priv_t
指针时,priv_t
结构成员不会对齐。
这可能很糟糕:
- 对正确性不利:某些架构/CPU 会默默地破坏对未对齐内存地址的读/写操作,而另一些架构/CPU 会产生故障。后者更好。
- 对性能不利:如果支持,仍然知道未对齐的访问/加载/存储处理不当:您可能要求 CPU 读取/写入对象内存大小的两倍……您可能会以这种方式严重命中缓存.
所以,如果你真的想隐藏结构内容,你应该注意底层结构的对齐:不要使用char
.
默认情况下,使用void *
,或者如果可以double
在结构的任何成员中,使用double
。这将一直有效,直到有人使用#prama
或__attribute__(())
为隐藏结构的(成员)选择更高的对齐方式。
让我们正确定义pub_t
:
typedef struct pub
{
double opaque[(sizeof(priv_t) + (sizeof(double) - 1)) / sizeof(double)];
} pub_t;
听起来可能很复杂,而且确实如此!这样,pub_t
结构将具有正确的对齐方式,并且至少与基础一样大priv_t
。
如果priv_t
被打包(带有#pragma
或__attribute__(())
),则使用sizeof(priv_t)/sizeof(double)
,pub_t
可能小于priv_t
... 这将比我们最初试图解决的问题更糟糕。但是如果结构被打包,谁在乎对齐。
malloc()
如果pub_t
结构是通过分配malloc()
而不是在堆栈上分配的,则对齐不会成为问题,因为malloc()
它被定义为返回与 C 本机类型的最大内存对齐对齐的内存块,例如。double
. 在现代malloc()
实现中,对齐最多可以达到 32 个字节。