为什么现在“ {static const char a [] = {...}”和“ {const char a [] = {...}”之间有区别?

时间:2018-10-30 02:35:35

标签: c++ c

看看哥德螺栓上的C codeC++ 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()保留为两条指令的简单版本。

实际上什么都没有改变,实际上对于这种用例,现代编译器现在变慢了.....

有人知道为什么吗?

2 个答案:

答案 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错误。