Where Is All That RAM Going??

I’m having no luck attempting to reconcile my class definitions, with the amount of RAM that disappears when the classes are instantiated. Several of them are consuming VASTLY more RAM than they should. This class compiles for both AVR and ESP targets. Only the AVR (Nano, at present) target is of concern here.

In all cases, ALL “strings” are stored in PROGMEM. There are NO external classes or data instantiated within the class, though one (GaugeConfig) is referenced through a pointer to an instance created within setup(), and passed into the Setup() function of this class. This class does USE a small subset of the FastLED library functionality (addLeds(), fill_solid(), show()) but, AFAICT, FastLED does not appear to use any RAM, other than the LED array, which is contained within the below LEDLighting class instance data.

Based on the class data, this class should consume well under 100 bytes, but according to the compilers memory report, it consumes over 450 bytes as soon as it is instantiated! Where in the world is all that RAM going?? Is there some huge run-time RAM usage (i.e. - stack or heap?) within FastLED???

This is but one example (LEDLighting.h/.cpp):

LEDLighting.h

#pragma once

#include "../types.h"
#include "types.h"

#ifdef ARDUINO_ARCH_ESP8266

// ESP Only
#include "../Config/MasterConfig.h"

#else

// AVR Only
#include "../MemoryFree/MemoryFree.h"

#endif

#include "../Config/GaugeConfig.h"
#include <FastLED.h>


class LEDLighting
{
private:
    #define             MAX_LEDS      10

    GaugeConfig         *Cfg            = NULL;
    CRGB                Neo[MAX_LEDS];
    gaugeModes          curMode         = gaugeModes::MODE_OFF;
    uint8_t             curColor        = 0;
    uint8_t             curBrightness   = 0;
    gaugeModes          nextMode        = gaugeModes::MODE_OFF;
    uint8_t             nextColor       = 0;
    uint8_t             nextBrightness  = 0;
    uint32_t            animationStart  = 0;
    uint8_t             animationStep   = 0;
    uint8_t             gaugeValue      = 0;
    uint32_t            lightingUpdateTime = 0;

public:

    LEDLighting() {};
    ~LEDLighting() {};
    void SetColor(uint8_t color, uint8_t bright);
    void SetMode(gaugeModes mode, uint8_t color, uint8_t bright);
    void SetAutoColor(boolean en);
    void SetValue(uint8_t val) { gaugeValue = val; };
    void Update(float val);
    void Tick(int tick);
    void Setup(GaugeConfig *cfg);
};

LEDLighting.cpp

#include "LEDLighting.h"


void LEDLighting::SetColor(uint8_t color, uint8_t bright)
{
    //Serial.printf(F("SetColor: color=%d bright=%d\n"), color, bright);
    if ((color >= 0) && (color <= 253) && (bright > 0))
    {
        fill_solid(Neo, Cfg->numLEDs(), CHSV(color,255,bright));
    }
    else if ((color == 253) || (bright == 0))
    {
        // Black
        fill_solid(Neo, Cfg->numLEDs(), CRGB(0, 0, 0));
    }
    else
    {
        // White
        fill_solid(Neo, Cfg->numLEDs(), CRGB(bright, bright, bright));
    }
    curColor = color;
    curBrightness = bright;
    FastLED.show();
}


void LEDLighting::SetMode(gaugeModes mode, uint8_t color, uint8_t bright)
{
    //Serial.printf(F("SetMode0: mode=%d color=%d bright=%d\n"), (int)mode, (int)color, (int)bright);
    nextMode = mode;
    nextColor = color;
    nextBrightness = bright;
    //Serial.printf(F("SetMode1: nextMode=%d nextColor=%d nextBrightness=%d\n"), (int)nextMode, (int)nextColor, (int)nextBrightness);
}


