简体   繁体   English

在处理指针时,C ++编译器能否优化代码?

[英]Can a C++ compiler optimize away code when dealing with pointers?

With these two questions as background ( first and second ), I got curious about how much optimization a C++ compiler can perform when dealing with pointers? 有了这两个问题作为背景( 第一第二 ),我很好奇C ++编译器在处理指针时可以执行多少优化? More specifically, I'm interested in how smart a compiler is when it optimizes away code which it may detect will never be run. 更具体地说,我对编译器在优化它可能检测到的永远不会运行的代码时的智能感兴趣。

(Some may point out that this is a dupe of this question , which it may be, but this particular part of it was not entirely answered. So therefore I decided to start a new question which deals only with this concern.) (有些人可能会指出,这可能是这个问题的一个骗局,但它的这个特殊部分并没有完全回答。所以我决定开始一个只涉及这个问题的新问题。)

(I'm not a C++ expert, so I may be wrong in the following statements, but I'll give it a shot anyway). (我不是C ++专家,所以我在下面的陈述中可能会出错,但无论如何我都会试一试)。 A C++ compiler may optimize away portions of code which it will recognize never to be executed or never exited (such as loops). C ++编译器可以优化掉它将识别的部分代码,它们永远不会被执行或永远不会被退出(例如循环)。 Here is an example: 这是一个例子:


void test() {
    bool escape = false;

    while ( !escape ); // Will never be exited

    // Do something useful after having escaped
}

The compiler would most likely recognize that the loop will never be exited as the code never changes the value of escape so that the loop is exited. 编译器很可能会认识到循环永远不会被退出,因为代码永远不会改变escape的值,因此退出循环。 This makes the loop useless. 这使得循环无用。

Now, if we were to change the variable to a pointer, would the compiler still optimize away the loop? 现在,如果我们要将变量更改为指针,编译器是否仍然会优化循环? Say the code looks like so: 说代码看起来像这样:


void test( bool* escape ) {
    while ( *escape ); // Will this be executed?

    // Do something useful after having escaped
}

My suspicion is that the compiler will do away with the loop, or else the keyword volatile would be redundant, yes?. 我怀疑编译器取消循环,否则关键字volatile将是多余的,是吗? But what about when working with threads -- where it is in fact modified but outside the function, and maybe even outside that C++ file entirely -- would the compiler still remove the loop? 但是当处理线程时 - 实际上它已被修改但在函数之外,甚至可能在C ++文件之外 - 编译器是否仍会删除循环? Does it make a difference if the variable pointed to by escape is a global variable, or a local variable inside another function? 如果escape指向的变量是全局变量,或者是另一个函数中的局部变量,它会有所不同吗? Can the compiler make this detection? 编译器可以进行此检测吗? In this question , some say that the compiler will not optimize the loop if library functions are invoked inside the loop. 这个问题中 ,有人说如果在循环内部调用库函数,编译器将不会优化循环。 What mechanisms are then in place when using library functions which prevent this optimization? 当使用阻止此优化的库函数时,会有什么机制?

In the first case ( while ( !escape ); ) the compiler will treat that as label: goto label; 在第一种情况下( while ( !escape ); )编译器会将其视为label: goto label; and omit everything after it (and probably give you a warning). 并省略其后的所有内容(可能会给你一个警告)。

In the second case ( while ( *escape ); ), the compiler has no way of knowing if *escape will be true or false when run, so it has to do the comaprision and loop or not. 在第二种情况下( while ( *escape ); ),编译器无法知道* escape在运行时是真还是假,所以它必须执行comaprision和循环。 Note however, it only has to read the value from *escape once, ie, it could treat that as: 但是请注意,它只需要从* escape读取一次值,即它可以将其视为:

 bool b = *escape;
 label:   if (b) goto label;

volatile will force it to read the value from '*escape' each time through the loop. volatile会强制它每次通过循环从'* escape'读取值。

Remember that the C++ compiler doesn't know, or give two shits, about your threads. 请记住,C ++编译器不知道或给出两个关于你的线程的屎。 Volatile is all you've got. 挥发性就是你所拥有的。 It's perfectly legal for any compiler to make optimizations that destroy multi-threaded code but run fine on single-threaded. 对于任何编译器来说,进行优化以破坏多线程代码但在单线程上运行良好是完全合法的。 When discussing compiler optimizations, ditch threading, it's just not in the picture. 在讨论编译器优化,沟渠线程时,它只是在图片中。

