Undefined reference to static member variable from within a static member function

I’m having an issue building a project that employs a static member variable within a static member function. Can you please review this and help me find out what is going on.

Configuration

Operating system: Mac OS Monterey version 12.6

PlatformIO Version: PlatformIO Core, version 6.1.5a4

Description of problem

When i build for upload, I get the following report:

/usr/local/bin/pio -c clion run --target upload -e nodemcuv2
Processing nodemcuv2 (platform: espressif8266; board: nodemcuv2; framework: arduino)

Verbose mode can be enabled via -v, --verbose option
CONFIGURATION: Redirecting...
PLATFORM: Espressif 8266 (4.0.1) > NodeMCU 1.0 (ESP-12E Module)
HARDWARE: ESP8266 80MHz, 80KB RAM, 4MB Flash
PACKAGES:

  • framework-arduinoespressif8266 @ 3.30002.0 (3.0.2)
  • tool-esptool @ 1.413.0 (4.13)
  • tool-esptoolpy @ 1.30000.201119 (3.0.0)
  • tool-mklittlefs @ 1.203.210628 (2.3)
  • tool-mkspiffs @ 1.200.0 (2.0)
  • toolchain-xtensa @ 2.100300.210717 (10.3.0)
    LDF: Library Dependency Finder → Library Dependency Finder (LDF) — PlatformIO latest documentation
    LDF Modes: Finder ~ chain, Compatibility ~ soft
    Found 35 compatible libraries
    Scanning dependencies…
    No dependencies
    Building in release mode
    Compiling .pio/build/nodemcuv2/src/StaticHelper.cpp.o
    Compiling .pio/build/nodemcuv2/src/Test.cpp.o
    Compiling .pio/build/nodemcuv2/src/main.cpp.o
    Generating LD script .pio/build/nodemcuv2/ld/local.eagle.app.v6.common.ld
    Archiving .pio/build/nodemcuv2/libFrameworkArduinoVariant.a
    Indexing .pio/build/nodemcuv2/libFrameworkArduinoVariant.a
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Crypto.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Esp-frag.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Esp-version.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Esp.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/FS.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/FSnoop.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/FunctionalInterrupt.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/HardwareSerial.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/IPAddress.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/LwipDhcpServer-NonOS.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/LwipDhcpServer.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/LwipIntf.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/LwipIntfCB.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/MD5Builder.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Print.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Schedule.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/StackThunk.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Stream.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/StreamSend.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Tone.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/TypeConversion.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/Updater.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/WMath.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/WString.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/abi.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/aes_unwrap.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/base64.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/cbuf.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/cont.S.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/cont_util.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_app_entry_noextra4k.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_eboot_command.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_features.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_flash_quirks.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_flash_utils.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_i2s.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_main.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_non32xfer.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_noniso.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_phy.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_postmortem.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_si2c.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_sigma_delta.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_spi_utils.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_timer.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_vm.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_waveform_phase.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_waveform_pwm.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_wiring.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_wiring_analog.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_wiring_digital.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_wiring_pulse.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_wiring_pwm.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/core_esp8266_wiring_shift.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/crc32.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/debug.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/exc-c-wrapper-handler.S.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/exc-sethandler.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/flash_hal.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/gdb_hooks.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/heap.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/hwdt_app_entry.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/libb64/cdecode.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/libb64/cencode.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/libc_replacements.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/mmu_iram.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/reboot_uart_dwnld.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/spiffs/spiffs_cache.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/spiffs/spiffs_check.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/spiffs/spiffs_gc.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/spiffs/spiffs_hydrogen.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/spiffs/spiffs_nucleus.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/spiffs_api.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/sqrt32.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/stdlib_noniso.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/time.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/uart.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/umm_malloc/umm_info.c.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/umm_malloc/umm_integrity.c.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/umm_malloc/umm_local.c.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/umm_malloc/umm_malloc.cpp.o
    Compiling .pio/build/nodemcuv2/FrameworkArduino/umm_malloc/umm_poison.c.o
    Archiving .pio/build/nodemcuv2/libFrameworkArduino.a
    Indexing .pio/build/nodemcuv2/libFrameworkArduino.a
    Linking .pio/build/nodemcuv2/firmware.elf
    /Users/jbasiago/.platformio/packages/toolchain-xtensa/bin/…/lib/gcc/xtensa-lx106-elf/10.3.0/…/…/…/…/xtensa-lx106-elf/bin/ld: .pio/build/nodemcuv2/src/StaticHelper.cpp.o:(.text._ZN12StaticHelper4helpEP4Test+0x0): undefined reference to `_ZN12StaticHelper4testE’
    collect2: error: ld returned 1 exit status
    *** [.pio/build/nodemcuv2/firmware.elf] Error 1
    [FAILED] Took 18.96 seconds

The content of platformio.ini:
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino

Source file to reproduce issue:
main.cpp
#include <Arduino.h>
#include “StaticHelper.h”

void setup() {
Test *test {};
StaticHelper::help(test);
}

void loop() {
}

StaticHelper.cpp
#include “StaticHelper.h”

void StaticHelper::help(Test *newTest) {
test = newTest;
}

StaticHelper.h
#pragma once

#include “Test.h”

class StaticHelper {
public:
static void help(Test *test);

private:
static Test *test;
};

Test.cpp

#include “Test.h”

Test.h

#pragma once

class Test {
};

Your code is invalid. If you declare a static member inside a class header file, the .cpp file belonging to that class should create at the outer-most scope. This means your StaticHelper.cpp should be

#include "StaticHelper.h"

static StaticHelper::Test *test;

void StaticHelper::help(Test *newTest) {
   test = newTest;
}

Thanks so much for the help. What I have found is that I can’t really declare the static member as part of the StaticHelper class at all. If I do what you’ve suggested, I get an error on the StaticHelper:: part of the declaration in the .cpp file. If I remove that part and just make Test a static variable, it does work, however, now the variable is public and anyone can change it. It’s not clear to me that you can create a static member variable that is truly private to a class. I am able to move on at this point. Thanks again.