C 堆栈上数据的对齐方式是什么?

C 堆栈上数据的对齐方式是什么?,c,architecture,malloc,memory-alignment,C,Architecture,Malloc,Memory Alignment,我读了K&R C(第二版)185p,其中一部分很难理解 尽管机器各不相同,但每台机器都有一个限制性最强的类型:如果限制性最强的类型可以存储在特定的地址,则所有其他类型也可以。在某些机器上,限制性最强的类型是双精度的;在其他情况下,int或long就足够了 我想 大多数现代计算机都是字节可寻址的(通过wiki)。最小的数据类型char足以适合任何堆栈区域。因此,所有数据类型都适合任意堆栈位置。但为何会有这样的限制呢在类似问题中 CPU通常要求(或更有效地工作,如果)某些类型的数据存储在某个(二的幂

我读了K&R C(第二版)185p,其中一部分很难理解

尽管机器各不相同,但每台机器都有一个限制性最强的类型:如果限制性最强的类型可以存储在特定的地址,则所有其他类型也可以。在某些机器上,限制性最强的类型是双精度的;在其他情况下,intlong就足够了

我想
大多数现代计算机都是字节可寻址的(通过wiki)。最小的数据类型char足以适合任何堆栈区域。因此,所有数据类型都适合任意堆栈位置。但为何会有这样的限制呢

在类似问题中

CPU通常要求(或更有效地工作,如果)某些类型的数据存储在某个(二的幂)值的倍数的地址

这就解释了我的问题。但我不明白。这是否意味着堆栈中二次幂(2,4,8,16,…,1024,2048,…)的某些地址需要某些类型?

若然,原因为何?或者,如果我错了,它指的是什么?

对齐数据有两个原因:

  • 硬件要求。某些机器只有在正确对齐的情况下才能访问内存中的数据。当然,您可以执行多次读取,并使用一些位算法模拟从任何地址读取,但这将对性能造成破坏
  • 表演。即使机器可以访问任何地址的任何数据,如果数据适当对齐,其性能也可能会更好
当然,这可能因机器而异,但“适当对齐”通常意味着N位数据的地址可以被N/8整除

因此,在一台需要对齐的机器上,32位
int
将被放置在一个可被4整除的内存地址上,64位指针将被放置在一个可被8整除的内存地址上,以此类推

您可以在结构中看到这一点

#包括
#包括
类型定义结构{
uint32_t u32;
void*p;
uint8_t u8;
}结构;
内部主(空){
结构;
printf(“%p\n”,(void*)和s.u32);
printf(“%p\n”,(void*)和s.p);
printf(“%p\n”,(void*)和s.u8);
printf(“%p\n”,(void*)(&s+1));
printf(“0x%zx\n”,大小f);
}
$gcc-Wall-Wextra-pedantic a.c-o a&&a
0x7ffef5f775d0
0x7ffef5f775d8
0x7ffef5f775e0
0x7ffef5f775e8
0x18
这意味着我们有:

0123456789abcdef01234567
+-------+-------+---------------+-+-------------+ 
|u32 | XXXXXXX | p |*| xxxxxxxxxxxx |*=u8
+-------+-------+---------------+-+-------------+X=未使用
注意
u32
p
之间浪费的空间。就是这样,
p
正确对齐

还要注意
u8
之后浪费的空间。这样,当您有一个数组时,结构本身就正确对齐了。如果没有最后的填充,数组第二个元素的
u32
p
将无法正确对齐

最后,请注意使用

typedef结构{
uint32_t u32;
uint8_t u8;
void*p;
}结构;
将导致更小的结构

01123456789abcdef
+-------+-+-----+---------------+
|u32 |*| XXXXX | p |*=u8
+-------+-+-----+---------------+X=未使用

哇,我明白了!你的回答甚至解释了结构的填充。关于你的第一个例子(大结构),我有一个问题。在该结构中,对齐是(4(u32)、4(x)、8(p)、1(u8)、7(x)),如果我们有一个数组,则相同的对齐将继续。但也可以在末尾设置3个字节的填充,而不是7个字节。然后两个模式将被重复为((4,4(填充),8,1,3(填充)),(4,8,1,3(填充)))。也可以使用某些系统吗?Re“但也可以在末尾设置3字节的填充,而不是7”,否。结构的大小需要可以被其最大对齐限制整除。我们的情况是8。而且
0x14
不能被8整除。让我们用一个例子来说明这是为什么。假设数组从地址0x1000开始<代码>a[0]。u32将位于0x1000(正常),
a[0]。p
将位于0x1008(正常),
a[1]。u32
将位于0x1014(正常),但
a[1]。p
将位于0x101C(不正常)。额外的四个字节的填充将
a[1].u32
推送到0x1018(正常),将
a[1].p
推送到0x1020(正常)