void LEDLighting::Update(float val)
{
    //Serial.printf(F("Update0: curMode=%d curColor=%d Brightness=%d\n"), (int)curMode, (int)curColor, (int)curBrightness);
    boolean busy = false;

    // if (Cfg->getAutoColor())
    // {
    //     // FIXME - Check for AutoColor Here!  
    //     curMode = gaugeModes::MODE_AUTOCOLOR;
    // }

    if ((nextMode != curMode) && (nextMode == gaugeModes::MODE_OFF))
    {
        // OFF mode takes effect immediately
        curMode = nextMode;
        curColor = nextColor;
        curBrightness = nextBrightness;
    }

    switch(curMode)
    {
        case gaugeModes::MODE_OFF:
        case gaugeModes::MODE_NORMAL:
            // Off mode, so just set lighting
            //Serial.printf(F("Update1: curMode=%d curColor=%d curBrightness=%d\n"), (int)curMode, (int)curColor, (int)curBrightness);
            SetColor(curColor, curBrightness);
            break;

//        case gaugeModes::MODE_INIT_ANIMATE:
//            // Do startup animation here
//            busy = true;
//            break;

        case gaugeModes::MODE_AUTOCOLOR:
            // Handle AutoColor Mode
            break;

        case gaugeModes::MODE_ALERTING:
            // Alert animation is handled in Tick()
            busy = true;
            break;
    }
    if (!busy)
    {
        switch(nextMode)
        {
            case gaugeModes::MODE_OFF:
            case gaugeModes::MODE_NORMAL:
                //Serial.printf(F("Update2: curMode=%d curColor=%d curBrightness=%d\n"), (int)curMode, (int)curColor, (int)curBrightness);
                // Normal or Off mode, so just set lighting
                curMode = nextMode;
                curColor = nextColor;
                curBrightness = nextBrightness;
                SetColor(curColor, curBrightness);
                //Serial.printf(F("Update3: curMode=%d curColor=%d curBrightness=%d\n"), (int)curMode, (int)curColor, (int)curBrightness);
                break;

            // case gaugeModes::MODE_INIT_ANIMATE:
            //     // Begin startup animation here
            //     SetColor(Colors::Black, 0);
            //     animationStart = millis();
            //     break;

            case gaugeModes::MODE_AUTOCOLOR:
                // Handle AutoColor Mode
                break;
                
            case gaugeModes::MODE_ALERTING:
                // Begin alert animation here
                //Serial.printf(F("Update4: curMode=%d curColor=%d curBrightness=%d\n"), (int)curMode, (int)curColor, (int)curBrightness);
                curMode = nextMode;
                curColor = nextColor;
                curBrightness = nextBrightness;
                //SetColor(curColor, curBrightness);
                animationStep = 0;
                //Serial.printf(F("Update5: curMode=%d curColor=%d curBrightness=%d\n"), (int)curMode, (int)curColor, (int)curBrightness);
                break;
        }
    }
}


void LEDLighting::Tick(int tick)
{
    // Handle clock ticks here
    switch(curMode)
    {
        case gaugeModes::MODE_OFF:
        case gaugeModes::MODE_NORMAL:
        case gaugeModes::MODE_INIT_ANIMATE:
            // Normal, Off or Animating mode, so nothing to do
            break;

        case gaugeModes::MODE_ALERTING:
            // Update alert animation here
            switch (animationStep)
            {
                case 0:
                    if (tick)
                    {
                        if (nextMode == gaugeModes::MODE_ALERTING)
                        {
                            SetColor(0, 255);
                            animationStep = 1;
                        }
                        else
                        {
                            curMode = nextMode;
                            curColor = nextColor;
                            curBrightness = nextBrightness;
                            SetColor(curColor, curBrightness);
                        }
                    } 
                    break;

                case 1:
                    if (!tick)
                    {
                        SetColor(0, 0);
                        animationStep = 0;
                    }
                    break;

                default:
                    break;
            }
            break;
    }
}


