繁体   English   中英

GCC的函数__attribute__s是否可以使用虚函数?

[英]Do GCC's function __attribute__s work with virtual functions?

GCC C ++编译器通过函数属性提供一系列扩展,例如:

int square(int) __attribute__((const));

两个属性,特别是constpure ,允许您声明函数的评估没有副作用,并且仅依赖于其参数( const ),或仅依赖于其参数和全局变量( pure )。 这允许公共子表达式消除,这可能导致这样的函数被调用的次数少于在代码中写入的次数。

我的问题是这是否可以安全,正确和合理地用于虚拟成员函数:

struct Foo
{
    virtual int square(int) __attribute__((pure));   // does that make sense?
};

这有什么明智的语义吗? 它是否允许? 还是只是被忽略了? 我担心在GCC文档中找不到答案。

这个问题的原因是存在一系列编译器选项-Wsuggest-attribute ,这使得GCC产生了可以放置这些属性以改进代码的建议。 然而,它似乎最终甚至为虚拟功能提出了这些建议,我想知道是否应该认真对待这些建议。

它被GCC允许和接受。 它通常被忽略(你知道这是因为GCC总是输出warning: attribute ignored ,当它完全忽略属性时,它不在这里)。 但也阅读了最后一段。

是否有意义是另一个问题。 虚函数可以重载,您可以在没有属性的情况下重载它。 这将打开下一个问题: 是合法的吗?

可以预期具有不同属性的函数具有不同的签名(例如使用const限定符或不同的异常规范),但事实并非如此。 GCC在这方面将它们视为完全相同。 您可以通过从Foo派生Bar并实现非const的成员函数来验证这一点。 然后

decltype(&Bar::square) f1 = &Foo::square;
decltype(&Foo::square) f2 = &Bar::square;

将在第二行中给出编译时错误,但不会在第一行中给出,正如您所期望的那样。 如果签名不同(尝试使用const限定函数,而不是使用属性!), 第一行就会产生错误。

最后,它是否安全,是否有意义? 它始终是安全的,编译器必须确保它是安全的。 它在语义上有意义,在限度内。

从语义的角度来看,声明一个函数constpure是“正确的”,如果它真的是它。 但是,如果你向界面的用户做出“承诺”,这可能不是真的,这有点尴尬。 有人可能会调用此函数,其中所有外观都是派生类上的const ,而这不是真的。 编译器必须确保它仍然有效,但用户对性能的期望可能与现实不同。

将函数标记为constpure可能允许编译器更好地进行优化。 现在,使用虚函数,这有点难,因为对象可能是派生类型,而这不是真的!
这必然意味着编译器必须忽略优化属性,除非可以静态解析虚拟调用。 这可能经常是这种情况,但一般情况下并非如此。

在您链接到的文档中,在const属性的描述下有这个注释:

请注意,具有指针参数并检查指向的数据的函数不能声明为const。

我会说这包括成员函数,因为它们有一个隐式指针参数(并且至少虚函数需要检查它才能到达vtable,不是吗?)。

他们似乎在这个帖子中得到了类似的结论: http//gcc.gnu.org/ml/gcc/2011-02/msg00460.html

第一个问题是这些属性是否具有虚拟方法的有效语义。 在我看来他们这样做。 我希望如果一个虚函数标记为纯,你会承诺编译器所有实现只依赖于它们的参数和全局内存中的数据(并且不要改变它),其中全局内存中的数据也包括内容物体。 如果虚函数被标记为const,这意味着它只能依赖于它的参数,甚至不允许检查对象的内容。 编译器必须强制所有重写虚拟方法声明属性至少与其父级一样强。

接下来的问题是GCC是否使用这些属性进行优化。 在下面的测试程序中,您可以看到版本4.6.3没有(尝试使用-O3编译汇编程序,您将看到循环已展开)。

struct A {
    virtual int const_f(int x) __attribute__((const)) = 0;
};

int do_stuff(A *a) {
    int b = 0;
    for (int i=0; i<10; i++) {
        b += a->const_f(0);
    }
    return b;
}

即使在编译时已知类型的以下程序中,编译器也不会优化循环。

struct A {
    virtual int const_f(int x) __attribute__((const)) = 0;
};

struct B : public A {
    int const_f(int x) __attribute__((const));
};

int do_stuff(B *b) {
    int c = 0;
    for (int i=0; i<10; i++) {
        c += b->const_f(0);
    }
    return c;
}

从A中删除继承(从而使该方法非虚拟)允许编译器进行预期的优化。

没有关于这些属性的标准或文档,因此我们可以获得的最佳参考是实现。 由于它们目前没有任何效果,我建议避免将它们用于虚拟方法,以防将来行为意外地发生变化。

当且仅当通过静态绑定调用该函数时, G ++ 4.8.1似乎尊重虚拟成员函数上的pureconst函数属性。

给出以下源代码:

struct Base {
    void w();
    void x() __attribute__ ((const));
    virtual void y();
    virtual void z() __attribute__ ((const));
};

struct Derived : public Base {
    void w() __attribute__ ((const));
    void x();
    virtual void y() __attribute__ ((const));
    virtual void z();
};

void example() {
    Base b, *pb;
    Derived d, *pd;
    b.w(); // called
    b.x(); // not called
    b.y(); // called
    b.z(); // not called
    pb->w(); // called
    pb->x(); // not called
    pb->y(); // called
    pb->z(); // called
    d.w(); // not called
    d.x(); // called
    d.y(); // not called
    d.z(); // called
    pd->w(); // not called
    pd->x(); // called
    pd->y(); // called
    pd->z(); // called
}

...编译器生成以下(摘录)汇编代码:

void example() {
    Base b, *pb;
    Derived d, *pd;
    b.w(); // called
  1c:   e8 00 00 00 00          callq  21 <_Z7examplev+0x21>
    b.x(); // not called
    b.y(); // called
  21:   48 89 e7                mov    %rsp,%rdi
  24:   e8 00 00 00 00          callq  29 <_Z7examplev+0x29>
    b.z(); // not called
    pb->w(); // called
  29:   48 89 df                mov    %rbx,%rdi
  2c:   e8 00 00 00 00          callq  31 <_Z7examplev+0x31>
    pb->x(); // not called
    pb->y(); // called
  31:   48 8b 2b                mov    (%rbx),%rbp
  34:   48 89 df                mov    %rbx,%rdi
  37:   ff 55 00                callq  *0x0(%rbp)
    pb->z(); // called
  3a:   48 89 df                mov    %rbx,%rdi
  3d:   ff 55 08                callq  *0x8(%rbp)
    d.w(); // not called
    d.x(); // called
  40:   48 8d 7c 24 10          lea    0x10(%rsp),%rdi
  45:   e8 00 00 00 00          callq  4a <_Z7examplev+0x4a>
    d.y(); // not called
    d.z(); // called
  4a:   48 8d 7c 24 10          lea    0x10(%rsp),%rdi
  4f:   e8 00 00 00 00          callq  54 <_Z7examplev+0x54>
    pd->w(); // not called
    pd->x(); // called
  54:   48 89 df                mov    %rbx,%rdi
  57:   e8 00 00 00 00          callq  5c <_Z7examplev+0x5c>
    pd->y(); // called
  5c:   48 8b 2b                mov    (%rbx),%rbp
  5f:   48 89 df                mov    %rbx,%rdi
  62:   ff 55 00                callq  *0x0(%rbp)
    pd->z(); // called
  65:   48 89 df                mov    %rbx,%rdi
  68:   ff 55 08                callq  *0x8(%rbp)
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM