看看哥德螺栓上的C code或C++ code这个小片段...
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);
}
gcc的早期版本将a()和c()都编译为两条指令,即加载z的地址,然后跳转到b。
所有现代编译器,我都尝试过“简化” a()来“制作堆栈框架,将z复制到堆栈上,调用b,拆除堆栈框架,但是将c()保留为两条指令的简单版本。
实际上什么都没有改变,实际上对于这种用例,现代编译器现在变慢了.....
有人知道为什么吗?
答案 0 :(得分:17)
C ++具有以下rule:
除非对象是位域或大小为零的子对象,否则该对象的地址为其所占据的第一个字节的地址。 两个生命周期重叠的非位域对象,如果一个嵌套在另一个域内,或者至少一个是零大小的子对象,并且它们属于不同类型,则它们可能具有相同的地址;否则,它们具有不同的地址并占用不相交的存储字节。
现在,查看以下代码:
#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();
}
在这里,c
被递归调用一次,因此根据规则,数组a
在每个递归级别中应具有不同的地址。 b
在第一次调用时存储a
,并在第二次调用时检查是否相同。使用兼容的编译器,它不应显示“问题!”。但是实际上,对于旧的编译器(GCC 4.1,clang 6.0),它会显示“问题!”,因此这些编译器违反了该标准。
仅在可以证明此更改不是observable的情况下,才允许编译器将a
设为静态:
根据“假设”规则,允许实现将两个对象存储在同一机器地址,或者如果程序无法观察到差异,则完全不存储对象
答案 1 :(得分:4)
我希望答案是编译器会执行您在代码中指定的操作-必须存在一个函数本地的自动存储数组,该数组不与其他线程共享,将传递给其他函数。以前,编译器可以使用as-if规则将其删除,并将其放置到其他位置,因为该语言在其模型中不存在线程,但是由于现在存在线程,因此它必须确保它不会意外导致错误。与他人分享。可能使它成为线程本地的,但这比仅函数本地的还要糟糕。
请注意,GCC从未进行过优化,但是Clang在6.0.0之后停止了优化。使用此优化甚至可能是Clang错误。