C 避免对齐问题
C Avoiding Alignment Issues
能否解释一下,下面的例子到底有什么问题,尤其是 "which might result in the 32-bit unsigned long being loaded from an address that is not a multiple of four" 的部分:
"The compiler generally prevents alignment issues by naturally
aligning all data types. In fact, alignment issues are normally not
major concerns of the kernel developersthe gcc folks have to worry
about them. Issues arise, however, when the programmer plays too
closely with pointers and accesses data outside the environment
anticipated by the compiler.
Accessing an aligned address with a recast pointer of a larger-aligned
address causes an alignment issue (whatever that might mean for a
particular architecture). That is, this is bad news:
char dog[10];
char *p = &dog[1];
unsigned long l = *(unsigned long *)p;
This example treats the pointer to a char as a pointer to an unsigned
long, which might result in the 32-bit unsigned long being loaded from
an address that is not a multiple of four.
If you are thinking, "When in the world would I do this?" you are
probably right. Nevertheless, it has come up, and it will again, so be
careful. The real-world examples might not be so obvious."
虽然我不是很明白这个问题,但是可以使用下面的代码解决吗,如果可以,为什么?
char * dog = (char *)malloc(10 * sizeof(char));
char *p = dog +1;
unsigned long l = *(unsigned long*)p;
您提出的解决方案与引用的解决方案几乎相同,因此存在同样的问题。
错位问题
当您保留内存时,编译器会使用自动变量 (char dog[10]
) 或 malloc
ed 变量以所需的对齐方式保留它。
当您通过指针算术技巧来愚弄编译器时,就像您正在做的那样,它不能保证访问对齐是正确的。
为什么会出现这个问题?
因为,根据您使用的硬件架构,编译器可能发出需要 2 或 4 字节对齐的指令。
例如,ARM 有几条指令要求数据是 2 字节对齐的(也就是说,它的地址必须是偶数)。
因此,为 ARM 处理器构建的代码可能会引发访问冲突。
那你会怎么解决你的问题?
通常,memcpy
:
char *dog = malloc(10 * sizeof(char));
char *p = dog;
unsigned long l;
memcpy(&l, p+1, sizeof(l));
//You can use l safely now.
//Copy back l to the array:
memcpy(p+1, &l, sizeof(l));
你引用的这段话完全正确。
大多数时候,您不必担心对齐问题,因为编译器会为您处理好它,并且效果很好,除非您做的事情太松鼠以至于成功挫败了编译器的尝试保护你。
当您调用 malloc
时,没有问题,因为 malloc
很特殊(在几个方面)。除其他外,它是 "guaranteed to return a pointer to storage suitably aligned for any type of object."
但是,是的,如果您为此努力,可能会给自己带来麻烦。回到原来的例子,假设我们有
char dog[] = "My dog Spot";
char *p = &dog[0];
unsigned long l = *(unsigned long *)p;
假设数组在内存中的布局是这样的:
+---+---+---+---+
100: | | | M | y |
+---+---+---+---+
104: | | d | o | g |
+---+---+---+---+
108: | | S | p | o |
+---+---+---+---+
112: | t |[=11=] | | |
+---+---+---+---+
也就是说,假设数组 dog
结束于内存地址 102,它不是 4 的倍数。所以指针 p
也指向地址 102,我们尝试访问一个 long int
在地址 102。(您会注意到我已将其更改为 &dog[0]
,而不是原始示例中的 &dog[1]
,试图使事情更清楚一些。)
所以我们可能期望变量 l
最终包含 1299783780 或 1679849805(即 0x4d792064 或 0x6420794d),因为它们是前四个字节的表示 "My d" big-endian 或 little-endian 表示法。
但由于它是未对齐的访问,我们可能两个都得不到;该程序可能会因 "bus error" 之类的内容而崩溃。
如果我们被约束并决心要做这种事情,我们可以设法自己进行对齐,像这样:
char dog[] = "My dog Spot";
char *p = dog;
int al = (intptr_t)p % sizeof(unsigned long);
al = sizeof(unsigned long) - al;
if(al == sizeof(unsigned long)) al = 0;
p += al;
unsigned long l = *(unsigned long *)p;
当然,移动指针 p
直到它指向 4 的适当倍数,它不再指向 "My d";现在它指向“狗”。
我做过一两次这样的事情,但我真的不能推荐它。
能否解释一下,下面的例子到底有什么问题,尤其是 "which might result in the 32-bit unsigned long being loaded from an address that is not a multiple of four" 的部分:
"The compiler generally prevents alignment issues by naturally aligning all data types. In fact, alignment issues are normally not major concerns of the kernel developersthe gcc folks have to worry about them. Issues arise, however, when the programmer plays too closely with pointers and accesses data outside the environment anticipated by the compiler.
Accessing an aligned address with a recast pointer of a larger-aligned address causes an alignment issue (whatever that might mean for a particular architecture). That is, this is bad news:
char dog[10]; char *p = &dog[1]; unsigned long l = *(unsigned long *)p;
This example treats the pointer to a char as a pointer to an unsigned long, which might result in the 32-bit unsigned long being loaded from an address that is not a multiple of four.
If you are thinking, "When in the world would I do this?" you are probably right. Nevertheless, it has come up, and it will again, so be careful. The real-world examples might not be so obvious."
虽然我不是很明白这个问题,但是可以使用下面的代码解决吗,如果可以,为什么?
char * dog = (char *)malloc(10 * sizeof(char));
char *p = dog +1;
unsigned long l = *(unsigned long*)p;
您提出的解决方案与引用的解决方案几乎相同,因此存在同样的问题。
错位问题
当您保留内存时,编译器会使用自动变量 (char dog[10]
) 或 malloc
ed 变量以所需的对齐方式保留它。
当您通过指针算术技巧来愚弄编译器时,就像您正在做的那样,它不能保证访问对齐是正确的。
为什么会出现这个问题?
因为,根据您使用的硬件架构,编译器可能发出需要 2 或 4 字节对齐的指令。 例如,ARM 有几条指令要求数据是 2 字节对齐的(也就是说,它的地址必须是偶数)。 因此,为 ARM 处理器构建的代码可能会引发访问冲突。
那你会怎么解决你的问题?
通常,memcpy
:
char *dog = malloc(10 * sizeof(char));
char *p = dog;
unsigned long l;
memcpy(&l, p+1, sizeof(l));
//You can use l safely now.
//Copy back l to the array:
memcpy(p+1, &l, sizeof(l));
你引用的这段话完全正确。
大多数时候,您不必担心对齐问题,因为编译器会为您处理好它,并且效果很好,除非您做的事情太松鼠以至于成功挫败了编译器的尝试保护你。
当您调用 malloc
时,没有问题,因为 malloc
很特殊(在几个方面)。除其他外,它是 "guaranteed to return a pointer to storage suitably aligned for any type of object."
但是,是的,如果您为此努力,可能会给自己带来麻烦。回到原来的例子,假设我们有
char dog[] = "My dog Spot";
char *p = &dog[0];
unsigned long l = *(unsigned long *)p;
假设数组在内存中的布局是这样的:
+---+---+---+---+
100: | | | M | y |
+---+---+---+---+
104: | | d | o | g |
+---+---+---+---+
108: | | S | p | o |
+---+---+---+---+
112: | t |[=11=] | | |
+---+---+---+---+
也就是说,假设数组 dog
结束于内存地址 102,它不是 4 的倍数。所以指针 p
也指向地址 102,我们尝试访问一个 long int
在地址 102。(您会注意到我已将其更改为 &dog[0]
,而不是原始示例中的 &dog[1]
,试图使事情更清楚一些。)
所以我们可能期望变量 l
最终包含 1299783780 或 1679849805(即 0x4d792064 或 0x6420794d),因为它们是前四个字节的表示 "My d" big-endian 或 little-endian 表示法。
但由于它是未对齐的访问,我们可能两个都得不到;该程序可能会因 "bus error" 之类的内容而崩溃。
如果我们被约束并决心要做这种事情,我们可以设法自己进行对齐,像这样:
char dog[] = "My dog Spot";
char *p = dog;
int al = (intptr_t)p % sizeof(unsigned long);
al = sizeof(unsigned long) - al;
if(al == sizeof(unsigned long)) al = 0;
p += al;
unsigned long l = *(unsigned long *)p;
当然,移动指针 p
直到它指向 4 的适当倍数,它不再指向 "My d";现在它指向“狗”。
我做过一两次这样的事情,但我真的不能推荐它。