看一下godbolt 上这个C代码或C++代码的小片段......
void b( char const *c);
void a(void)
{
char const z[] = {0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf, 0xa};
b(z);
}
void c(void)
{
static char const z[] = {0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf, 0xa};
b(z);
}
Run Code Online (Sandbox Code Playgroud)
早期版本的gcc将a()和c()编译为两条指令,加载z的地址,跳转到b.
所有现代编译器我都试图"悲观化"a()以"制作堆栈帧,将z复制到堆栈,调用b,拆除堆栈帧,但将c()作为两个指令简单版本.
实际上没有任何改变,在实践中现代编译器现在对这个用例来说更慢.....
有谁知道为什么?
gez*_*eza 17
C++有以下规则:
除非对象是零字段或零大小的子对象,否则该对象的地址是它占用的第一个字节的地址.如果一个嵌套在另一个中,则两个具有重叠生命周期且不是位字段的对象可以具有相同的地址,或者如果至少一个对象是零大小的子对象并且它们具有不同类型; 否则,它们具有不同的地址并占据不相交的存储字节.
现在,看看这段代码:
#include <stdio.h>
void c();
void b(const char *a) {
static const char *p = 0;
if (!p) {
p = a;
c();
} else {
if (a==p) {
printf("problem!\n");
}
}
}
void c() {
const char a[] = { 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf };
b(a);
}
int main() {
c();
}
Run Code Online (Sandbox Code Playgroud)
这里,c递归调用一次,因此根据规则,数组a在每个递归级别应该有不同的地址.在第一次调用时b存储a,在第二次调用时,它会检查它是否相同.使用符合标准的编译器,它不应该打印"问题!".但实际上,使用旧的编译器(GCC 4.1,clang 6.0),它会打印出"问题!",因此这些编译器违反了标准.
a只有在可以证明此更改不可观察的情况下,才允许编译器生成静态:
在"as-if"规则下,允许实现在同一个机器地址存储两个对象,或者如果程序无法观察到差异,则根本不存储对象