void LEDLighting::Setup(GaugeConfig *cfg)
{
    Cfg = cfg;

    switch(Cfg->ledPin())
    {
        case 2:     // Pin 2, used by all AVR "smart" gauges
            //Serial.printf(F("Using pin %d %d LEDs\n"), Cfg->ledPin(), Cfg->numLEDs());
            FastLED.addLeds<NEOPIXEL, 2>(Neo, Cfg->numLEDs());
            break;

        case 3:
            //Serial.printf(F("Using pin %d %d LEDs\n"), Cfg->ledPin(), Cfg->numLEDs());
            FastLED.addLeds<NEOPIXEL, 3>(Neo, Cfg->numLEDs());
            break;            
    }

    fill_solid(Neo, Cfg->numLEDs(), CRGB(100,100,100));
    FastLED.show();   
    //Serial.printf(F("Setup0: curMode=%d curColor=%d curBrightness=%d\n"), (int)curMode, (int)curColor, (int)curBrightness);
    //Update(0.0);
    //Serial.printf(F("Setup1: curMode=%d curColor=%d curBrightness=%d\n"), (int)curMode, (int)curColor, (int)curBrightness);
}

Regards,
Ray L.

USING FastLED does appear to consume ~135 bytes. That still leaves my class with ~250+ bytes of RAM usage unaccounted for... That is a LOT of RAM!

Regards,
Ray L.

If you’re on Linux, run the following command to get an overview of the static RAM usage:

$HOME/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino5/bin/avr-nm -Crtd --size-sort /tmp/arduino_build_xxxxxx/<sketch-name>.ino.elf | grep -i ' [dbv] '

Each addLeds call instantiates a static LED controller instance, and it probably includes some guard variables as well.

An array of 10 CRGB objects takes at least 30 bytes.

Pieter

PieterP:
If you’re on Linux, run the following command to get an overview of the static RAM usage:

$HOME/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino5/bin/avr-nm -Crtd --size-sort /tmp/arduino_build_xxxxxx/<sketch-name>.ino.elf | grep -i ' [dbv] '

Each addLeds call instantiates a static LED controller instance, and it probably includes some guard variables as well.

An array of 10 CRGB objects takes at least 30 bytes.

Pieter

Yup! I’ve confirmed the 3 bytes per LED, and since there are only 10 LEDs, that does not concern me. As I indicated, simply calling the three FastLED functions (addLEDs, fill_solid, and show) disappears another ~100 bytes of RAM.

I’m trying to pin down actual RAM usage for some other classes in my program, and getting some really bizarre results! For example, the following line:

    GaugeConfig cfg = GaugeConfig(GaugeIDs::ID_TEMP);

If placed in global scope, above setup(), it consumes a whopping 560 bytes of RAM. That exact same line, when placed INSIDE setup(), consumes “only” 466 bytes of RAM. Keep in mind, however, that the class data of GaugeConfig is WELL under 100 bytes. So where is the other 350+ bytes going?? Why does moving that one line from global scope to setup() scope, “free up” nearly 100 bytes of RAM??

This just makes no sense at all to me… It seems obvious the RAM usage estimate from the compiler takes into account function nesting and worst-case static and dynamic stack and heap usage. But even so, these numbers seem completely detached from reality…

Regards,
Ray L.

RayLivingston:
I'm trying to pin down actual RAM usage for some other classes in my program, and getting some really bizarre results! For example, the following line:

    GaugeConfig cfg = GaugeConfig(GaugeIDs::ID_TEMP);

If placed in global scope, above setup(), it consumes a whopping 560 bytes of RAM. That exact same line, when placed INSIDE setup(), consumes "only" 466 bytes of RAM. Keep in mind, however, that the class data of GaugeConfig is WELL under 100 bytes. So where is the other 350+ bytes going??

The other 350+ bytes are probably static variables in functions called by the GaugeConfig class, or other static objects used by the code.

RayLivingston:
Why does moving that one line from global scope to setup() scope, "free up" nearly 100 bytes of RAM??

Because it's invisible to the Arduino memory usage tool if it's inside of a function.

