简体   繁体   中英

C++ memory leak that I can't find

I have a small example program here for the particle photon that has a memory bug that I cannot figure out.

What it does: loads up a buffer with small string chunks, converts that large buffer back into a string. Then it creates a bunch of objects that are only wrappers for small chunks of buffer. It does this repetitively, and I don't allocate any new memory after the setup(), yet the memory goes down slowly until it crashes.

main.cpp

includes, variable declarations

#include "application.h" //needed when compiling spark locally

#include <string>
#include <unordered_map>
#include "dummyclass.h"

using namespace std;
SYSTEM_MODE(MANUAL);

char* buffer;
unordered_map<int, DummyClass*> store;
string alphabet;
unsigned char alphabet_range;
unsigned char state;
int num_chars;

static const unsigned char STATE_INIT = 0;
static const unsigned char STATE_LOAD_BUFFER = 1;
static const unsigned char STATE_PREP_FOR_DESERIALIZE = 2;
static const unsigned char STATE_FAKE_DESERIALIZE = 3;
static const unsigned char STATE_FINISH_RESTART = 4;

delete objects helper function

bool delete_objects()
{
    Serial.println("deleting objects in 'store'");
    for(auto iter = store.begin(); iter != store.end(); iter++)
    {
        delete iter->second;
        iter->second = nullptr;
    } 
    store.clear();

    if(store.empty())
        return true;
    else
        return false;
}

set up function, allocates memory, initial assignments

void setup()
{
    Serial.begin(9600);
    Serial1.begin(38400);
    delay(2000);

    buffer = new char[9000];
    alphabet = string("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$^&*()_-?/><[]{}|");
    alphabet_range = alphabet.length() - 1;
    state = STATE_INIT;
    num_chars = 0;
}

loop function, gets run over and over

void loop()
{
    switch(state){
        case STATE_INIT: {

            strcpy(buffer, "");
            state = STATE_LOAD_BUFFER;
            delay(1000);
            break;

        }
        case STATE_LOAD_BUFFER: {

            if(num_chars < 6000){
                string chunk;
                for(char i = 0; i < 200; i++){
                    int index = rand() % alphabet_range;
                    chunk.append(alphabet.substr(index, 1));
                    num_chars++;
                }
                strcat(buffer, chunk.c_str());
            }
            else{
                num_chars = 0;
                state = STATE_PREP_FOR_DESERIALIZE;
            }
            delay(500);
            break;

        }
        case STATE_PREP_FOR_DESERIALIZE: {

            Serial.println("\nAttempting to delete current object set...");
            delay(500);
            if(delete_objects())
                Serial.println("_delete_objects succeeded");
            else {
                Serial.println("_delete_objects failed");
                break;
            }
            state = STATE_FAKE_DESERIALIZE;
            delay(1000);
            break;

        }
        case STATE_FAKE_DESERIALIZE: {

            string buff_string(buffer);
            if(buff_string.length() == 0){
                Serial.println("Main:: EMPTY STRING CONVERTED FROM BUFFER");
            }

            int index = 0;
            int key = 1;
            while(index < buff_string.length())
            {
                int amount = (rand() % 50) + 5;
                DummyClass* dcp = new DummyClass(buff_string.substr(index, amount));
                store[key] = dcp;
                index += amount;
                key++;
            }
            state = STATE_FINISH_RESTART;
            delay(1000);
            break;

        }
        case STATE_FINISH_RESTART: {

            state = STATE_INIT;
            break;

        }
    }

}

dummyclass.h

very minimal, constructor just stores a string in a character buffer. this object is just a wrapper.

using namespace std;

class DummyClass {
    private:
        char* _container;

    public:
        DummyClass(){
        }

        DummyClass(string input){
            _container = new char[input.length()];
            strcpy(_container, input.c_str());
        }

        ~DummyClass(){
            delete _container;
            _container = nullptr;
        }

        char* ShowMeWhatYouGot(){
            return _container;
        }
};

EDIT:

This is a real problem that I am having, I'm not sure why it is getting downvoted. Help me out here, how can I be more clear? I'm reluctant to shrink the code since it imitates many aspects of a much bigger program that it is modeling simply. I want to keep the structure of the code in place in case this bug is an emergent property.

Always account for the string terminator:

    DummyClass(string input){
        _container = new char[input.length()];
        strcpy(_container, input.c_str());
    }

Allocates one too few bytes to hold the input string and terminator that is then copied into it. The \\0 that's appended at the end is overwriting something, which is most likely metadata required to re-integrate the alloced memory fragment back into the heap successfully. I'm actually surprised it didn't crash...

It probably doesn't happen every allocation (only when you overflow into a new 8 byte aligned chunk), but once is enough :)

So, after some testing, I'd like to give a shout out to Russ Schultz who commented the right answer. If you want to post a solution formally, I would be happy to mark it as correct.

The memory bug is caused by allocating the char buffer _container without considering the null terminating character, meaning I am loading in a string that is too big. (not entirely sure why this causes a bug and doesn't throw an error?)

On a different site however, I also received this piece of advice:

 string chunk; for(char i = 0; i < 200; i++){ int index = rand() % alphabet_range; chunk.append(alphabet.substr(index, 1)); // strcat(buffer, alphabet.substring(index, index + 1)); num_chars++; }

This loop looks suspect to me. You are depending on the string append method to grow chunk as needed, but you know you are going to run that loop 200 times. Why not use the string reserve method to just allocate that much space? I bet that this chews up a lot of memory with each new char you append calling realloc, potentially fragmenting memory.

This ended up not being the solution, but it might be good to know.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM