memcpy 别名 int 到 char 产生 UB?

memcpy alias int to char yields UB?

严格的别名让我变得偏执。有时我使用 *int 指针设置值并期望目标内存读取相同的数据,无论读取指针类型是什么。严格的别名并不能保证这一点,有时甚至会导致情况并非如此。

如果我在循环中读取一个 char[] 并且在该 char[] 数组中有一个 *int 链接某些东西,那么我就违反了其他标准 C 事物中的别名规则。

我正在制作一个 JIT 编译器,因为我使用的是 x86,所以我确信我不必关心 int 对齐。在我们解决别名问题之前,让我们将其排除在外。

考虑这个片段:

unsigned char x86[] = {0x11, 0x44, 0x42, ... };
uint32_t *specific_imm = (x86+10);

现在,*specific_imm = 42;在 x86 平台上仍然是 UB,因为允许编译器假定 *specific_imm 不与 x86[] 别名。通过做出该假设,它不需要立即设置这些字节,但可以进行各种优化。将 x86[] 和 *specific_imm 都设置为 volatile 可以解决我的问题,但这还不够好,因为我想正确学习 C。

我们现在已经解决了别名问题。有人建议这个解决方案: memcpy(x86+10,specific_imm, 4);

但 C 标准似乎也存在关于别名指针的问题(如果我理解正确的话),如下面的代码所示。

/* naive implementation of memcpy */
inline void _memcpy(unsigned char *a, unsigned char *b){
  *a = *b;
}

int main(void) {
  long i = 0xFFFFFFFF;
  unsigned char c = 1;
  ++i;
  _memcpy(&c,&i);
  return c;
}

由于编译器可以自由假设 'i' 在这种情况下不会以某种方式影响 c(?),所以 main 可以自由地优化为仅 return 1?

我更感兴趣的是在直接跳到解决方案之前解决问题。

提前致谢

你错了。 C 编译器可以 not 假设任意指针和指向 char 变体的指针没有别名。它也不能假定指向有符号和无符号整型的两个指针,或指向有符号和无符号长整数的两个指针等未对齐。

在您的最后一个示例中,任何理智的软件开发人员都会以无法编译的方式设置编译器警告。

By making that assumption, it doesn't need to set those bytes right away but may do all kinds of optimizations

根本不需要设置。它可以做任何事情。


Setting both x86[] and *specific_imm as volatile would solve my problem

不是真的。严格别名表示不能通过指向不相关类型的指针更改某个变量。这样做会导致您的程序执行标准未指定的操作。通常这表现为各种与优化器相关的错误,但不一定。该程序还不如什么也不做,或者崩溃并烧毁。

volatile 不会解决这个问题(特别是因为您将指针声明为指向 volatile 数据的东西,而不是使实际数据成为变量 volatile)。

一些编译器(如 GCC)会假设您的程序永远不会违反严格的别名(从而调用未定义的行为)来优化代码。但这并不意味着关闭优化将删除未定义的行为本身,它只会关闭优化器依赖,即假设您的程序没有调用未定义的行为。它不会修复实际的错误。


Some suggest this solution: memcpy

这将解决问题,因为 有效类型 的规则。 6.5/6:

If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one.

这满足严格别名规则的第一部分,6.5/7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

— a type compatible with the effective type of the object,


But the C standard seems to have a problem with that too regarding aliasing pointers (if I've understood things correctly)

不,那是不正确的。由于上述原因,真正的 memcpy 函数使用空指针并且不能违反严格的别名。您的自制版本使用 unsigned char*,这也很好,到 6.5/7:

— a character type.

请阅读What is the strict aliasing rule?, particularly this answer