RayLivingston:
This just makes no sense at all to me... It seems obvious the RAM usage estimate from the compiler takes into account function nesting and worst-case static and dynamic stack and heap usage.

As far as I know, it only counts static memory (i.e. strings, variables declared at the global scope, static class variables, static local variables in functions, guard variables, virtual function tables, serial buffers, etc.). It doesn't count any dynamic stack or heap memory.

Could you post a zip of the entire sketch/project?
The only way to know where the RAM is going is to look at the output of the avr-nm command I posted earlier.

And, it gets stranger still! I just went back to an order version of this same code. The TOTAL RAM usage of that older code is under 900 bytes, despite the fact that is has a stepper motor driver class, and a couple of others, included, that have not yet been added to the new code.

The LEDLighting class in that older code consumes only ~70-80 bytes of RAM, as I would expect. Doing a diff between the old and new code, there are lots of differences, none of them at all major. Certainly nothing that comes close to explaining 5X more RAM usage! I compiled both using the same v1.8.9 compiler.

Regards,
Ray L.

There’s no reason to keep on guessing.
Just look at the output of avr-nm.

I was not familiar with avr-nm, but have tried to use it. I am not getting any RAM addresses, at least not within my classes - just FLASH. Here is what I’ve been able to get out of it so far, using the --demangle and --numeric-sort options. Are there other options I should be using?

         w serialEvent()
