此代码:
#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
的第二次调用已被删除... call
和noCall
之间的区别仅在指定的初始值设定项的顺序。
使用-pedantic
选项会产生警告,指定初始值设定项是C99的功能但不是C ++,但仍然创建代码而没有第二次调用printInfo
。
是否知道bug?
答案 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符合要求的实施可能有扩展(包括额外的库函数),只要它们有 不改变任何格式良好的程序的行为。 诊断程序需要实现 根据本国际标准,使用格式错误的扩展。但是,这样做了 他们可以编译和执行这样的程序。