关于指针值是编译时常数的困惑

Confusion about pointer values being compile-time constatns

本文关键字:编译 常数 于指针 指针      更新时间:2023-10-16

在C++中,指针值可能是编译时常数。这是真的,否则,非类型模板参数和constexpr将无法使用指针。然而,据我所知,静态存储的函数和对象的地址(至少)在链接时是已知的,而不是在编译时。下图:

main.cpp

#include <iostream>
template <int* p>
void f() { std::cout << p << 'n'; }
extern int a;
int main() {
f<&a>();
}

a.cp

int a = 0;

我只是想知道在编译main.cpp时,a的地址怎么可能是已知的。我希望有人能向我解释一下

特别是,考虑这个

template <int* p, int* pp>
constexpr std::size_t f() { 
return (p + 1) == (pp + 7) ? 5 : 10; 
}
int main() {
int arr[f<&a, &b>()] = {};
}

arr的存储应该如何分配?

PLUS:这种机制似乎相当稳健。即使我启用了随机化基地址,也能获得正确的输出。

编译器在编译时不需要知道&a,就像它需要函数地址的值一样。

可以这样想:编译器将用&a作为参数实例化函数模板,并生成"对象代码"(以传递给链接器的任何格式)。目标代码看起来像(好吧,不会,但你明白了):

func f__<funky_mangled_name_to_say_this_is_f_for_&a>__:
reg0 <- /* linker, pls put &std::cout here */
reg1 <- /* hey linker, stuff &a in there ok? */
call std::basic_stream::operator<<(int*) /* linker, fun addr please? */
[...]

若实例化f<b&>,假设b是另一个全局静态,编译器会做同样的事情:

func f__<funky_mangled_name_to_say_this_is_f_for_&b>__:
reg0 <- /* linker, pls put &std::cout here */
reg1 <- /* hey linker, stuff &b in there ok? */
call std::basic_stream::operator<<(int*) /* linker, fun addr please? */
[...]

当你的代码需要调用其中一个时:

fun foo:
call f__<funky_mangled_name_to_say_this_is_f_for_&a>__ 
call f__<funky_mangled_name_to_say_this_is_f_for_&b>__

要调用的确切函数编码在损坏的函数名称中。生成的代码不依赖于&a&b的运行时值。编译器知道在运行时会有这样的事情(你告诉过它),这就是它所需要的。它会让链接器填补空白(或者如果你没有兑现承诺,就会对你大喊大叫)。


对于您的补充,恐怕我对constexpr规则不够熟悉,但我拥有的两个编译器告诉我,这个函数将在运行时进行评估,根据他们的说法,这会使代码不一致。(如果他们错了,那么上面的答案至少是不完整的。)

template <int* p, int* pp>
constexpr std::size_t f() { 
return (p + 1) == (pp + 7) ? 5 : 10; 
}
int main() {
int arr[f<&a, &b>()] = {};
}

clang 3.5在C++14标准符合模式:

$ clang++ -std=c++14 -stdlib=libc++ t.cpp -pedantic
t.cpp:10:10: warning: variable length arrays are a C99 feature [-Wvla-extension]
int arr[f<&a, &b>()];
^
1 warning generated.

GCC g++5.1,相同模式:

$ g++ -std=c++14 t.cpp -O3 -pedantic
t.cpp: In function 'int main()':
t.cpp:10:22: warning: ISO C++ forbids variable length array 'arr' [-Wvla]
int arr[f<&a, &b>()];

据我所知,静态存储和函数的变量在编译时只是作为符号/占位符存储在符号表中。当占位符被解析时,它处于链接阶段。

编译器输出保持占位符不变的机器代码。然后,链接器将变量/函数的占位符替换为它们各自的内存位置。因此,在这种情况下,如果您只编译main.cpp,而不编译.cpp并与之链接,那么您必然会面临链接器错误,正如您在这里看到的那样http://codepad.org/QTdJCgle(我只编译了main.cpp)