00000000 W __heap_end
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 W __vector_default
00000000 T __vectors
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000003 A __FUSE_REGION_LENGTH__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
00000068 T __trampolines_end
00000068 T __trampolines_start
00000068 t FS::exists(char const*)::__c
00000080 t FS::exists(char const*)::__c
00000094 t FS::exists(char const*)::__c
000000a2 t GaugeConfig::C_KEY
000000a4 t GaugeConfig::ALERT_KEY
000000aa t GaugeConfig::AUTO_KEY
000000af t GaugeConfig::ZERO_KEY
000000b4 t GaugeConfig::NLEDS_KEY
000000ba t GaugeConfig::PIN_KEY
000000be t GaugeConfig::TYPE_KEY
000000c3 t GaugeConfig::VER_KEY
000000c8 T __ctors_start
000000ca T __ctors_end
000000ca T __dtors_end
000000ca T __dtors_start
000000ca W __init
000000d6 T __do_copy_data
000000ec T __do_clear_bss
000000f4 t .do_clear_bss_loop
000000f6 t .do_clear_bss_start
000000fc T __do_global_ctors
0000011a T __bad_interrupt
0000011a W __vector_1
0000011a W __vector_10
0000011a W __vector_11
0000011a W __vector_12
0000011a W __vector_13
0000011a W __vector_14
0000011a W __vector_15
0000011a W __vector_17
0000011a W __vector_2
0000011a W __vector_20
0000011a W __vector_21
0000011a W __vector_22
0000011a W __vector_23
0000011a W __vector_25
0000011a W __vector_3
0000011a W __vector_4
0000011a W __vector_5
0000011a W __vector_6
0000011a W __vector_7
0000011a W __vector_8
0000011a W __vector_9
0000011e t micros
00000168 t Print::write(unsigned char const*, unsigned int)
000001bc t HardwareSerial::availableForWrite()
000001da t HardwareSerial::read()
00000202 t HardwareSerial::peek()
0000021e t HardwareSerial::available()
00000236 t Serial0_available()
0000024a t HardwareSerial::_tx_udr_empty_irq()
0000028e t HardwareSerial::write(unsigned char)
00000328 t HardwareSerial::flush()
0000036c t serialEventRun()
00000380 t twi_stop
00000394 t twi_transmit
000003e4 t TwoWire::flush()
000003e6 t TwoWire::peek()
000003ec t TwoWire::read()
000003f2 t TwoWire::available()
00000400 A __LOCK_REGION_LENGTH__
00000400 A __SIGNATURE_REGION_LENGTH__
00000400 A __USER_SIGNATURE_REGION_LENGTH__
00000400 t TwoWire::write(unsigned char const*, unsigned int)
00000416 t TwoWire::write(unsigned char)
00000438 t File::available()
0000045e t File::write(unsigned char const*, unsigned int)
000004b4 t convertPath(char const*, char*)
000004f8 t Print::flush()
000004fa t Print::availableForWrite()
00000500 t File::read()
0000053e t FS_Header& EEPROMClass::get<FS_Header>(int, FS_Header&) [clone .isra.4] [clone .constprop.28]
00000576 t FS_File& EEPROMClass::get<FS_File>(int, FS_File&) [clone .isra.7]
000005c2 t File::peek()
000005f6 t Print::printf(__FlashStringHelper const*, ...) [clone .constprop.21]
0000066e t __cxa_pure_virtual
00000672 t Print::write(char const*)
0000069c t Print::println(char const*) [clone .constprop.9]
000006c2 t File::write(unsigned char)
00000734 t FS_File const& EEPROMClass::put<FS_File>(int, FS_File const&) [clone .isra.6]
0000078a t FS::exists(char const*)
000008aa t FS::open(char const*, char const*) [clone .constprop.12]
000008ff W __stack
00000a20 t File::close() [clone .constprop.11]
00000ae2 t Print::printf(char const*, ...)
00000b7a t GaugeConfig::write(char const*)
00000d3e t GaugeConfig::read(char const*)
00000ffe t GaugeConfig::init(void const*)
000012a0 t GaugeConfig::dump()
000013f0 T __vector_16
00001484 T __vector_19
000014d0 T __vector_18
00001534 T __vector_24
00001788 t global constructors keyed to 65535_0_TestSketch.ino.cpp.o.4962
0000184e T main
0000196c T atoi
000019a8 T memcpy_P
000019ba T strcmp_P
000019cc T strtok
000019d6 T strtok_r
00001a24 T __mulhi_const_10
00001a34 T vsnprintf_P
00001a8c T vfprintf
00001e4e T strnlen_P
00001e64 T strnlen
00001e7a T fputc
00001ef2 T __ultoa_invert
00001fae T eeprom_read_byte
00001fbe T eeprom_write_byte
00001fc0 T eeprom_write_r18
00001fda T __prologue_saves__
00002012 T __epilogue_restores__
00002048 T __tablejump2__
00002054 T abort
0000205e T toupper
0000206e T memcmp
00002088 T memset
00002096 T strcmp
000020a8 T vsnprintf
00002100 T _exit
00002100 W exit
00002102 t __stop_program
00002104 A __data_load_start
00002104 T _etext
00002300 A __data_load_end
0000ffa0 A __DATA_REGION_LENGTH__
00010000 A __EEPROM_REGION_LENGTH__
00020000 A __TEXT_REGION_LENGTH__
00800100 D __data_start
00800100 d CSWTCH.20
00800106 d vtable for File
00800118 d vtable for HardwareSerial
0080012a d vtable for TwoWire
0080013c d vtable for GaugeConfig
008002fc B __bss_start
008002fc D __data_end
008002fc D _edata
008002fc b GaugeConfig::parse(char*)::i
008002fe b TwoWire::rxBufferIndex
008002ff b twi_txBuffer
0080031f b twi_txBufferLength
00800320 b twi_txBufferIndex
00800321 b twi_rxBuffer
00800341 b twi_rxBufferIndex
00800342 b twi_error
00800343 b twi_state
00800344 b twi_inRepStart
00800345 b twi_sendStop
00800346 b twi_masterBuffer
00800366 b twi_masterBufferLength
00800367 b twi_masterBufferIndex
00800368 b twi_slarw
00800369 b timer0_fract
0080036a b timer0_millis
0080036e b timer0_overflow_count
00800372 b Serial
0080040f b Wire
0080041b b FastLED
00800428 b cfg
0080047a b SPIFFS
0080047d b p
0080047f B __bss_end
0080047f n __heap_start
0080047f N _end
00810000 N __eeprom_end

