diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 449acc64cf..aad71ff4d0 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -669,7 +669,14 @@ void Application::yield_with_select_(uint32_t delay_ms) { #endif } -Application App; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// App storage — asm label shares the linker symbol with "extern Application App". +// char[] is trivially destructible, so no __cxa_atexit or destructor chain is emitted. +// Constructed via placement new in the generated setup(). +#ifndef __GXX_ABI_VERSION +#error "Application placement new requires Itanium C++ ABI (GCC/Clang)" +#endif +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +alignas(Application) char app_storage[sizeof(Application)] asm("_ZN7esphome3AppE"); #if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) void Application::setup_wake_loop_threadsafe_() { diff --git a/esphome/core/config.py b/esphome/core/config.py index 21ed8ced1a..a2b8746a62 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -512,6 +512,9 @@ async def to_code(config: ConfigType) -> None: cg.add_global(cg.RawExpression("using std::min")) cg.add_global(cg.RawExpression("using std::max")) + # Construct App via placement new — see application.cpp for storage details + cg.add_global(cg.RawExpression("#include ")) + cg.add(cg.RawExpression("new (&App) Application()")) cg.add( cg.App.pre_setup( config[CONF_NAME],