clang может удалить вызов функции, если порядок обозначенных инициализаторов не соответствует объявлению полей

этот код:

#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,

Это известная ошибка?

3

Решение

Я думаю, что это по крайней мере несправедливо, если не ошибка, потому что предупреждение только на уровне педантичности, когда Clang просто удаляет все ссылки на foo в функции nocall, Мы можем подтвердить это, посмотрев код сборки в режиме отладки (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

Но для примечания соответствующие строки (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 делает вид, что обрабатывает синтаксис C99 в режиме C ++, когда это не так.


ИМХО, это ошибка, о которой можно сообщать лязгу, потому что как минимум диагностика должна быть выдана в соответствии с 1.4 Соответствием реализации [intro.compliance]

1 Набор диагностируемых правил состоит из всех синтаксических и семантических правил в этом международном стандарте Кроме
для тех правил, которые содержат явное обозначение, что «диагностика не требуется» или которые описаны как
в результате чего «неопределенное поведение».

2 Хотя в этом международном стандарте указаны только требования к реализациям C ++, эти требования
часто легче понять, если они сформулированы как требования к программам, частям программ или
выполнение программ. Такие требования имеют следующее значение:

  • Если программа не содержит нарушений правил в этом международном стандарте, соответствующая реализация
    в пределах своих ресурсов принимает и корректно исполняет эту программу2.
  • Если программа содержит нарушение любого диагностируемого правила или вхождение конструкции, описанной в
    этот стандарт как «условно поддерживаемый», когда реализация не поддерживает эту конструкцию,
    соответствующая реализация должна выдать хотя бы одно диагностическое сообщение.


8 Соответствующая реализация может иметь расширения (включая дополнительные библиотечные функции), если они
не изменять поведение любой правильно сформированной программы. Реализации необходимы для диагностики программ, которые
использовать такие расширения, которые плохо сформированы
в соответствии с этим международным стандартом. Сделав это, однако,
они могут компилировать и выполнять такие программы.

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]