当指定的初始值设定项的顺序与字段声明不对应时,clang可以删除函数调用

时间:2017-01-18 15:36:44

标签: c++ clang++ designated-initializer

此代码:

#include <iostream>
struct Acc {
    int a;
};
struct Buu {
    int b;
};

struct Foo {
    const Acc& acc;
    Buu& buu;
};

void printInfo( const Foo& ) {
    std::cout << "hi!" << std::endl;
}

void call( Buu& buu ) {
    Acc acc = { 1 };
    Foo foo = {
        .acc = acc,
        .buu = buu,
    };
    std::cout << "before" << std::endl;
    printInfo( foo );
    std::cout << "after" << std::endl;
}
void noCall( Buu& buu ) {
    Acc acc = { 1 };
    Foo foo = {
        .buu = buu,
        .acc = acc,
    };
    std::cout << "before" << std::endl;
    printInfo( foo );
    std::cout << "after" << std::endl;
}

int main() {
    Buu buu = { 2 };
    call( buu );
    noCall( buu );
    return 0;
}

当被clang(我试过3.7.0,3.7.1)卷入时将会出局:

before
hi!
after
before
after

printInfo的第二次调用已被删除... callnoCall之间的区别仅在指定的初始值设定项的顺序。

使用-pedantic选项会产生警告,指定初始值设定项是C99的功能但不是C ++,但仍然创建代码而没有第二次调用printInfo

是否知道bug?

1 个答案:

答案 0 :(得分:3)

我认为如果不是一个bug,这至少是不公平的,因为当Clang简单地删除函数foo中对nocall的所有引用时,警告只是在pedineic级别。我们可以通过在调试模式(c++ -S -g file.cpp)中查看汇编代码来确认它,以确切了解编译器如何解释每一行。

当我们查看.s genererated文件时,我们可以看到,在通话中,会生成第20行Foo foo = {...和25 printInfo(foo)

    .loc    1 20 0                  # ess.cpp:20:0
    movq    %rcx, -64(%rbp)
    movq    -40(%rbp), %rcx
.Ltmp45:
    movq    %rcx, -56(%rbp)
    .loc    1 24 0                  # ess.cpp:24:0
    movq    %rax, %rdi
    callq   _ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc
    leaq    -64(%rbp), %rdi
    leaq    _ZNSt3__14endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_, %rcx
    movq    %rax, -24(%rbp)
    movq    %rcx, -32(%rbp)
    movq    -24(%rbp), %rax
    .loc    5 322 0                 # /usr/include/c++/v1/ostream:322:0
.Ltmp46:
    movq    %rdi, -72(%rbp)         # 8-byte Spill
    movq    %rax, %rdi
    callq   *-32(%rbp)
.Ltmp47:
    .loc    1 25 0                  # ess.cpp:25:0
    movq    -72(%rbp), %rdi         # 8-byte Reload
    movq    %rax, -80(%rbp)         # 8-byte Spill
    callq   _Z9printInfoRK3Foo
    leaq    _ZNSt3__14coutE, %rdi
    leaq    .L.str2, %rsi

但是对于nocall,相应的行(30和35)不是:

    .loc    1 29 0 prologue_end     # ess.cpp:29:0
.Ltmp57:
    movl    .L_ZZ6noCallR3BuuE3acc, %ecx
    movl    %ecx, -48(%rbp)
    .loc    1 34 0                  # ess.cpp:34:0
    movq    %rax, %rdi
    callq   _ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc
    leaq    _ZNSt3__14coutE, %rdi
    leaq    .L.str2, %rsi
    leaq    _ZNSt3__14endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_, %rdx
    movq    %rax, -24(%rbp)
    movq    %rdx, -32(%rbp)
    movq    -24(%rbp), %rax
    .loc    5 322 0                 # /usr/include/c++/v1/ostream:322:0
.Ltmp58:
    movq    %rdi, -72(%rbp)         # 8-byte Spill
    movq    %rax, %rdi
    movq    %rsi, -80(%rbp)         # 8-byte Spill
    callq   *-32(%rbp)
.Ltmp59:
    .loc    1 36 0                  # ess.cpp:36:0
    movq    -72(%rbp), %rdi         # 8-byte Reload
    movq    -80(%rbp), %rsi         # 8-byte Reload
    movq    %rax, -88(%rbp)         # 8-byte Spill
    callq   _ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc
    leaq    _ZNSt3__14endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_, %rdx
    movq    %rax, -8(%rbp)
    movq    %rdx, -16(%rbp)
    movq    -8(%rbp), %rdi
    .loc    5 322 0                 # /usr/include/c++/v1/ostream:322:0
.Ltmp60:
    callq   *-16(%rbp)
.Ltmp61:
    .loc    1 37 0                  # ess.cpp:37:0

cpp文件中的编号行为:

18  void call( Buu& buu ) {
19      Acc acc = { 1 };
20      Foo foo = {
21          .acc = acc,
22          .buu = buu,
23      };
24      std::cout << "before" << std::endl;
25      printInfo( foo );
26      std::cout << "after" << std::endl;
27  }
28  void noCall( Buu& buu ) {
29      Acc acc = { 1 };
30      Foo foo = {
31              .buu = buu,
32              .acc = acc
33      };
34      std::cout << "before" << std::endl;
35      printInfo( foo );
36      std::cout << "after" << std::endl;
37  }

我的理解是clang假装在C ++模式下处理C99语法,而不是。

恕我直言,这是一个可以向clang报告的错误,因为至少应该按照1.4实施合规性发布诊断[intro.compliance]

  

1 可诊断规则集包含本国际标准中的所有语法和语义规则   对于包含“无需诊断”或描述为“无需诊断”的明确表示法的规则   导致“未定义的行为”。   
2虽然本国际标准仅规定了对C ++实现的要求,但这些要求   如果它们被表达为对程序,程序部分或者程序的要求,则通常更容易理解   执行程序。这些要求具有以下含义:

     
      
  • 如果某个程序不违反本国际标准中的规则,则符合规定   应在其资源限制内接受并正确执行该程序。
  •   
  • 如果某个程序包含违反任何可诊断规则或某个构造中出现的构造   当实现不支持该构造时,此标准为“有条件支持”,   符合要求的实施应至少发布一条诊断信息
  •   
     

...
8符合要求的实施可能有扩展(包括额外的库函数),只要它们有   不改变任何格式良好的程序的行为。 诊断程序需要实现   根据本国际标准,使用格式错误的扩展。但是,这样做了   他们可以编译和执行这样的程序。