我知道带有对齐冲突的指针强制转换的结果在取消引用后会调用未定义的行为。
但是,指针投射只用于地址计算(不进行解引用(呢?
void *addr_calc(single_byte_aligned_struct_t *ptr, uint32_t dword_offset)
{
uint32_t *dw_ptr = (uint32_t *)ptr;
return dw_ptr + dword_offset;
}
假设ptr
的值为X。是否保证addr_calc()
将返回X + sizeof(uint32_t) * dword_offset
?
我的假设是这样的,但最近我在C11标准的J.2节中看到了以下未定义的行为
两种指针类型之间的转换会产生不正确对齐的结果(6.3.2.3(。
如果我理解正确,强制转换本身会调用未定义的行为,而不仅仅是取消引用,这意味着在这种情况下,即使是指针算术也可能会出现不可预测的行为。我说得对吗?
如果ptr
没有为uint32_t
正确对齐,这实际上会导致未定义的行为。一些系统可能允许,但其他系统可能会触发故障。
一个安全的转换是转换为char *
,然后对其进行指针运算。
return (char *)ptr + dword_offset * sizeof(uint32_t);
是的,你理解得对。例如,在一些有字寻址但char
类型小于字的计算机上,int *
指针的大小可能小于,因此不知道将未对齐的char *
投射到int *
会做什么,但陷阱是的最佳情况。
如果需要字节指针算术,请使用指向字符类型的指针所有其他对象指针类型都应仅用于引用指向类型的真对象或数组。
投射未对齐指针可能导致故障的一个显著例子是在处理时:
void test(void *dest, void *src)
{
uint32_t *d = dest;
uint32_t *s = src;
memcpy(d, s, 4);
}
在不支持未对齐的单词访问的平台上使用clang。在源和目的地不重叠的情况下,memcpy(d, s, 4);
的行为被指定为等效于:
((unsigned char*)d)[0] = ((unsigned char*)s)[0];
((unsigned char*)d)[1] = ((unsigned char*)s)[1];
((unsigned char*)d)[2] = ((unsigned char*)s)[2];
((unsigned char*)d)[3] = ((unsigned char*)s)[3];
然而,Clang将利用uint32_t*
可以被认为永远不会包含未对齐的地址这一事实,从而生成使用单个32位加载和存储的代码,因此只有在指针对齐的情况下才能工作。尽管clang为执行对uint32_t*
的赋值而生成的代码并不关心指针是否对齐,并且尽管指针在传递给memcpy
时被强制为void*
,但在该事件序列内将指针转换为uint32_t*
将导致clang生成确实关心对齐的代码。