转换为 char 指针以将指针递增一定量然后作为不同类型访问是否违反严格别名?

Does casting to a char pointer to increment a pointer by a certain amount and then accessing as a different type violate strict aliasing?

例如,您可能希望将与 4 字节边界对齐的 void * 对齐到 16 字节边界。

int *align16(void *p) {
    return (int *)((char *)p + 16 - (uintptr_t)p % 16);
}

通常,访问从 char * 转换而来的 int * 会违反严格的别名规则并调用未定义的行为,而相反的情况是安全的。

在上述情况下,它仍然是未定义的行为吗?

我发现 与此类似,回复说只要保持对齐就可以了。但是,该答案没有提及任何关于严格别名的内容,也没有回复与标准规范相关的评论。


附带问题。 align16优化后的编译输出可以反编译如下

int *align16(void *p) {
    return (int *)((uintptr_t)p + 16 & -16);
}

标准如何处理这种情况,访问在转换为 uintptr_t 时修改的指针?

将结构指针替换为结构大小的倍数然后使用结果指针的操作在 1974 C 参考手册定义的语言中明确定义了语义。如果这些操作至少在某些情况下不能预期表现有用,那么该语言将对许多目的无用,但一些现有的实现不会在所有情况下有意义地处理它们,并且语言的任何正式描述中都没有指出何时进行此类操作应该或不应该期望有意义的行为(以类似于 1974 年语言的方式)。该标准确实允许实施施加某些约束,如果违反了 1974 年的行为,但 none 似乎适用于此。

给定一个像

这样的函数
struct foo { int x,y; };
int test(struct foo *p1, struct foo *p2)
{
    p1->x = 1;
    p2->y = 2;
    return p1->x;
}

如果 p1->x 和 p2->y 碰巧重合,则不会违反任何约束,但在这种情况下,clang 和 gcc 都不会生成 return 2 的代码。即使代码是这样的:

struct foo { int x,y; };
void test(struct foo *p1, struct foo *p2, int mode)
{
  p1->x = 1;
  p2->y = 2;
  if (mode)
    p1->x = 1;
}

如果在模式为零的情况下调用代码通过 p2->y 独占访问存储,并且通过 p1->x 独占访问存储,则应定义其行为,否则,clang 将生成共享存储位置持有 2 的代码即使模式为零。

如果从另一种对象派生出一种类型的指针、以“奇怪”的方式操纵它,然后访问结果指针的操作相对于通过其他方式访问存储的干预操作被认为是无序的,那么上述构造都会在 storage-overlap 情况下调用未定义行为,因为它们涉及对 partially-overlapping 类型 struct foo 对象的无序访问。不幸的是,即使这样的规则允许几乎所有有用的 type-based 别名优化而不干扰有用的 type-punning 构造,即使编译器在违反这样的规则的情况下表现不可靠,标准的措辞方式使用了不同的抽象模型,既不适合程序员的需要,也不符合实际编译器的行为。

Does casting to a char pointer to increment a pointer by a certain amount and then accessing as a different type violate strict aliasing?

并非天生如此。

Normally, accessing an int * casted from a char * violates strict aliasing rules

不一定。严格别名与 pointed-to 对象的(有效)类型有关。 char * 指向的对象很可能是 int,或者与 int 兼容,或者是 分配的 有效类型int 作为(写)访问的结果。在这种情况下,转换为 int * 并取消引用结果是完全有效的。

是的,在很多情况下,将 char * 转换为 int * 然后取消引用结果将构成 strict-aliasing 违规,但这并不是特别因为的参与,或投射到或从,键入 char *.

无论特定的 char * 值是如何获得的,以上内容都适用,因此在您的特定示例中也是如此。如果你的指针计算的结果是一个有效的指针,并且它指向的对象确实是一个(有效的)int 或与 int 兼容,具体方式之一在第 6.5 节中记录语言规范,然后通过指针读取 pointed-to 值就可以了。否则就是strict-aliasing违规。

尝试取消引用未针对其类型正确对齐的指针值通常是指针操作的潜在问题,但严格的别名规则比指针对齐注意事项更强大并有效地包含在内。如果您有一个满足严格别名规则的访问,那么所涉及的指针必须与其类型令人满意地对齐。反过来不一定成立。


但是请注意,尽管在许多平台上,您的 align16() 确实会尝试读取 16-byte-aligned 对象,但 C 语言规范并不要求这样做所以。 Pointer-to-integer 和 integer-to-pointer 转换是明确允许的,但它们的结果是 实现定义的 。这种转换的整数端的值不一定报告或控制另一端指针的对齐。

How does the standard deal with such case, accessing a pointer modified while casted to a uintptr_t?

见上文。就语言规范而言,Pointer-to-integer 和 integer-to-pointer 转换具有 implementation-defined 效果。但是,在您可能遇到的大多数实现中,align16() 的两个版本将具有相同的行为。