Now, library functions. 现在,库函数。 Of course, your function could have *escape changed at any time by any of those functions, as the compiler has no way to know how they work. 当然,您的函数可以随时通过任何这些函数更改* escape,因为编译器无法知道它们是如何工作的。 This is especially true if you pass the function as a callback. 如果将函数作为回调传递,则尤其如此。 If, however, you have a library where you have the source code, the compiler may dig in and discover that *escape is never changed within. 但是,如果你有一个拥有源代码的库,那么编译器可能会深入研究并发现* escape永远不会被改变。

Of course, if the loop is empty, it will almost certainly just let your program hang, unless it can determine that the condition isn't true when it starts. 当然,如果循环为空,几乎可以肯定只是让你的程序挂起,除非它可以确定启动时条件不正确。 Removing an empty infinite loop is not the job of the compiler, it's the job of the programmer's brain cells. 删除空的无限循环不是编译器的工作,它是程序员脑细胞的工作。

The generic problem with a question like this is that they often include a very unrealistic code snippet. 像这样的问题的一般问题是它们通常包含一个非常不切实际的代码片段。 A "what will the compiler do" question needs real code. “编译器会做什么”问题需要真正的代码。 Since compilers were designed and optimized to compile real code. 由于编译器的设计和优化是为了编译实际代码。 Most compilers will completely eliminate the function, and the function call, since the code has no side-effects. 大多数编译器将完全消除函数和函数调用,因为代码没有副作用。 Leaving us with a question that doesn't have a useful answer. 让我们留下一个没有实用答案的问题。

But, sure, you are leaning towards finding a use for the volatile keyword. 但是,当然,您倾向于寻找volatile关键字的用法。 You can find many threads on SO talking about why volatile isn't appropriate in a multi-threaded app. 您可以在SO上找到许多线程,讨论为什么volatile不适合多线程应用程序。

There's a difference in what the compiler is allowed to do, and what real compilers do. 编译器允许执行的操作与实际编译器的功能有所不同。

The Standard covers this in "1.9 Program execution". 该标准涵盖了“1.9程序执行”中的内容。 The standard describes a sort of abstract machine, and the implementation is required to come up with the same "observable behavior". 该标准描述了一种抽象机器,并且实现需要提出相同的“可观察行为”。 (This is the "as-if" rule, as documented in a footnote.) (这是“假设”规则,如脚注中所述。)

From 1.9(6): "The observable behavior of the abstract machine is its sequence of reads and writes to volatile data and calls to library I/O functions." 从1.9(6):“抽象机器的可观察行为是它对volatile数据的读写顺序以及对库I / O函数的调用。” This means that, if you can show that a modification to a function will cause changes to neither, either in that function or after it's called, the modification is legal. 这意味着,如果您可以证明对函数的修改既不会导致对该函数的修改,也不会在该函数调用之后,则修改是合法的。

Technically, this means that if you write a function that will run forever testing (say) Goldbach's Conjecture that all even numbers greater than 2 are the sum of two primes, and only stopping if it finds one that isn't, a sufficiently ingenious compiler could substitute either an output statement or an infinite loop, depending on whether the Conjecture is false or true (or unprovable in the Goedel sense). 从技术上讲,这意味着如果你编写一个将永远运行的函数测试(比方说)Goldbach的猜想,所有大于2的偶数都是两个素数的总和,并且只有在找到一个非素数时才停止,一个足够巧妙的编译器可以替换输出语句或无限循环,具体取决于猜测是假还是真(或者在Goedel意义上是无法证明的)。 In practice, it will be a while, if ever, before compilers have theorem provers better than the best mathematicians. 在实践中,在编译器具有比最佳数学家更好的定理证明之前,将会有一段时间。

Yes, some compilers are smarter than others. 是的,有些编译器比其他编译器更聪明。 Your first example a decent compiler, optimized or not will see that it does nothing, it may or may not generate code for it, and it may or may not warn you that your code does nothing. 你的第一个例子是一个体面的编译器,无论是否优化都会看到它什么都不做,它可能会也可能不会为它生成代码,它可能会或可能不会警告你,你的代码什么都不做。

I have seen a compiler that optimized what was many lines of code in different functions called in a loop. 我见过一个编译器,它优化了循环中调用的不同函数中的许多代码行。 I was trying to do a compiler comparison actually, with an lfsr randomizer called repeatedly in a loop (the loop was run a hardcoded number of times). 我试图实际进行编译器比较,在循环中重复调用lfsr随机函数(循环运行硬编码次数)。 One compiler faithfully compiled (and optimized) the code, performing each functional step, the other compiler figured out what I was doing and the assembler produced was the equivalent of ldr r0,#0x12345 where 0x12345 was the answer if you computed all the variables as many times as the loop was run. 一个编译器忠实地编译(和优化)代码,执行每个功能步骤,另一个编译器弄清楚我正在做什么和生成的汇编程序相当于ldr r0,#0x12345其中0x12345是答案,如果你计算所有变量为循环运行很多次。 One instruction. 一条指令。

