首页 > 解决方案 > C ++链表中出现垃圾节点

问题描述

在我重写的新函数中实现调用堆栈跟踪以跟踪分配时,我使用 ::malloc 创建一个未跟踪的调用堆栈对象,然后将其放入链表中。当我的工具完成所有测试用例的更新时,这个列表是合理的。但是,当我去报告列表(打印到控制台)时,现在有一些值不应该存在并导致它崩溃。下面是简化版(我很抱歉,即使简化了它仍然是很多代码),我希望有人可以这样做:

#define convertToKiB(size) size * 1024UL
#define convertToMiB(size) size * (1024UL * 1024UL)
#define convertToGiB(size) size * (1024UL * 1024UL * 1024UL)
#define convertToReadableBytes(size) ((uint32_t)size > convertToKiB(2) && (uint32_t)size < convertToMiB(2)) ? (float)size / (float)convertToKiB(1) : ((uint32_t)size > convertToMiB(2) && (uint32_t)size < convertToGiB(2)) ? (float)size / (float)convertToMiB(1) : ((uint32_t)size > convertToGiB(2)) ? (float)size / (float)convertToMiB(1) : (float)size
#define convertToReadableBytesString(size) ((uint32_t)size > convertToKiB(2) && (uint32_t)size < convertToMiB(2)) ? "KiB" : ((uint32_t)size > convertToMiB(2) && (uint32_t)size < convertToGiB(2)) ? "MiB" : ((uint32_t)size > convertToGiB(2)) ? "GiB" : "B"

全局变量

const uint8_t MAX_FRAMES_PER_CALLSTACK = 128;
const uint16_t MAX_SYMBOL_NAME_LENGTH = 128;
const uint32_t MAX_FILENAME_LENGTH = 1024;
const uint16_t MAX_DEPTH = 128;

typedef BOOL(__stdcall *sym_initialize_t)(IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess);
typedef BOOL(__stdcall *sym_cleanup_t)(IN HANDLE hProcess);
typedef BOOL(__stdcall *sym_from_addr_t)(IN HANDLE hProcess, IN DWORD64 Address, OUT PDWORD64 Displacement, OUT PSYMBOL_INFO Symbol);
typedef BOOL(__stdcall *sym_get_line_t)(IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Symbol);

static HMODULE g_debug_help;
static HANDLE g_process;
static SYMBOL_INFO* g_symbol;

static sym_initialize_t g_sym_initialize;
static sym_cleanup_t g_sym_cleanup;
static sym_from_addr_t g_sym_from_addr;
static sym_get_line_t g_sym_get_line_from_addr_64;

static int g_callstack_count = 0;
static callstack_list* g_callstack_root = nullptr;

调用堆栈对象

struct callstack_line_t
{
    char file_name[128];
    char function_name[256];
    uint32_t line;
    uint32_t offset;
};

class CallStack
{
public:
    CallStack();

    uint32_t m_hash;
    uint8_t m_frame_count;
    void* m_frames[MAX_FRAMES_PER_CALLSTACK];
};

CallStack::CallStack()
    : m_hash(0)
    , m_frame_count(0) {}

bool CallstackSystemInit()
{
    // Load the dll, similar to OpenGL function fecthing.
    // This is where these functions will come from.
    g_debug_help = LoadLibraryA("dbghelp.dll");
    if (g_debug_help == nullptr) {
        return false;
    }

    // Get pointers to the functions we want from the loded library.
    g_sym_initialize = (sym_initialize_t)GetProcAddress(g_debug_help, "SymInitialize");
    g_sym_cleanup = (sym_cleanup_t)GetProcAddress(g_debug_help, "SymCleanup");
    g_sym_from_addr = (sym_from_addr_t)GetProcAddress(g_debug_help, "SymFromAddr");
    g_sym_get_line_from_addr_64 = (sym_get_line_t)GetProcAddress(g_debug_help, "SymGetLineFromAddr64");

    // Initialize the system using the current process [see MSDN for details]
    g_process = ::GetCurrentProcess();
    g_sym_initialize(g_process, NULL, TRUE);

    // Preallocate some memory for loading symbol information. 
    g_symbol = (SYMBOL_INFO *) ::malloc(sizeof(SYMBOL_INFO) + (MAX_FILENAME_LENGTH * sizeof(char)));
    g_symbol->MaxNameLen = MAX_FILENAME_LENGTH;
    g_symbol->SizeOfStruct = sizeof(SYMBOL_INFO);

    return true;
}

