在以下示例中:
static inline void foo(const int varA)
{
...
__some_builtin_function(varA);
...
}
int main()
{
foo(10);
return 0;
}
这里varA是否被视为编译时常量?
请注意我正在使用C,而不是使用C ++。
任何指向标准的链接或描述编译时常量的可靠文档,特别是它们与形式参数的关系都将非常感激。
答案 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()
只是设置堆栈帧并且(毫无意义地)将edi
(varA
参数)推送到堆栈。
当我们从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
。