c++ - 当指定初始化程序的顺序与字段声明不对应时,clang 可以删除函数调用

标签 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 的情况下创建代码。

这是已知错误吗?

最佳答案

我认为即使不是错误,这至少也是不公平的,因为当 Clang 简单地删除函数 nocall 中对 foo 的所有引用时,警告仅处于学究级别。我们可以通过在 Debug模式下查看汇编代码 (c++ -S -g file.cpp) 来确认编译器如何准确解释每一行。

当我们查看.s生成的文件时,我们可以看到在调用中,第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 The set of diagnosable rules consists of all syntactic and semantic rules in this International Standard except for those rules containing an explicit notation that “no diagnostic is required” or which are described as resulting in “undefined behavior.”
2 Although this International Standard states only requirements on C++ implementations, those requirements are often easier to understand if they are phrased as requirements on programs, parts of programs, or execution of programs. Such requirements have the following meaning:

  • If a program contains no violations of the rules in this International Standard, a conforming implementation shall, within its resource limits, accept and correctly execute2 that program.
  • If a program contains a violation of any diagnosable rule or an occurrence of a construct described in this Standard as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.

...
8 A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this International Standard. Having done so, however, they can compile and execute such programs.

关于c++ - 当指定初始化程序的顺序与字段声明不对应时,clang 可以删除函数调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41723401/

相关文章:

c++ - 多个隐式转换运算符

ios - 从 initWithCoder : 调用另一个 init 时指定的初始化程序警告

c++ - 在 C++ 中初始化指向结构的指针数组

c++ - 哪些平台具有不兼容的 C 和 C++ ABI?

c++ - 我需要一个测试用例来证明我的算法/代码是错误的

c++ - 用指针直接访问STL列表的元素

c++ - 模板化转换运算符类型推导在 clang 和 gcc 中不同

c++ - cast 运算符函数在 g++ 中编译良好,但在其他编译器中则不然。为什么?

ios - (iOS)自定义初始化UIViewController时黑屏

c++ - 棋盘游戏AI设计: choosing STL data container