活动介绍
file-type

手动构建内存泄露分析工具:探究memory leak & double free的排查

下载需积分: 41 | 559KB | 更新于2024-09-12 | 2 浏览量 | 3 评论 | 4 下载量 举报 收藏
download 立即下载
"本文主要探讨了如何排查内存泄露和双释放(double free)的问题,通过构建自己的内存分析工具来理解内存管理的本质,提供了一种在没有现成工具时自我解决的方法。文章提到了内存泄露是分配内存多于释放,而双释放则是释放内存多于分配,后者通常能被系统检测到并导致程序崩溃。排查方法包括收集分配和释放的信息,并分析这些信息以找出未释放的内存。获取调用者(caller)地址是关键,文章列举了几种获取caller地址的方法,并计划介绍常用的内存泄露工具。" 在排查内存泄露和双释放问题时,首先要理解它们的定义。内存泄露是指程序中申请的内存没有被正确地释放,这可能导致系统资源逐渐耗尽。而双释放是指同一个内存块被释放两次,这是不安全的操作,因为第二次释放可能会导致数据损坏或程序崩溃。由于C++标准库和一些运行时环境(如glibc)会对双释放进行检查,因此双释放通常更容易被检测到。 为了排查这些问题,文章建议从两个主要步骤着手。第一步是收集内存分配和释放的信息。在调用`malloc`、`calloc`、`realloc`、`memalign`、`new`等分配函数时记录分配点(即调用者地址)、分配大小和内存地址,在调用`free`、`delete`时记录释放点和释放的内存地址。这可以通过插入日志或修改编译器选项来实现。 第二步是对收集到的数据进行分析。通过比较分配的内存地址集合和释放的内存地址集合,可以找到未释放的内存块。这通常涉及到遍历记录的数据并计算地址的差集。 获取调用者地址是这一过程的关键,因为它可以帮助定位问题的源头。文章提到了几种获取caller地址的方法,例如通过在函数中设置标签然后获取标签地址,或者利用调试信息(如GCC的`__builtin_return_address`)来获取。 最后,文章还计划介绍一些常用的内存泄露检测工具,这些工具可能包含更复杂的算法和机制,如Valgrind、LeakSanitizer等,它们能够自动化地帮助开发者发现内存泄露和双释放等问题,提高排查效率。 理解和掌握内存管理以及排查技巧对于任何涉及内存操作的程序开发都至关重要。通过构建自己的工具或利用现有的内存分析工具,开发者可以有效地防止和修复这类问题,从而保证程序的稳定性和效率。

相关推荐

filetype

