c++ - 奇怪的 Arduino C++ 编译错误取决于文件位置

标签 c++ build arduino

我最近在 Arduino 论坛 (https://forum.arduino.cc/index.php?topic=656968) 上将此作为一个可能的错误发布,但我怀疑他们会“哼哼,那又怎样?”这是因为它超出了 Arduino 图书馆组织的标准规则。不过还是想了解为什么它失败了,我怀疑这是我唯一能得到体面和/或明确答案的地方:

(为了记录,一个 arduino 库通常位于一个文件夹中,该文件夹有一个 library.properties 元数据“ list ”和一个包含所有文件的 src 文件夹。他们的构建系统自动编译(和链接!)src 中的所有 .cpp 文件 -不管它们是否被使用。显然在 99% 的情况下它们都被使用/需要,但是由于各种原因......)

开始纳入

版本 1.8.10
ESP8266 核心 2.6.2

在开始之前,我已经编写了几个成功的库,并且我熟悉“标准”的做事方式。我这样说是因为下面的内容可能被认为是“非标准的”,尽管它在语法上是有效的,并且除了一个实例之外,它都能成功编译和运行。我在尝试绕过编译 src 文件夹中所有内容的限制时偶然遇到它,无论你是否想要它

在失败的实例中,我得到一个我以前从未见过的编译错误,不理解也没有意义;但更糟糕的是:它根本不应该发生,因为它是 a) 有效代码 b) 在功能上与工作案例相同 - 并且当不像当前案例那样在单独的文件夹中时可以干净地编译......

根本问题是这段有效 C++ 代码的失败或成功之间的唯一显着区别是它在文件系统中的物理位置,这让我感到困惑。

首先是草图:

#include <H4P_WiFiX.h>

void f1(){}
void f2(){}
/*
 * H4P_WiFi has defaults of [](){}, [](){}
 */
//H4P_WiFi h4wifi(f1,f2); // compiles OK
//H4P_WiFi h4wifi([](){},f2); // compiles OK
//H4P_WiFi h4wifi(f1,[](){}); // compiles OK
//H4P_WiFi h4wifi(f1); // compiles OK
//H4P_WiFi h4wifi([](){},[](){}); // compiles OK
//H4P_WiFi h4wifi([](){}); // compiles OK
H4P_WiFi h4wifi; // using BOTH defaults (and ONLY that case), fails with the "helpful":
/*
C:\Users\phil\AppData\Local\Temp\ccnwDwvr.s: Assembler messages:

C:\Users\phil\AppData\Local\Temp\ccnwDwvr.s:21: Error: symbol `_ZNSt17_Function_handlerIFvvEZN8H4P_WiFiC1ESt8functionIS0_ES3_Ed_NUlvE_EE9_M_invokeERKSt9_Any_data' is already defined

C:\Users\phil\AppData\Local\Temp\ccnwDwvr.s:97: Error: symbol `_ZNSt14_Function_base13_Base_managerIZN8H4P_WiFiC1ESt8functionIFvvEES4_Ed_NUlvE_EE10_M_managerERSt9_Any_dataRKS7_St18_Manager_operation' is already defined

*/

void setup() {}

void loop() {}

现在奇怪的东西:
“奇数”文件夹结构是:
libraries/H4Pwtf
|--optional
     |
     |--H4P_WiFi.cpp
     |--H4P_WiFi.h
     |--H4Plugins.h

!--src
     |
     |--H4P_WiFiX,h
     |--H4Plugins.cpp
library.properties

src/H4P_WiFiX.h
#ifndef H4P_WiFi2_H
#define H4P_WiFi2_H
// Yes, I know, I know! See above - it's valid even though weird
#include"../optional/H4P_WiFi.cpp"

#endif

src/H4Plugins.cpp
#include"../optional/H4Plugins.h"

H4PluginService::H4PluginService(H4P_FN_VOID onConnect,H4P_FN_VOID onDisconnect){}

可选/H4P_Plugins.h
#ifndef H4P_HO
#define H4P_HO

#include<functional>

using H4P_FN_VOID = std::function<void()>;

class H4Plugin {};

class H4PluginService: public H4Plugin {
    public:
        H4PluginService(H4P_FN_VOID onConnect=[](){},H4P_FN_VOID onDisconnect=[](){});
};

#endif // H4P_HO

可选/H4P_WiFi.h
#ifndef H4P_WiFi_HO
#define H4P_WiFi_HO


#include"H4Plugins.h"

class H4P_WiFi: public H4PluginService{
    public:
        H4P_WiFi(H4P_FN_VOID onConnect=[](){},H4P_FN_VOID onDisconnect=[](){});
};

#endif // H4P_WiFi_HO

可选/H4P_WiFi.cpp
#include"H4P_WiFi.h"

H4P_WiFi::H4P_WiFi(H4P_FN_VOID onC,H4P_FN_VOID onD): H4PluginService(onC,onD){}

不用说,如果我将这一切都移到 src 并编辑“奇怪”包含回到 < > 变体而不是“”版本,并且不做奇怪的 .cpp 包含(obvs,因为它无论如何都会被编译)只是“在那里”),那么不应该失败的行不会失败,一切都会按预期编译和运行 100%。

但这不是重点:在我看来,构建系统似乎包含了两次不应该包含的东西……我就是无法理解那个错误!有任何想法吗?

结束包容

所以,溢出者:任何人都可以解释这个错误,以及它只在使用两个默认值时才会发生?