Regards,
Ray L.

I've had no luck whatsoever prying any useful information about RAM allocation out of the .elf, .o, or .map files, using avr-nm, objdump, and several other utilities. FLASH allocation is documented out the wazoo, but no breakdown on RAM.

What am I missing? There HAS to be a way to get this information. Even if it's only the total size of RAM used by each class? I can't even find that!

Regards,
Ray L.

Did you use the options and the grep from my first reply?

If you use some newer version, try AVR boards package 1.6.21. it has older avr-gcc version

Juraj:
If you use some newer version, try AVR boards package 1.6.21. it has older avr-gcc version

Why?

PieterP:
Why?

there are problems reported with the avr-gcc versions used in AVR core after 1.6.21. first it was the “segmentation fault error” in compiler. now there are some strange optimizations removing valid code or maybe leaving some unused code. I didn’t store the links. One report was here in forum, other on Stack Overflow (I am not sure which avt-gcc version it was and it was an online simulator)

What am I missing? There HAS to be a way to get this information. Even if it's only the total size of RAM used by each class? I can't even find that!

Trial and error method. IOW comment out all functions in the class, and in your sketch, and add them one by one again.

Deva_Rishi:
Trial and error method. IOW comment out all functions in the class, and in your sketch, and add them one by one again.

-1

There's absolutely no point in doing that. The command I posted in my very first reply shows you exactly where the RAM is used.

If you're on Linux

@Deva_Rishi What is wrong with the Windows version of avr-nm?

{Arduino Install Dir}\hardware\tools\avr\bin\avr-nm.exe -Crtd --size-sort {path to elf file}.ino.elf > buildinfo.txt

Open the txt file and there you go.

OK, here is the output from avr-nm, manually “grep’ed”, and re-arranged, grouped by class, and within the classes by memory segment, with my classes at the top. I see NO RAM usage, other than the vtables. Are class variables just included in the vtables?

Doesn’t seem to me this sheds any light at all…

#############################
# FS/FS_File/FS_Header/File
#############################

00000374 t FS::open(char const*, char const*) [clone .constprop.14]
00000288 t FS::exists(char const*)
00000194 t File::close() [clone .constprop.23]
00000114 t File::write(unsigned char)
00000086 t File::write(unsigned char const*, unsigned int)
00000086 t FS_File const& EEPROMClass::put<FS_File>(int, FS_File const&) [clone .isra.6]
00000076 t FS_File& EEPROMClass::get<FS_File>(int, FS_File&) [clone .isra.7]
00000068 t convertPath(char const*, char*)
00000062 t File::read()
00000056 t FS_Header& EEPROMClass::get<FS_Header>(int, FS_Header&) [clone .isra.4] [clone .constprop.28]
00000052 t File::peek()
00000038 t File::available()
00000024 t FS::exists(char const*)::__c
00000020 t FS::exists(char const*)::__c
00000014 t FS::exists(char const*)::__c

00000018 d vtable for File


#############################
# GaugeConfig
#############################
00000704 t GaugeConfig::read(char const*)
00000674 t GaugeConfig::init(void const*)
00000452 t GaugeConfig::write(char const*)
00000336 t GaugeConfig::dump()
00000006 t GaugeConfig::NLEDS_KEY
00000006 t GaugeConfig::ALERT_KEY
00000005 t GaugeConfig::ZERO_KEY
00000005 t GaugeConfig::TYPE_KEY
00000005 t GaugeConfig::AUTO_KEY
00000004 t GaugeConfig::VER_KEY
00000004 t GaugeConfig::PIN_KEY
00000004 t __cxa_pure_virtual
00000002 t TwoWire::flush()
00000002 t Print::flush()
00000002 t GaugeConfig::C_KEY

00000012 d vtable for GaugeConfig