As seen in one of your other questions, you are struggling with the use of volatile as well as other specifics of the language. 正如您在其他一个问题中所看到的那样,您正在努力使用该语言的volatile和其他细节。 In your second example, with only visibility into that function, the compiler does not know what the memory location that is pointed to is, it could very well be a hardware register that is expected to change at some point, or a memory location shared in some other way by an interrupt or other thread that is expected to change at some point. 在你的第二个例子中,只有对该函数的可见性,编译器不知道所指向的内存位置是什么,它很可能是一个预期在某个时刻发生变化的硬件寄存器,或者是一个共享的内存位置。中断或其他线程的某种其他方式,预计会在某些时候发生变化。 Without a volatile an optimizer is well within its rights to do something like this: 如果没有易变性,优化器完全有权做这样的事情:

ldr r0,[r1]
compare:
  cmp r0,#0
  bne compare

Exactly the lesson I learned when I learned about volatile. 正是我在学习挥发性知识时学到的教训。 The memory location was read (once), and then the loop waited for that value to change, just as my code had "told" it to do. 读取(一次)内存位置,然后循环等待该值更改,就像我的代码“告诉”它要做的那样。 Not what I "wanted" it to do (exit the loop when the register being pointed to changed). 不是我“想要”它做的事情(当寄存器被指向改变时退出循环)。

Now if you were to do something like this: 现在,如果你做这样的事情:

void test( bool* escape ) {
    while ( *escape );
}

pretest() {
    bool escape = false;
    test(&escape);
}

Some compilers will faithfully compile this code even though it does nothing (other than burn clock cycles which may be exactly what was desired). 有些编译器会忠实地编译这段代码,即使它什么都不做(除了刻录时钟周期,这可能正是所期望的)。 Some compilers can look outside one function into another and see that the while(*escape); 有些编译器可以在一个函数外面查看另一个函数并看到while(* escape); is never true. 从来都不是真的。 And some of those will not put any code in pretest() but for some reason will faithfully include the test() function in the binary even though it is never called by any code. 其中一些不会在pretest()中放入任何代码,但由于某种原因,它会忠实地在二进制文件中包含test()函数,即使它从未被任何代码调用过。 Some compilers will completely remove test() and leave pretest as a simple return. 一些编译器将完全删除test()并将pretest作为简单的返回。 All perfectly valid within these non-real-world-educational examples. 在这些非现实世界教育的例子中完全有效。

Bottom line your two examples are completely different, the first case what the compiler needs to know to determine the while loop is a nop is all there. 底线你的两个例子是完全不同的,第一种情况是编译器需要知道什么来确定while循环是一个nop就在那里。 In the second case the compiler has no way of knowing the state of escape or if it will ever change and has to faithfully compile the while loop into executable instructions. 在第二种情况下,编译器无法知道转义的状态,或者它是否会改变,并且必须忠实地将while循环编译成可执行指令。 The only optimization opportunity is whether or not it reads from memory on every pass through the loop. 唯一的优化机会是它是否在每次通过循环时从内存中读取。

If you want to really understand what is going on, compile these functions with different optimization options and then disassemble the object code. 如果您想真正了解正在发生的事情,请使用不同的优化选项编译这些函数,然后反汇编目标代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 一个好的C ++编译器会优化参考吗? - Will a good C++ compiler optimize a reference away? C ++:编译器优化和变量; 远? - C++ : Will compiler optimize &Variable; away? c ++:编译器能否优化此代码段? - c++ : Can the compiler optimize this code segment? VS2010 C ++ / C#编译器能否优化掉循环内部声明的变量? - Can a VS2010 C++/C# compiler optimize away variables declared inside of the loop? 现代C / C ++编译器可以使用标头中的代码更好地优化吗? - Can a modern C/C++ compiler optimize better with the code in header? 何时允许编译器优化 C++ 中枚举或枚举类类型值的有效性检查? - When is the compiler allowed to optimize away a validity check of an enum or enum class type value in C++? C++ 编译器能否优化用于将函数结果传递给另一个函数的虚拟变量的使用? - Can the C++ compiler optimize away the use of dummy variables that are used to pass the results of functions to another function? C ++编译器可以优化一个类吗? - Can C++ compilers optimize away a class? 现代 C++ 编译器会优化掉错误的 constexpr 条件吗? - Will a modern C++ compiler optimize away false constexpr conditionals? c ++编译器会通过`reference`优化掉未使用的返回值吗? - Will the c++ compiler optimize away unused return value by `reference`?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM