memcpy alias int to char产生UB?

时间:2016-07-07 09:57:03

标签: c undefined-behavior memcpy strict-aliasing

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

如果我在一个循环中读取一个char []并且在那个char []数组中有一个* int chainging的东西我在其他标准C之类的东西中打破别名规则。< / p>

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

请考虑以下代码段:

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;
}

由于编译器可以自由地假设&#39; i&#39;在这种情况下(?)不会以某种方式影响c,main可以自由优化以返回1?

在直接寻求解决方案之前,我对解决这个问题更感兴趣。

提前致谢

2 个答案:

答案 0 :(得分:1)

你错了。 C编译器可以假设任意指针和指向char变体的指针没有别名。它也不能假设有两个指向signed和unsigned int的指针,或两个指向signed和unsigned long等的指针都没有对齐。

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

答案 1 :(得分:1)

  

通过做出这个假设,它不需要立即设置这些字节,但可以进行各种优化

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

  

将x86 []和* specific_imm设置为volatile将解决我的问题

不是真的。严格别名表示某个变量可能无法通过指向不相关类型的指针进行更改。这样做会导致程序执行标准未指定的操作。通常这表现在各种与优化器相关的错误中,但不一定如此。该程序可能无所事事,或崩溃和烧毁。

volatile不会解决此问题(特别是因为您将指针声明为指向volatile数据的内容,而不是将实际数据变量volatile)。

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

  

有人建议使用此解决方案:memcpy

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

  

如果将值复制到没有声明类型的对象中    memcpy memmove ,或者被复制为字符数组的数组,然后   该访问和修改对象的有效类型   不修改值的后续访问是有效类型   复制值的对象,如果有的话。

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

  

对象的存储值只能由具有其中一个的左值表达式访问   以下类型:

     

- 与对象的有效类型兼容的类型,

  

但是C标准似乎也存在关于别名指针的问题(如果我理解正确的话)

不,这不正确。真正的memcpy函数使用void指针,并且由于上面提到的原因,不能违反严格的别名。你家自酿的版本使用unsigned char*,这也很好,6.5 / 7:

  

- 字符类型。

请阅读What is the strict aliasing rule?,特别是this answer