在对齐违规的情况下,指针算术仍然定义明确吗?

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 生成 关心对齐的代码。