感谢“核桃”解决。一个遗留问题是编译器错误仅在两个相同的 lambdas-as-defaults 函数在类外定义时才会出现。我将它移回内部,使用我备受 mock (但有效)的非标准文件组织重新编译,并且:Bingo!代码现在完全按预期运行,并且仅在 #included 模块中编译。我是一只快乐的兔子。感谢其他也做出贡献的人。结案。 +1 到堆栈溢出

最佳答案

在替换 #include 之后,您可以将有问题的代码归结为以下最小可重现测试用例。指令并删除不影响结果错误的部分:

#include<functional>

using H4P_FN_VOID = std::function<void()>;

class H4P_WiFi{
    public:
        H4P_WiFi(H4P_FN_VOID onConnect=[](){}, H4P_FN_VOID onDisconnect=[](){});
};

H4P_WiFi::H4P_WiFi(H4P_FN_VOID, H4P_FN_VOID) {}

H4P_WiFi h4wifi;

int main() {}

将其编译为单个编译单元确实仍然会使用 GCC 生成错误消息,即使在最新版本 9.2 上也是如此,而 Clang 对此没有任何问题,请参阅 this godbolt .

GCC 9.2 在组装过程中给出的错误消息与您的非常相似:
/tmp/cceVlRkg.s: Assembler messages:
/tmp/cceVlRkg.s:77: Error: symbol `_ZNSt14_Function_base13_Base_managerIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_E10_M_managerERSt9_Any_dataRKS7_St18_Manager_operation' is already defined
/tmp/cceVlRkg.s:131: Error: symbol `_ZNSt17_Function_handlerIFvvEZN8H4P_WiFiC4ESt8functionIS0_ES3_Ed_UlvE_E9_M_invokeERKSt9_Any_data' is already defined

拆解我们得到的符号名称:
std::_Function_handler<void (), H4P_WiFi::H4P_WiFi(std::function<void ()>, std::function<void ()>)::{default arg#1}::{lambda()#1}>::_M_invoke(std::_Any_data const&)
std::_Function_base::_Base_manager<H4P_WiFi::H4P_WiFi(std::function<void ()>, std::function<void ()>)::{default arg#1}::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<H4P_WiFi::H4P_WiFi(std::function<void ()>, std::function<void ()>)::{default arg#1}::{lambda()#1}> const&, std::_Manager_operation)

我很确定这是 GCC 中的编译器错误。在 GCC 主干上(在 Godbolt 上),我们甚至得到一个 ICE(内部编译器错误):
<source>:12:16: error: Two symbols with same comdat_group are not linked by the same_comdat_group list.
   12 | H4P_WiFi h4wifi;
      |                ^
_ZNSt9_Any_data9_M_accessIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_EERT_v/1079 (_Tp& std::_Any_data::_M_access() [with _Tp = H4P_WiFi::<lambda()>]) @0x7f019ddbd000
  Type: function definition analyzed
  Visibility: public weak comdat comdat_group:_ZNSt9_Any_data9_M_accessIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_EERT_v one_only visibility_specified
  previous sharing asm name: 1078
  References: 
  Referring: 
  Function flags: body
  Called by: static void std::_Function_base::_Base_manager<_Functor>::_M_destroy(std::_Any_data&, std::true_type) [with _Functor = H4P_WiFi::<lambda()>]/1077 
  Calls: void* std::_Any_data::_M_access()/217 
_ZNSt9_Any_data9_M_accessIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_EERT_v/1078 (_Tp& std::_Any_data::_M_access() [with _Tp = H4P_WiFi::<lambda()>]) @0x7f019ddb7e10
  Type: function definition analyzed
  Visibility: public weak comdat comdat_group:_ZNSt9_Any_data9_M_accessIZN8H4P_WiFiC4ESt8functionIFvvEES4_Ed_UlvE_EERT_v one_only visibility_specified
  next sharing asm name: 1079
  References:
  Referring: 
  Function flags: body
  Called by: static void std::_Function_base::_Base_manager<_Functor>::_M_destroy(std::_Any_data&, std::true_type) [with _Functor = H4P_WiFi::<lambda()>]/1073 
  Calls: void* std::_Any_data::_M_access()/217 
<source>:12:16: internal compiler error: symtab_node::verify failed
Please submit a full bug report,
with preprocessed source if appropriate.
See <https://gcc.gnu.org/bugs/> for instructions.
Compiler returned: 1

this GCC bug report 中报告了一个类似的错误。 .显然,当使用至少两个默认参数调用时,它只显示类的类外定义的成员函数,每个默认参数都包含一个相等签名的 lambda。

确实,如果我例如将参数添加到 lambda 的参数列表之一(例如 [](auto...){}[](int=0){} ),但不添加其他参数,则没有错误。
但是,如果我对两者都添加相同的内容,我们又会遇到同样的错误。

关于c++ - 奇怪的 Arduino C++ 编译错误取决于文件位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59586701/

相关文章:

c++ - Near 和 Far 指针

c++ - ppl,如何正确使用它?

c++ - 为什么构建C++代码时编译+链接,而不是直接生成可执行文件

java - 从动态 Web 项目引用另一个项目,而不将其转换为多面项目

c++ - 如果我传递临时引用并将其存储为类成员会怎样?

c++ - 除非用户指定自己的头文件,否则我可以编写一个包含默认头文件的库吗?

C++ 常量转换

c++ - XE2 - Indy TCPServer : what is the best way to write and read a listview?

java - Android 构建失败 - java.lang.IllegalArgumentException : already added: Lcom/google/api/client/escape/CharEscapers;

c++ - 是否可以在 iOS 上编译为 Arduino 编写的代码?