编译时const和形式参数

时间:2016-10-14 21:32:27

标签: c compile-time-constant

在以下示例中:

static inline void foo(const int varA)
{
  ...
  __some_builtin_function(varA);
  ...
}

int main()
{
  foo(10);
  return 0;
}

这里varA是否被视为编译时常量?

请注意我正在使用C,而不是使用C ++。

任何指向标准的链接或描述编译时常量的可靠文档,特别是它们与形式参数的关系都将非常感激。

2 个答案:

答案 0 :(得分:3)

不,varA不是编译时常量 - 每次调用函数时它肯定都不同。常量在标准中有一个特定的定义 - this answer中涉及一些关键细节,或者你可以阅读官方单词的标准。

那就是说,你可能想知道的是,如果编译器将视为常量,在你用例子中的常量值调用它的情况下。对于任何打开优化的合适编译器,答案是肯定的。调用内联和不断传播是实现这一目标的神奇之处。编译器将尝试内联对foo的调用,然后将10替换为参数,并将以递归方式执行。

我们来看看你的例子。我稍微修改了它以在main中使用 return foo(10),这样编译器就不会完全优化所有内容!我还选择了gcc的__builtin_popcount作为foo()调用的未指定函数。查看程序的this godbolt version,无需优化,在gcc 6.2中编译。装配看起来像:

foo(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        popcnt  eax, eax
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        mov     edi, 10
        call    foo(int)
        pop     rbp
        ret

这很简单。大多数foo()只是设置堆栈帧并且(毫无意义地)将edivarA参数)推送到堆栈。

当我们从main调用foo()时,我们传递10作为参数。很明显,这是一个常数并没有帮助。

好的,让我们用更现实的-O2设置 1 来编译它。 Here's what we get

main:
        mov     eax, 2
        ret

就是这样。整个事情只是return 2,差不多。所以编译器肯定能够看到10是一个常量值,并展开foo(10)。此外,它能够完全评估foo(10),直接计算10的popcount(二进制0b1010),而根本不需要popcount指令,只返回答案{{1 }}。

另请注意,编译器甚至没有生成2全部的任何代码。那是因为它可以看到它被声明为foo() 2 所以它只能从这个编译单元中调用,并且实际上没有调用者需要完整的函数,因为唯一的调用 - 网站被内联。所以foo就消失了。

那么标准所说的编译时常量只有助于理解编译器必须做什么,以及某些表达式可能合法使用,但它没有'帮助理解编译器在实践中将通过优化做什么。

这里的关键是你的方法static inline在与调用者相同的编译单元中声明,因此编译器可以内联并有效地优化这两个函数。如果它在一个单独的编译单元中,则不会发生这种情况,除非您使用某些选项,例如链接时代码生成。

1 事实证明,这里几乎任何优化设置都会产生相同的代码,因为转换非常简单。

2 实际上,{em> foo()inline足以使函数成为编译单元的本地函数。但是,如果省略两者,则会生成static的主体,因为它可以从单独编译的单元中调用。通过优化,主体看起来像:

foo()

答案 1 :(得分:2)

不,varA不是编译时常量,它只是一个const int变量,这意味着它的值可能不会在函数foo()内改变。但是,编译器可能推断您使用编译时常量10作为参数调用此函数,并编译此函数的一个版本,其中varA的每个出现都被替换与10