#############################
# misc.
#############################
00000198 t global constructors keyed to 65535_0_TestSketch.ino.cpp.o.4962
00000154 t HardwareSerial::write(unsigned char)
00000152 t Print::printf(char const*, ...)
00000120 t Print::printf(__FlashStringHelper const*, ...) [clone .constprop.25]
00000084 t Print::write(unsigned char const*, unsigned int)
00000080 t twi_transmit
00000074 t micros
00000068 t HardwareSerial::flush()
00000068 t HardwareSerial::_tx_udr_empty_irq()
00000042 t Print::write(char const*)
00000040 t HardwareSerial::read()
00000038 t Print::println(char const*) [clone .constprop.8]
00000034 t TwoWire::write(unsigned char)
00000030 t HardwareSerial::availableForWrite()
00000028 t HardwareSerial::peek()
00000024 t HardwareSerial::available()
00000022 t TwoWire::write(unsigned char const*, unsigned int)
00000020 t twi_stop
00000020 t Serial0_available()
00000020 t serialEventRun()
00000014 t TwoWire::available()
00000006 t TwoWire::read()
00000006 t TwoWire::peek()
00000006 t Print::availableForWrite()

00000018 d vtable for TwoWire
00000018 d vtable for HardwareSerial
00000006 d CSWTCH.20

Regards,
Ray L.

Variables are not included in the vtables.

If you post your code or binary, I might be able to run it through avr-nm on Linux (although the Windows version should work just as well).
What options did you use?

I feel like I’m going deeper and deeper down a rabbit hole… I’ve modified freeMemory, so instead of just returning the amount of free memory, it prints out all the available information - bottom of stack address, __sbrk, __flp, and the entire free memory list. I then littered my code with calls to this new function. What comes out makes NO sense.

Here is the code:

void setup(void)
{
    Serial.begin(38400);
    Serial.printf(F("Starting...\n\n"));

    while (Serial.read() != '\r')
        ;

    showMemory("Start");

    GaugeConfig cfg = GaugeConfig(GaugeIDs::ID_TEMP);

    showMemory("After cfg()");

    cfg.read("temp.cfg");

    showMemory("After cfg.read()");

    CRGB leds[25];

    showMemory("After leds()");

    int a[10];
    for (int i=0; i<10; i++)
        a[i] = i * i;
    for (int i=0; i<10; i++)
        Serial.printf("i=%d a[%d]=%d\n", i, i, a[i]);

    showMemory("After a");

   while(1)
      ;
}

Here is the function I added to freeMemory:

void showMemory(char *s)
{
	char __stack = 0;
	
	Serial.println(s);
	Serial.print("__brkval=");
	Serial.println((unsigned int)__brkval);
	Serial.print("__flp=");
	Serial.println((unsigned int)__flp);
	Serial.print("__stack=");
	Serial.println((unsigned int)&__stack);
	
	struct __freelist* current;
	int total = 0;
	for (current = __flp; current; current = current->nx)
	{
		total += 2; /* Add two bytes for the memory block's header  */
		total += (int) current->sz;
		Serial.print("mblk: sz=");
		Serial.print((unsigned int)current->sz);
		Serial.print(" nx=");
		Serial.println((unsigned int)current->nx);
		Serial.print("Total: ");
		Serial.println(total);
	}
	Serial.println("\n");
}

Here is what prints out when the code is run:

Starting...


Start
__brkval=0
__flp=0
__stack=2185


After cfg()
__brkval=0
__flp=0
__stack=2185


After cfg.read()
__brkval=0
__flp=0
__stack=2185


After leds()
__brkval=0
__flp=0
__stack=2185


i=0 a[0]=0
i=1 a[1]=1
i=2 a[2]=4
i=3 a[3]=9
i=4 a[4]=16
i=5 a[5]=25
i=6 a[6]=36
i=7 a[7]=49
i=8 a[8]=64
i=9 a[9]=81
After a
__brkval=0
__flp=0
__stack=2185

So, if we are to believe this nonsense, creating a new GaugeConfig object, and a new array of 25 CRGB objects, consumes NO RAM AT ALL! This is BS!!

Regards,
Ray L.