Create a function that always returns zero, but the optimizer doesn't know

最后都变了- 提交于 2019-12-05 13:32:15

I would be amazed if a compiler can figure this out:

int not_a_zero_honest_guv()
{
    // static makes sure the initialization code only gets called once
    static int const i = std::ifstream("") ? 1:0;
    return i;
}

int main()
{
    std::cout << not_a_zero_honest_guv();
}

This uses a complex, (unpredictable) runtime initialization of a function local static. If the naughty little compiler figures out that an empty filename will always fail, then put some illegal filename in there.

First an aside: I believe that the OP's third suggestion:

int zero() {
  volatile int x = 0;
  return x;
}

would in fact work (but this is not my answer; see below). This exact same function two weeks ago was the subject of Is it allowed for a compiler to optimize away a local volatile variable?, with much discussion and differing opinions, which I will not repeat here. But for a recent test of this, see https://godbolt.org/g/SA7k5P.


My answer is to add a static to the above, namely:

int zero() {
  static volatile int x;
  return x;
}

See some tests here: https://godbolt.org/g/qzWYJt.

Now with the addition of static, the abstract concept of "observable behavior" becomes more believable. With a little bit of work, I could figure out the address of x, especially if I disabled Address space layout randomization. This would probably be in the .bss segment. Then with a bit more work I could attach a debugger/hacking tool to the running process and then change the value of x. And with volatile, I have told the compiler that I might do this, so it is not allowed to change this "observable behavior" by optimizing x away. (It could perhaps optimize the call to zero away by inlining, but I don't care.)

The title of Is it allowed for a compiler to optimize away a local volatile variable? is a bit misleading, as the discussion centred on x being on the stack rather than it being a local variable. So is not applicable here. But we could change x from local scope to file scope or even global scope, as in:

volatile int x;
int zero() {
  return x;
}

This would not change my argument.


Further discussion:

Yes, volatile's are sometimes problematic: for example, see the pointer-to-volatile issues shown here https://godbolt.org/g/s6JhpL and in Does accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses?.

And yes, sometimes (always?) compilers have bugs.

But I would like to argue that this solution is not an edge case, and that there is a consensus among compiler writers, and I will do so by looking at existing analyses.

John Regehr's 2010 blogpost Volatile Structs Are Broken reports a bug where a volatile access was optimized away in both gcc and Clang. (It was fixed in three hours.) One commentator quoted the standard (emphasis added):

"6.7.3 ... What constitutes an access to an object that has volatile-qualified type is implementation-defined."

Regehr agreed, but added that there is consensus in how it should work on non-edge cases:

Yes, what constitutes an access to a volatile variable is implementation defined. But you have missed the fact that all reasonable C implementations consider a read from a volatile variable to be a read access and a write to a volatile variable to be a write access.

For further references. see:

These are reports about compiler bugs and programmers' errors. But they show how volatile should/does work, and that this answer meets those norms.

You'll find that each compiler has an extension for achieving this.

GCC:

__attribute__((noinline))
int zero()
{
    return 0;
}

MSVC:

__declspec(noinline)
int zero()
{
    return 0;
}

On clang and gcc, clobbering a variable works, but imposes some overhead

int zero()
{
    int i = 0;
    asm volatile(""::"g"(&i):"memory");
    return i;
}

which under O3 on gcc gets compiled to

    mov     DWORD PTR [rsp-4], 0
    lea     rax, [rsp-4]
    mov     eax, DWORD PTR [rsp-4]
    ret

and on clang

    mov     dword ptr [rsp - 12], 0
    lea     rax, [rsp - 12]
    mov     qword ptr [rsp - 8], rax
    mov     eax, dword ptr [rsp - 12]
    ret

Live.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!