堆栈上数据的对齐方式是什么?
What is the alignment of data on the stack?
我读了K&R C(2nd) 185p,有一部分很难理解。
Although machines vary, for each machine there is a most restrictive type: if the most restrictive type can be stored at a particular address, all other types may be also. On some machines, the most restrictive type is a double; on others, int or long suffices.
我觉得
大多数现代计算机都是字节可寻址的(通过 wiki)。最小的数据类型 char 足以放入任何堆栈区域。所以所有数据类型都适合任意堆栈位置。但是为什么会有这样的限制呢?
在 类似的问题中,
CPUs often require that (or work more efficiently if) certain types of data are stored at addresses that are a multiple of some (power-of-two) value.
这解释了我的问题。但是我听不懂。这是否意味着堆栈中某些 2 的幂 (2, 4, 8, 16, ..., 1024, 2048, ...) 的地址需要某些类型?
如果是这样,为什么?或者,如果我错了,它指的是什么?
对齐数据有两个原因:
- 硬件要求。有些机器只有在正确对齐的情况下才能访问内存中的数据。当然,您可以执行多次读取并使用一些位算法来模拟从任何地址读取,但这会对性能造成破坏。
- 性能。即使一台机器可以访问任何地址的任何数据,如果数据对齐得当,它的性能可能会更好。
当然,这可能因机器而异,但“适当对齐”通常意味着 N 位数据的地址可以被 N/8 整除。
因此,在对齐很重要的机器上,32 位 int
将放置在可被 4 整除的内存地址处,64 位指针将放置在可被 8 整除的内存地址处,等等
你可以在结构中看到这一点。
#include <stdint.h>
#include <stdio.h>
typedef struct {
uint32_t u32;
void* p;
uint8_t u8;
} Struct;
int main(void) {
Struct s;
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", sizeof(s));
}
$ gcc -Wall -Wextra -pedantic a.c -o a && ./a
0x7ffef5f775d0
0x7ffef5f775d8
0x7ffef5f775e0
0x7ffef5f775e8
0x18
这意味着我们有这个:
0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7
+-------+-------+---------------+-+-------------+
| u32 |XXXXXXX| p |*|XXXXXXXXXXXXX| * = u8
+-------+-------+---------------+-+-------------+ X = unused
注意 u32
和 p
之间浪费的 space。这样 p
就正确对齐了。
还要注意 u8
之后浪费的 space。这样当你有一个数组时,结构本身就会正确对齐。如果没有最后的填充,数组第二个元素的 u32
和 p
将无法正确对齐。
最后,注意使用
typedef struct {
uint32_t u32;
uint8_t u8;
void* p;
} Struct;
会导致结构更小。
0 1 2 3 4 5 6 7 8 9 a b c d e f
+-------+-+-----+---------------+
| u32 |*|XXXXX| p | * = u8
+-------+-+-----+---------------+ X = unused
我读了K&R C(2nd) 185p,有一部分很难理解。
Although machines vary, for each machine there is a most restrictive type: if the most restrictive type can be stored at a particular address, all other types may be also. On some machines, the most restrictive type is a double; on others, int or long suffices.
我觉得
大多数现代计算机都是字节可寻址的(通过 wiki)。最小的数据类型 char 足以放入任何堆栈区域。所以所有数据类型都适合任意堆栈位置。但是为什么会有这样的限制呢?
在
CPUs often require that (or work more efficiently if) certain types of data are stored at addresses that are a multiple of some (power-of-two) value.
这解释了我的问题。但是我听不懂。这是否意味着堆栈中某些 2 的幂 (2, 4, 8, 16, ..., 1024, 2048, ...) 的地址需要某些类型?
如果是这样,为什么?或者,如果我错了,它指的是什么?
对齐数据有两个原因:
- 硬件要求。有些机器只有在正确对齐的情况下才能访问内存中的数据。当然,您可以执行多次读取并使用一些位算法来模拟从任何地址读取,但这会对性能造成破坏。
- 性能。即使一台机器可以访问任何地址的任何数据,如果数据对齐得当,它的性能可能会更好。
当然,这可能因机器而异,但“适当对齐”通常意味着 N 位数据的地址可以被 N/8 整除。
因此,在对齐很重要的机器上,32 位 int
将放置在可被 4 整除的内存地址处,64 位指针将放置在可被 8 整除的内存地址处,等等
你可以在结构中看到这一点。
#include <stdint.h>
#include <stdio.h>
typedef struct {
uint32_t u32;
void* p;
uint8_t u8;
} Struct;
int main(void) {
Struct s;
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", sizeof(s));
}
$ gcc -Wall -Wextra -pedantic a.c -o a && ./a
0x7ffef5f775d0
0x7ffef5f775d8
0x7ffef5f775e0
0x7ffef5f775e8
0x18
这意味着我们有这个:
0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7
+-------+-------+---------------+-+-------------+
| u32 |XXXXXXX| p |*|XXXXXXXXXXXXX| * = u8
+-------+-------+---------------+-+-------------+ X = unused
注意 u32
和 p
之间浪费的 space。这样 p
就正确对齐了。
还要注意 u8
之后浪费的 space。这样当你有一个数组时,结构本身就会正确对齐。如果没有最后的填充,数组第二个元素的 u32
和 p
将无法正确对齐。
最后,注意使用
typedef struct {
uint32_t u32;
uint8_t u8;
void* p;
} Struct;
会导致结构更小。
0 1 2 3 4 5 6 7 8 9 a b c d e f
+-------+-+-----+---------------+
| u32 |*|XXXXX| p | * = u8
+-------+-+-----+---------------+ X = unused