【问题标题】:breaking strict aliasing and getting away with it打破严格的混叠并摆脱它
【发布时间】:2015-01-15 06:44:04
【问题描述】:

众所周知,优化 C(也可能是 C++)编译器会破坏执行以下操作的代码:

type1 foo, *pfoo;
type2_incompatible_with_type1 *pbar;
/* ... */
pfoo = &foo;
pbar = (type2_incompatible_with_type1*)pfoo;
/* ... */
while(condition){
    change_type2_value(pbar);
    /* ... */
    use_type1_value_which_should_have_changed(*pfoo);
    /* ... */
}

change_type2_value 看起来像这样:

void change_type2_value(type2_incompatible_with_type1 *pbar){
    *pbar = SOME_VALUE;
}

编译器可能会认为pfoopbar 是不同的指针,尽管它们指向相同的内存位置,因此,每次我们更改@ 指向的值时,都不需要重新加载*pfoo 的内容987654326@,即使它也改变了pfoo指向的内存。

但是,如果我们这样做:

type1 foo, *pfoo;
/* ... */
pfoo = &foo;
/* ... */
while(condition){
    change_type2_value((type2_incompatible_with_type1*)pfoo);
    /* ... */
    use_type1_value_which_should_have_changed(*pfoo);
    /* ... */
}

虽然取消引用会发生在change_type2_value 函数内,但这在技术上仍然破坏了严格的别名,因为实际上,我们的指针指向不同的类型。但是,是否存在真正的编译器使用的严格别名优化也会破坏这段代码的实际情况?

我认为,如果编译器超出当前函数的范围来查看 另一个 函数中应该发生的事情,只是为了确定它是否应该重新加载指向的内存,这是有可能的已传递给它的变量。这对我来说似乎不太可行。

或者一个真正的编译器是否有可能做一些令人讨厌的事情,假设如果我们将它作为 cast 传递,所讨论的函数将不会改变我们的指针指向的内存指向不兼容类型的指针

【问题讨论】:

  • 你有多喜欢玩火?如果你搞砸了,你可能迟早会被烧死——无论你是在玩火还是严格的混叠违规。

标签: c pointers compiler-optimization strict-aliasing


【解决方案1】:

严格遵守?没有。

看下面:

void bar(){
    int x=7;
    foo();
    printf("%d\n",x);
}

x 的值是多少?是 7。foo() 修改 x 是没有合法途径的。

怎么样

void bar(int* x){
    *x=7;
    foo();
    printf("%d\n",*x);
}

所有赌注都取消了。 foo() 可以通过其他方式访问x 指向的地址。我们不能说,编译器很可能和我们这里一样少。 这取决于foo() 的定义位置和方式以及编译器的整体性。 例如如果foo()inline 等等。

现在你所做的是转换 before 传递给change_type2_value(.) 并且由于取消引用该指针并不严格符合编译器如果假设change_type2_value(.) 不符合'不要取消引用它的参数,如果它是一个局部变量(你的 sn-p 没有说清楚)它绝对可以假设它没有改变。

下一点是个坏主意

如果您将调用替换为调用:

void do_secret_stuff_with_type1(type1* t1){
    type2_incompatible_with_type1* pbar = (type2_incompatible_with_type1*)pfoo;
    change_type2_value((type2_incompatible_with_type1*)pfoo);
}

type1 foo, *pfoo;
/* ... */
pfoo = &foo;
/* ... */
while(condition){
    do_secret_stuff_with_type1(pfoo); //Looks innocent, right?
    /* ... */
    use_type1_value_which_should_have_changed(*pfoo);
    /* ... */
}

同样如此。这无疑使编译器更难发现正在发生的事情。如果do_secret_stuff_with_type1() 是在单独的翻译单元中定义的,那么您将逐渐增加混淆编译器的机会。

坏主意结束

然而,这种黑客行为几乎可以肯定是一个可怕的想法。 你为什么要这样做? 忘记别名你在做什么不兼容类型之间的转换会产生一个有用的程序?

对于任何实际情况,几乎总有一种解决方案涉及通过unsigned char* 访问对象或使用memcpy() 复制,在某些情况下使用union 将不符合要求的程序转换为符合要求的程序。

【讨论】:

  • 感谢您提供如此详细的答案 =) 但是,我的问题提到了 real 编译器。你能举一个编译器的例子,在优化模式下,假设指针指向的内存在我们将这个指针传递给另一个函数后不会改变,就像一个指向不兼容类型的指针一样?
  • @Mints97 LLVM 的优化器确实进行过程间(甚至模块间)优化。使用 Clang 使用 -O3 -flto 编译时,您的代码可能会中断。
  • @TheParamagneticCroissant:谢谢,这就是我要找的。​​span>
  • 一个紧固件标准应该保证每个合规的 X 级螺母可以与每个合规的 X 级螺栓、每个 Y 级螺母和每个 Y 级螺栓等配合使用,这样使用 X 级螺母的人可以通过询问“它是 X 级螺栓吗?”来确定它可以使用的螺栓。但是,C89 文档不能以这种方式使用。不能保证即使是严格符合的程序也可以在没有 UB 的情况下在每个实现上运行(实际上,一个可以有一对实现,这样每个程序都会在其中至少一个上调用 UB)。如果...的维护者...
  • ...C 标准将接受不同实现具有自然能力的概念,它们可以定义程序可以指定特殊要求的方法,并要求所有符合实现必须要么遵守所有规定的要求,要么至少确定一个他们不满足的要求。反过来,这将有可能定义一大类“选择性符合”程序,这些程序将保证不会在满足所有指定要求的任何实现上调用 UB,从而使“标准”可用作实际标准。
猜你喜欢
  • 2015-01-31
  • 2019-04-10
  • 2013-06-29
  • 1970-01-01
  • 2011-01-24
  • 1970-01-01
  • 2016-10-26
  • 2023-03-15
  • 2014-02-08
相关资源
最近更新 更多