void CallstackSystemDeinit()
{
    // cleanup after ourselves
    ::free(g_symbol);
    g_symbol = nullptr;

    g_sym_cleanup(g_process);

    FreeLibrary(g_debug_help);
    g_debug_help = NULL;
}

// Can not be static - called when
// the callstack is freed.
void DestroyCallstack(CallStack *ptr)
{
    ::free(ptr);
}

CallStack* CreateCallstack(uint8_t skip_frames)
{
    // Capture the callstack frames - uses a windows call
    void *stack[MAX_DEPTH];
    DWORD hash;

    // skip_frames:  number of frames to skip [starting at the top - so don't return the frames for "CreateCallstack" (+1), plus "skip_frame_" layers.
    // max_frames to return
    // memory to put this information into.
    // out pointer to back trace hash.
    uint32_t frames = CaptureStackBackTrace(1 + skip_frames, MAX_DEPTH, stack, &hash);

    // create the callstack using an untracked allocation
    CallStack *cs = (CallStack*) ::malloc(sizeof(CallStack));

    // force call the constructor (new in-place)
    cs = new (cs) CallStack();

    // copy the frames to our callstack object
    unsigned int frame_count = min(MAX_FRAMES_PER_CALLSTACK, frames);
    cs->m_frame_count = frame_count;
    ::memcpy(cs->m_frames, stack, sizeof(void*) * frame_count);

    cs->m_hash = hash;

    return cs;
}

//------------------------------------------------------------------------
// Fills lines with human readable data for the given callstack
// Fills from top to bottom (top being most recently called, with each next one being the calling function of the previous)
//
// Additional features you can add;
// [ ] If a file exists in yoru src directory, clip the filename
// [ ] Be able to specify a list of function names which will cause this trace to stop.
uint16_t CallstackGetLines(callstack_line_t *line_buffer, const uint16_t max_lines, CallStack *cs)
{
    IMAGEHLP_LINE64 line_info;
    DWORD line_offset = 0; // Displacement from the beginning of the line 
    line_info.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

    unsigned int count = min(max_lines, cs->m_frame_count);
    unsigned int idx = 0;

    for (unsigned int i = 0; i < count; ++i) {
        callstack_line_t *line = &(line_buffer[idx]);
        DWORD64 ptr = (DWORD64)(cs->m_frames[i]);
        if (FALSE == g_sym_from_addr(g_process, ptr, 0, g_symbol)) {
            continue;
        }

        strcpy_s(line->function_name, 256, g_symbol->Name);

        BOOL bRet = g_sym_get_line_from_addr_64(
            GetCurrentProcess(), // Process handle of the current process 
            ptr,                 // Address 
            &line_offset,        // Displacement will be stored here by the function 
            &line_info);         // File name / line information will be stored here 

        if (bRet)
        {
            line->line = line_info.LineNumber;
            strcpy_s(line->file_name, 128, line_info.FileName);
            line->offset = line_offset;
        }
        else {
            // no information
            line->line = 0;
            line->offset = 0;
            strcpy_s(line->file_name, 128, "N/A");
        }

        ++idx;
    }

    return idx;
}

运营商

// Treat as Linked List Node
struct callstack_list
{
    CallStack* current_stack = nullptr;
    uint16_t total_allocation = 0;
    callstack_list* next = nullptr;
};

struct allocation_meta
{
    uint16_t size;
    callstack_list callstack_node;
};

void* operator new(const size_t size)
{
    uint16_t alloc_size = (uint16_t)size + (uint16_t)sizeof(allocation_meta);
    allocation_meta *ptr = (allocation_meta*)::malloc((size_t)alloc_size);
    ptr->size = (uint16_t)size;

    ptr->callstack_node.current_stack = CreateCallstack(0);
    ptr->callstack_node.total_allocation = (uint16_t)size;
    ptr->callstack_node.next = nullptr;

    bool run = true;
    callstack_list* currentNode = nullptr;
    while (g_callstack_root != nullptr && run)
    {
        if (currentNode == nullptr)
        {
            currentNode = g_callstack_root;
        }

        if (currentNode->next != nullptr)
        {
            currentNode = currentNode->next;
        }
        else
        {
            currentNode->next = &ptr->callstack_node;
            run = false;
        }

    }

    if (g_callstack_root == nullptr)
    {
        g_callstack_root = &ptr->callstack_node;
    }

    return ptr + 1;
}

