ESP8285 Arduino: linker fails to find move operators

I am in the process of porting a project from the ESP32 to an ESP8285. This is involving a class with move constructor and move assignment operator defined.

While this is running fine on the ESP32 and even compiling without warnings for the ESP8285, the linker throws these error messages all referring to the move constructor and assignment operator:

Linking .pio\build\esp8285\firmware.elf
c:/users/micha/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld.exe: .pio\build\esp8285\src\main.cpp.o: in function `std::_Function_handler<ModbusMessage (ModbusMessage), ModbusMessage (*)(ModbusMessage)>::_M_invoke(std::_Any_data const&, ModbusMessage)':
main.cpp:(.text._ZNSt17_Function_handlerIF13ModbusMessageS0_EPS1_E9_M_invokeERKSt9_Any_dataS0_[std::_Function_handler<ModbusMessage (ModbusMessage), ModbusMessage (*)(ModbusMessage)>::_M_invoke(std::_Any_data const&, ModbusMessage)]+0x12): undefined reference to `ModbusMessage::ModbusMessage(ModbusMessage&&)'
c:/users/micha/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld.exe: .pio\build\esp8285\src\ModbusServerTCPasync.cpp.o:(.text._ZN20ModbusServerTCPasync9mb_client6onDataEPhj+0x28): undefined reference to `ModbusMessage::ModbusMessage(ModbusMessage&&)'
c:/users/micha/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld.exe: .pio\build\esp8285\src\ModbusServerTCPasync.cpp.o:(.text._ZN20ModbusServerTCPasync9mb_client6onDataEPhj+0x2c): undefined reference to `ModbusMessage::operator=(ModbusMessage&&)'
c:/users/micha/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld.exe: .pio\build\esp8285\src\ModbusServerTCPasync.cpp.o: in function `ModbusServerTCPasync::mb_client::onData(unsigned char*, unsigned int)':
ModbusServerTCPasync.cpp:(.text._ZN20ModbusServerTCPasync9mb_client6onDataEPhj+0x2ee): undefined reference to `ModbusMessage::ModbusMessage(ModbusMessage&&)'
c:/users/micha/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld.exe: ModbusServerTCPasync.cpp:(.text._ZN20ModbusServerTCPasync9mb_client6onDataEPhj+0x30c): undefined reference to `ModbusMessage::operator=(ModbusMessage&&)'
collect2.exe: error: ld returned 1 exit status
*** [.pio\build\esp8285\firmware.elf] Error 1

I tried a clean-up of the project and a rebuild, but that did not change a bit.
c_cpp_properties.json has these entries:

"cStandard": "c99",
"cppStandard": "c++11",

What can I do to fix that?

What C++ version was your ESP32 project built against? (“Verbose Build” and look for -std=c++.. flag)

What’s the minimal C++ code to reproduce the problem?

Have you tried using a newer compiler?

By adding

build_flags = -D_GLIBCXX_USE_CXX11_ABI=0
platform_packages = 
    toolchain-xtensa@5.100200.201223

to the platformio.ini you can use a GCC version 10.2.0 instead of the 4.8.2 default.

The first line is needed to fix C++ string ABI incompatibilities when using the newer compiler.

First of all thanks once more for helping me :wink:

  • How do I trigger a “verbose build”? I could provide you the .gcc-flags.json for the ESP32 project, that has a -std=gnu++11 inside:
{
  "gccDefaultCppFlags": "-fsyntax-only -fno-rtti -fno-exceptions -std=gnu++11 -Os -g3 -Wall -nostdlib -Wpointer-arith -Wno-error=unused-but-set-variable -Wno-error=unused-variable -mlongcalls -ffunction-sections -fdata-sections -fstrict-volatile-bitfields -Wno-error=deprecated-declarations -Wno-error=unused-function -Wno-unused-parameter -Wno-sign-compare -fstack-protector -fexceptions -Werror=reorder -DPLATFORMIO=40303 -DARDUINO_ESP32_DEV -DESP32 -DESP_PLATFORM -DF_CPU=240000000L -DHAVE_CONFIG_H -DMBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\" -DARDUINO=10805 -DARDUINO_ARCH_ESP32 -DARDUINO_VARIANT=\"esp32\" -DARDUINO_BOARD=\"AZ-Delivery\\ ESP-32\\ Dev\\ Kit\\ C\\ V4\"",
}
  • I tried the new toolchain first, but that got me numerous errors concerning the use of malloc in the ESP8285 Arduino core
  • I will try to reduce the effect to a minimal code, but that may take a while.

Standard project tasks window
grafik

or pio run -v. But you seem to have already found it.

Looks like C++11 with GNU extensions then, for the ESP32. ESP8266 uses only C++. So you might try as well

build_unflags = -std=c++11
build_flags = -std=gnu++11

I compiled a normal firmware with extra code adapted from Move constructors - cppreference.com and the above platformio.ini additions to a standard NodeMCUv2 environment, and it compiled fine, no malloc error / compile failures

Well, the move operators were part of C++11 anyway, so I doubt that GNU made the difference. I tried with the build_unflags=/build_flags= option you proposed to no avail.

The error I got with the new toolchain are concentrating on one source file in the ESP8266WiFi domain:

In file included from c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\cstdlib:75,
                 from c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\bits\stl_algo.h:59,
                 from c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\algorithm:62,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\src\WiFiClientSecureBearSSL.cpp:27:
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\stdlib.h:94:44: error: expected initializer before '__result_use_check'
   94 | void *calloc(size_t, size_t) __malloc_like __result_use_check
      |                                            ^~~~~~~~~~~~~~~~~Compiling .pio\build\esp8285\libe3a\ESPAsyncTCP\ESPAsyncTCPbuffer.cpp.o
~
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\stdlib.h:112:36: error: expected initializer before '__result_use_check'
  112 | void *malloc(size_t) __malloc_like __result_use_check __alloc_size(1) _NOTHROW;
      |                                    ^~~~~~~~~~~~~~~~~~
Compiling .pio\build\esp8285\libe3a\ESPAsyncTCP\SyncClient.cpp.o
In file included from c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\cstdlib:75,
                 from c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\bits\stl_algo.h:59,
                 from c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\algorithm:62,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\src\WiFiClientSecureBearSSL.cpp:27:
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\stdlib.h:149:31: error: expected initializer before '__result_use_check'
  149 | void *realloc(void *, size_t) __result_use_check __alloc_size(2) _NOTHROW;
      |                               ^~~~~~~~~~~~~~~~~~
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\stdlib.h:151:44: error: expected initializer before '__result_use_check'
  151 | void *reallocarray(void *, size_t, size_t) __result_use_check __alloc_size2(2, 3);
      |                                            ^~~~~~~~~~~~~~~~~~
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\stdlib.h:152:32: error: expected initializer before '__result_use_check'
  152 | void *reallocf(void *, size_t) __result_use_check __alloc_size(2);
      |                                ^~~~~~~~~~~~~~~~~~
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\stdlib.h:293:6: error: expected initializer before '__result_use_check'
  293 |      __result_use_check;
      |      ^~~~~~~~~~~~~~~~~~
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\stdlib.h:336:52: error: expected initializer before '__alloc_align'
  336 | void * aligned_alloc(size_t, size_t) __malloc_like __alloc_align(1)
      |                                                    ^~~~~~~~~~~~~
In file included from c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\bits\stl_algo.h:59,
                 from c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\algorithm:62,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\src\WiFiClientSecureBearSSL.cpp:27:
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\cstdlib:144:11: error: 'calloc' has not been declared in '::'
  144 |   using ::calloc;
      |           ^~~~~~
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\cstdlib:151:11: error: 'malloc' has not been declared in '::'
  151 |   using ::malloc;
      |           ^~~~~~
c:\users\micha\.platformio\packages\toolchain-xtensa\xtensa-lx106-elf\include\c++\10.2.0\cstdlib:164:11: error: 'realloc' has not been declared in '::'
  164 |   using ::realloc;
      |           ^~~~~~~
In file included from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\tools\sdk\lwip\include/arch/cc.h:43,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\tools\sdk\lwip\include/lwip/arch.h:43,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\tools\sdk\lwip\include/lwip/debug.h:35,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\tools\sdk\lwip\include/lwip/opt.h:46,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\tools\sdk\lwip\include/lwip/init.h:35,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\cores\esp8266/IPAddress.h:27,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\src\ESP8266WiFi.h:31,
                 from C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\src\WiFiClientSecureBearSSL.cpp:34:
c:\users\micha\.platformio\packages\framework-arduinoespressif8266\cores\esp8266\core_esp8266_features.h: In function 'T* arduino::new0(size_t, TConstructorArgs ...)':       
c:\users\micha\.platformio\packages\framework-arduinoespressif8266\cores\esp8266\core_esp8266_features.h:50:22: error: there are no arguments to 'malloc' that depend on a template parameter, so a declaration of 'malloc' must be available [-fpermissive]
   50 |         T* ptr = (T*)malloc(offset + (arraysize * sizeof(T)));
      |                      ^~~~~~
c:\users\micha\.platformio\packages\framework-arduinoespressif8266\cores\esp8266\core_esp8266_features.h:50:22: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\src\WiFiClientSecureBearSSL.cpp: In member function 'uint8_t* BearSSL::WiFiClientSecure::_streamLoad(Stream&, size_t)':
C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\src\WiFiClientSecureBearSSL.cpp:1604:29: error: 'malloc' was not declared in this scope
 1604 |   uint8_t *dest = (uint8_t*)malloc(size);
      |                             ^~~~~~
C:\Users\Micha\.platformio\packages\framework-arduinoespressif8266\libraries\ESP8266WiFi\src\WiFiClientSecureBearSSL.cpp:47:1: note: 'malloc' is defined in header '<cstdlib>'; did you forget to '#include <cstdlib>'?
   46 | #include "coredecls.h"
  +++ |+#include <cstdlib>
   47 |
*** [.pio\build\esp8285\lib766\ESP8266WiFi\WiFiClientSecureBearSSL.cpp.o] Error 1

Yep I can reproduce that, as soon as #include <ESP8266WiFi.h> is done, the compiler throws these errors. Hm now it get’s really hard if the compiler isn’t able to compile the framework’s library anymore.

I’d suggest you try and find the minimal code that reproduces the error, and then also check in the Arduino IDE if it fails to compile for a NodeMCUv2.

This error was also encountered in Possible to use C++17 - #4 by maxgerhardt but for Arduino-ESP32 where these errors were fixed by modifying the libraries. Arduino-ESP8266 has no such branch, but for the future an issue there would probably be good, so that it may at some point use a better compiler…

:man_facepalming:

Disregard everything after “Good morning!”, please!

I just found out that it is exactly the other way around than I thought: the ESP32 project is compiled without the compiler using the move pattern at all (according to C++11, this is optional and a compiler’s decision). The ESP8266 compiler does know about the move pattern and is trying to use it, but I simply forgot to add the code for these!

Argh. Sorry for having bothered you.

Okay so implementing the move constructor ModbusMessage::ModbusMessage(ModbusMessage&&) and the equal operator ModbusMessage::operator=(ModbusMessage&&) fixes it?

Yes, I would think so. Just leaving out the declarations fixes it already, as the compiler then will use the copy pattern instead. I had the declarations only in the header file, but no implementation code - silly me!