在对齐违规的情况下,指针算术仍然定义明确吗?
Is pointer arithmetic still well defined after casting with alignment violation?
我知道一旦取消引用,对齐违规的指针转换的结果会调用未定义的行为。
但是仅用于地址计算(不取消引用)的指针转换呢?
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()
会 return X + sizeof(uint32_t) * dword_offset
?
我的假设是,但最近我在 C11 标准中看到以下内容,J.2 未定义行为
— Conversion between two pointer types produces a result that is incorrectly aligned (6.3.2.3).
如果我理解正确的话,转换本身会调用未定义的行为,而不仅仅是取消引用,这意味着在这种情况下,即使是指针算法也可能会出现不可预测的行为。我说得对吗?
如果 ptr
没有与 uint32_t
正确对齐,这实际上会导致未定义的行为。有些系统可能允许它,但其他系统可能会触发错误。
安全的转换是 char *
,然后对其进行指针运算。
return (char *)ptr + dword_offset * sizeof(uint32_t);
是的,你没看错。例如,在一些 word-addressed 但仍然有一个 char
类型小于单词的计算机上, int *
指针可能 更小 ,所以不知道将未对齐的 char *
转换为 int *
会做什么 - 但陷阱将是 best-case 场景。
如果需要byte-wise指针运算,请使用指向字符类型的指针。 所有 其他对象指针类型应该永远 仅用于引用真实对象 或 pointed-to 类型的数组。
投射未对齐的指针可能导致故障的一个值得注意的例子是在处理时:
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 生成 关心对齐的代码。
我知道一旦取消引用,对齐违规的指针转换的结果会调用未定义的行为。
但是仅用于地址计算(不取消引用)的指针转换呢?
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()
会 return X + sizeof(uint32_t) * dword_offset
?
我的假设是,但最近我在 C11 标准中看到以下内容,J.2 未定义行为
— Conversion between two pointer types produces a result that is incorrectly aligned (6.3.2.3).
如果我理解正确的话,转换本身会调用未定义的行为,而不仅仅是取消引用,这意味着在这种情况下,即使是指针算法也可能会出现不可预测的行为。我说得对吗?
如果 ptr
没有与 uint32_t
正确对齐,这实际上会导致未定义的行为。有些系统可能允许它,但其他系统可能会触发错误。
安全的转换是 char *
,然后对其进行指针运算。
return (char *)ptr + dword_offset * sizeof(uint32_t);
是的,你没看错。例如,在一些 word-addressed 但仍然有一个 char
类型小于单词的计算机上, int *
指针可能 更小 ,所以不知道将未对齐的 char *
转换为 int *
会做什么 - 但陷阱将是 best-case 场景。
如果需要byte-wise指针运算,请使用指向字符类型的指针。 所有 其他对象指针类型应该永远 仅用于引用真实对象 或 pointed-to 类型的数组。
投射未对齐的指针可能导致故障的一个值得注意的例子是在处理时:
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 生成 关心对齐的代码。