void operator delete(void* ptr)
{
    if (nullptr == ptr)
        return;

    allocation_meta *data = (allocation_meta*)ptr;
    data--;

    if (data->callstack_node.current_stack != nullptr)
        DestroyCallstack(data->callstack_node.current_stack);

    bool run = true;
    callstack_list* currentNode = nullptr;
    while (g_callstack_root != nullptr && run && &data->callstack_node != NULL)
    {
        if (currentNode == nullptr && g_callstack_root != &data->callstack_node)
        {
            currentNode = g_callstack_root;
        }
        else
        {
            g_callstack_root = nullptr;
            run = false;
            continue;
        }

        if (currentNode->next != nullptr && currentNode->next != &data->callstack_node)
        {
            currentNode = currentNode->next;
        }
        else
        {
            currentNode->next = nullptr;
            run = false;
        }
    }

    ::free(data);
}

测试线束

void ReportVerboseCallStacks(const char* start_time_str = "", const char* end_time_str = "")
{
    callstack_list* currentNode = g_callstack_root;

    unsigned int totalSimiliarAllocs = 0;
    uint32_t totalSimiliarSize = 0;

    while (currentNode != nullptr)
    {
        callstack_list* nextNode = currentNode->next;

        uint32_t& currentHash = currentNode->current_stack->m_hash;
        uint32_t nextHash;
        if (nextNode == nullptr)
            nextHash = currentHash + 1;
        else
            nextHash = nextNode->current_stack->m_hash;

        if (nextHash == currentHash)
        {
            totalSimiliarSize += currentNode->total_allocation;
            totalSimiliarAllocs++;
        }

        if (nextHash != currentHash)
        {
            //Print total allocs for type and total size
            float reportedBytes = convertToReadableBytes(totalSimiliarSize);
            std::string size = convertToReadableBytesString(totalSimiliarSize);

            char collection_buffer[128];
            sprintf_s(collection_buffer, 128, "\nGroup contained %s allocation(s), Total: %0.3f %s\n", std::to_string(totalSimiliarAllocs).c_str(), reportedBytes, size.c_str());
            printf(collection_buffer);

            //Reset total allocs and size
            totalSimiliarAllocs = 0;
            totalSimiliarSize = 0;
        }

        // Printing a call stack, happens when making report
        char line_buffer[512];
        callstack_line_t lines[128];
        unsigned int line_count = CallstackGetLines(lines, 128, currentNode->current_stack);
        for (unsigned int i = 0; i < line_count; ++i)
        {
            // this specific format will make it double click-able in an output window 
            // taking you to the offending line.

            //Print Line For Call Stack
            sprintf_s(line_buffer, 512, "     %s(%u): %s\n", lines[i].file_name, lines[i].line, lines[i].function_name);

            printf(line_buffer);
        }

        currentNode = currentNode->next;
    }
}

void Pop64List(int64_t* arr[], int size)
{
    for (int index = 0; index < size; ++index)
    {
        arr[index] = new int64_t;
        *arr[index] = (int64_t)index;
    }
}

void Pop8List(int8_t* arr[], int size)
{
    for (int index = 0; index < size; ++index)
    {
        arr[index] = new int8_t;
        *arr[index] = (int8_t)index;
    }
}

int main()
{
    if (!CallstackSystemInit())
        return 1;

    const int SIZE_64 = 8000;
    int64_t* arr_64[SIZE_64];
    const int SIZE_8 = 10000;
    int8_t* arr_8[SIZE_8];

    Pop64List(arr_64, SIZE_64);
    Pop8List(arr_8, SIZE_8);

    ReportVerboseCallStacks();

    CallstackSystemDeinit();

    return 0;
}

标签: c++memory-managementlinked-list

解决方案


我终于想出了答案。在我的报告功能中,我std::string用来创建一些报告对象。std::string在内部调用::new以创建一个小的分配,然后在字符串的内部数组重新分配内存时锤击额外的内存。切换到 C 字符串解决了我的问题。


推荐阅读