#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <ctype.h> #define TOTAL_FLAGS 200000 #define WORDS_NEEDED ((TOTAL_FLAGS + 15) / 16) typedef struct { uint16_t* data; size_t total_flags; size_t data_size; } FlagManager; // 创建FlagManager FlagManager* flag_manager_create() { FlagManager* fm = (FlagManager*)malloc(sizeof(FlagManager)); if (!fm) return NULL; fm->total_flags = TOTAL_FLAGS; fm->data_size = WORDS_NEEDED; fm->data = (uint16_t*)calloc(fm->data_size, sizeof(uint16_t)); if (!fm->data) { free(fm); return NULL; } return fm; } // 释放FlagManager void flag_manager_free(FlagManager* fm) { if (fm) { free(fm->data); free(fm); } } // 设置标志位 void flag_set(FlagManager* fm, size_t position, bool value) { if (!fm || position >= fm->total_flags) return; size_t word_index = position / 16; size_t bit_index = position % 16; if (value) { fm->data[word_index] |= (1U << bit_index); } else { fm->data[word_index] &= ~(1U << bit_index); } } // 获取标志位 bool flag_get(FlagManager* fm, size_t position) { if (!fm || position >= fm->total_flags) return false; size_t word_index = position / 16; size_t bit_index = position % 16; return (fm->data[word_index] >> bit_index) & 1; } // 将单个十六进制字符转换为数值 static uint8_t hex_char_to_value(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; return 0; } // 将数值转换为十六进制字符 static char value_to_hex_char(uint8_t value) { static const char hex_chars[] = "0123456789ABCDEF"; return hex_chars[value & 0x0F]; } // 将FlagManager转换为十六进制字符串 char* flag_manager_to_hex(FlagManager* fm) { if (!fm) return NULL; // 每个uint16_t需要4个十六进制字符,加上结束符 size_t hex_length = fm->data_size * 4 + 1; char* hex_str = (char*)malloc(hex_length); if (!hex_str) return NULL; for (size_t i = 0; i < fm->data_size; i++) { uint16_t value = fm->data[i]; // 手动转换,避免sprintf的开销 hex_str[i * 4 + 0] = value_to_hex_char(value >> 12); hex_str[i * 4 + 1] = value_to_hex_char(value >> 8); hex_str[i * 4 + 2] = value_to_hex_char(value >> 4); hex_str[i * 4 + 3] = value_to_hex_char(value); } hex_str[hex_length - 1] = '\0'; return hex_str; } // 从十六进制字符串恢复FlagManager FlagManager* flag_manager_from_hex(const char* hex_str) { if (!hex_str) return NULL; size_t hex_len = strlen(hex_str); if (hex_len % 4 != 0) { printf("错误: 十六进制字符串长度必须是4的倍数\n"); return NULL; } size_t data_size = hex_len / 4; size_t total_flags = data_size * 16; // 创建FlagManager FlagManager* fm = (FlagManager*)malloc(sizeof(FlagManager)); if (!fm) return NULL; fm->total_flags = total_flags; fm->data_size = data_size; fm->data = (uint16_t*)malloc(data_size * sizeof(uint16_t)); if (!fm->data) { free(fm); return NULL; } // 解析十六进制字符串 for (size_t i = 0; i < data_size; i++) { const char* hex_pos = hex_str + i * 4; // 手动解析,避免sscanf的开销 uint16_t value = 0; value |= (hex_char_to_value(hex_pos[0]) << 12); value |= (hex_char_to_value(hex_pos[1]) << 8); value |= (hex_char_to_value(hex_pos[2]) << 4); value |= hex_char_to_value(hex_pos[3]); fm->data[i] = value; } return fm; } // 验证十六进制字符串是否有效 bool is_valid_hex_string(const char* hex_str) { if (!hex_str) return false; size_t len = strlen(hex_str); if (len % 4 != 0) return false; for (size_t i = 0; i < len; i++) { char c = hex_str[i]; if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) { return false; } } return true; } // 生成SQL插入语句 char* generate_insert_sql_hex(const char* table_name, int record_id, const char* hex_flags) { if (!table_name || !hex_flags) return NULL; size_t sql_size = strlen(table_name) + strlen(hex_flags) + 100; char* sql = (char*)malloc(sql_size); if (!sql) return NULL; snprintf(sql, sql_size, "INSERT INTO %s (id, flags_hex) VALUES (%d, '%s')", table_name, record_id, hex_flags); return sql; } // 生成SQL更新语句 char* generate_update_sql_hex(const char* table_name, int record_id, const char* hex_flags) { if (!table_name || !hex_flags) return NULL; size_t sql_size = strlen(table_name) + strlen(hex_flags) + 100; char* sql = (char*)malloc(sql_size); if (!sql) return NULL; snprintf(sql, sql_size, "UPDATE %s SET flags_hex = '%s' WHERE id = %d", table_name, record_id, hex_flags); return sql; } // 显示内存使用信息 void print_memory_info(FlagManager* fm, const char* hex_str) { if (!fm || !hex_str) return; size_t binary_size = fm->data_size * sizeof(uint16_t); size_t hex_size = strlen(hex_str); printf("=== 内存使用信息 ===\n"); printf("标志数量: %zu\n", fm->total_flags); printf("二进制数据大小: %.2f KB\n", binary_size / 1024.0); printf("十六进制字符串大小: %.2f KB\n", hex_size / 1024.0); printf("膨胀率: %.2f%%\n", (hex_size * 100.0) / binary_size); printf("十六进制字符串长度: %zu\n", hex_size); printf("===================\n"); } // 测试函数 void test_hex_conversion() { printf("测试十六进制编码/解码...\n"); // 创建并设置一些标志 FlagManager* fm = flag_manager_create(); flag_set(fm, 0, true); flag_set(fm, 1, false); flag_set(fm, 15, true); // 第一个字的最后一位 flag_set(fm, 16, true); // 第二个字的第一位 flag_set(fm, 999, true); flag_set(fm, 199999, true); // 最后一个标志 // 转换为十六进制 char* hex_str = flag_manager_to_hex(fm); if (!hex_str) { printf("十六进制转换失败\n"); flag_manager_free(fm); return; } print_memory_info(fm, hex_str); // 生成SQL语句 char* insert_sql = generate_insert_sql_hex("user_flags", 123, hex_str); char* update_sql = generate_update_sql_hex("user_flags", 123, hex_str); printf("插入SQL: %s\n", insert_sql); printf("更新SQL: %s\n", update_sql); // 从十六进制恢复 FlagManager* restored_fm = flag_manager_from_hex(hex_str); if (restored_fm) { printf("恢复验证:\n"); printf("Flag 0: %d (期望: 1)\n", flag_get(restored_fm, 0)); printf("Flag 1: %d (期望: 0)\n", flag_get(restored_fm, 1)); printf("Flag 15: %d (期望: 1)\n", flag_get(restored_fm, 15)); printf("Flag 16: %d (期望: 1)\n", flag_get(restored_fm, 16)); printf("Flag 999: %d (期望: 1)\n", flag_get(restored_fm, 999)); printf("Flag 199999: %d (期望: 1)\n", flag_get(restored_fm, 199999)); printf("Flag 100: %d (期望: 0)\n", flag_get(restored_fm, 100)); flag_manager_free(restored_fm); } else { printf("十六进制恢复失败\n"); } // 清理 free(hex_str); free(insert_sql); free(update_sql); flag_manager_free(fm); } // 性能测试 void performance_test() { printf("\n性能测试...\n"); FlagManager* fm = flag_manager_create(); // 设置大量标志 for (size_t i = 0; i < TOTAL_FLAGS; i += 100) { flag_set(fm, i, true); } clock_t start = clock(); char* hex_str = flag_manager_to_hex(fm); clock_t end = clock(); if (hex_str) { double encode_time = (double)(end - start) / CLOCKS_PER_SEC * 1000; printf("编码时间: %.2f ms\n", encode_time); start = clock(); FlagManager* restored = flag_manager_from_hex(hex_str); end = clock(); if (restored) { double decode_time = (double)(end - start) / CLOCKS_PER_SEC * 1000; printf("解码时间: %.2f ms\n", decode_time); flag_manager_free(restored); } free(hex_str); } flag_manager_free(fm); } int main() { printf("十六进制编码方案测试\n"); printf("==================\n"); test_hex_conversion(); performance_test(); return 0; }这个是代码

资源评论
用户头像
VashtaNerada
2025.08.02
文章聚焦实践操作,助你成为内存问题解决高手。
用户头像
雨后的印
2025.04.29
无工具也可排查内存问题,这篇文章给出了很好的指导。
用户头像
俞林鑫
2025.03.19
深入浅出,教你自己动手排查内存问题,方法实用。🐷