【问题标题】:Will compiler optimize out unused arguments of static function?编译器会优化静态函数的未使用参数吗?
【发布时间】:2012-12-26 19:48:28
【问题描述】:

我有一组函数都声明为staticfastcall。它们中的大多数使用指向结构的指针,该结构或多或少地充当 C++ 中的this。有些函数在结构中不需要任何东西,但为了统一起见,我还是想将指针传递给它们。编译器会注意到参数没有被使用并省略分配寄存器给它吗?

【问题讨论】:

    标签: c++ c compiler-construction compiler-optimization


    【解决方案1】:

    我写了这个无意义的程序来测试这个。它在函数中有一些无意义的代码,并多次调用它,否则编译器只会内联整个函数调用,使测试无用。 (我知道这是 C 和 C++ 的奇怪组合......愚蠢的代码,但仍然可以证明问题)

    #include <stdio.h>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    struct demo
    {
        int a;
        char ch;
    };
    
    static  void __fastcall func(struct demo* d)
    {
        for(int i = 0; i < 100; i++) {
        std::vector<int> a;
        std::sort(begin(a), end(a));
        printf("THis is a test");
        printf("Hello world\n");
        }
    }
    
    int main()
    {
      //  void*p = (void*)&func;
        struct demo d;
        func(&d);
        func(&d);
        func(&d);
        func(&d);
        func(&d);
        func(&d);
        func(&d);
        //printf((const char*)p);
    }
    

    正如那里写的那样,函数调用编译成这个:-

        func(&d);
    00F61096  call        func (0F61000h)  
        func(&d);
    00F6109B  call        func (0F61000h)  
        func(&d);
    00F610A0  call        func (0F61000h)  
        func(&d);
    00F610A5  call        func (0F61000h)  
        func(&d);
    00F610AA  call        func (0F61000h)  
        func(&d);
    00F610AF  call        func (0F61000h)  
        func(&d);
    00F610B4  call        func (0F61000h)  
    

    这表明编译器确实会省略参数,如果不使用它。 但是如果我取消注释那里的两行以获取函数的地址,那么事情就会改变,它会生成这个:-

    00C71099  lea         ecx,[esp]  
    00C7109C  call        func (0C71000h)  
        func(&d);
    00C710A1  lea         ecx,[esp]  
    00C710A4  call        func (0C71000h)  
        func(&d);
    00C710A9  lea         ecx,[esp]  
    00C710AC  call        func (0C71000h)  
        func(&d);
    00C710B1  lea         ecx,[esp]  
    00C710B4  call        func (0C71000h) 
    

    它在哪里发送指针。

    我的假设是,在第一种情况下,编译器能够证明,如果它为函数生成了自定义调用约定,则不可能有任何用户可见的效果,但在第二种情况下,您可以使用指向函数的指针,该函数可以从另一个单独编译的模块中调用,该模块无法知道是否需要该参数。尽管在这种情况下,寄存器是否设置都无关紧要,但通常编译器需要坚持精确的调用模式,所以我假设它生成了最通用的代码,并且如果可以的话,不会使用自定义调用约定'不能证明它可以看到所有可以调用该函数的代码。

    无论如何,要回答这个问题,编译器不会总是在寄存器中传递未使用的参数,但我当然不会编写任何取决于此处任何特定行为的代码,因为在其他地方更改不相关的代码(获取函数的地址),改变了这种行为,我能看到的任何标准都无法保证。

    【讨论】:

    • 获取函数地址不允许优化未使用的参数是有道理的,因为函数指针是函数签名的类型,(即使您将其强制转换为 void*)和编译器通常无法确定您是否通过某种奇怪的魔法再次使用它。 +1 指出这个问题
    • 确实如此。我确实想知道通过链接时间代码生成,编译器是否可以使用自定义调用约定,因为它可以“看到”所有代码,但显然不是,至少在我的测试中是这样。
    • 我从我的测试中得出的主要结论是不要依赖这个。它可能会也可能不会在寄存器中传递参数。
    • 你的测试表明调用者没有填充寄存器,但它没有显示被调用者是否分配了一个(即后续参数是否不太容易溢出到堆栈上)
    • 非常感谢。我认为通常编译器会避免在获取函数地址时更改调用约定,因为指针可以跳转到不同的函数。
    【解决方案2】:

    首先,如果您使用正确的编译器标志(例如 -Wall),编译器应该警告您未使用的函数参数。我建议您将不使用的参数留在参数列表之外,虽然我猜他们会被优化掉。但是,您可以做的是确保编译您的代码两次,一次带有参数,一次不带参数,并在二进制文件之间执行diff 并查看它们是否匹配。我认为这应该取决于您使用的编译器和优化标志。

    干杯,

    安迪

    【讨论】:

    • 同意编译器确实对其进行了优化,但使用diff 对其进行检查 - 将无法检查差异(因为执行取决于许多其他因素,每个执行过程可能会有所不同,而且变量初始化不会那么多时间
    • @sansix 如果二进制文件相等,为什么行为会不同?
    • 顺便说一句- diff b/w 二进制文件是什么? (可能正在考虑其他方式)
    • @sansix 抱歉,我不明白你的问题。
    • 你的这个声明do a diff between the binaries - 我问的是什么不同的黑白二进制文件? “执行时间还是??”
    【解决方案3】:

    您可以在函数定义中省略参数。这将向编译器提示未使用特定参数,它将利用它并优化代码

    void func(结构演示 *) { ... }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多