【CPP】内存泄漏详解

C++ 内存泄漏-原因、避免、定位 大家好!作为C/C++开发人员,内存泄漏是最容易遇到的问题之一,这是由C/C++语言的特性引起的。C/C++语言与其他语言不同,需要开发者去申请和释放内存,即需要开发者去管理内存,如果内存使用不当,就容易造成或者。今天,借助此文,分析下项目中经常遇到的导致内存泄漏的原因,以及如何避免和定位内存泄漏。 阅读详情

一、内存泄漏概念

内存泄漏(Memory Leak)是指程序在运行过程中申请了内存而未能释放,导致这部分内存无法被系统回收,从而造成内存资源的浪费。内存泄漏通常会逐渐积累,导致程序占用过多的内存资源,最终可能引发程序崩溃、系统卡顿,甚至在长时间运行的系统中发生内存溢出。内存泄漏问题在开发过程中必须引起足够重视,因为它会显著降低系统性能并影响程序的稳定性。

在 C++ 中,内存泄漏通常是由于开发者在使用动态内存分配(如 new / malloc)时未能正确释放内存(如未使用 delete / free)所导致。内存泄漏不仅仅发生在堆内存分配时,还可能发生在其他资源(如文件句柄、数据库连接等)未正确释放时。

二、内存泄漏常见场景

1)忘记释放堆内存

当使用 new 或 malloc 分配堆内存时,如果没有使用 delete 或 free 释放这部分内存,就会导致内存泄漏。这种情况是最常见的内存泄漏问题。忘记释放内存会导致程序持续占用内存,最终耗尽系统资源。

#include <iostream>

void leakMemory() {
    int* p = new int(10);  // 动态分配内存
    // 忘记释放内存
}

int main() {
    leakMemory();
    return 0;
}
  • 在上面的代码中,new int(10) 分配了堆内存,但没有释放内存,导致内存泄漏。程序退出时,系统无法回收这部分内存。

2)异常路径未释放资源

如果程序中发生异常,且没有适当地释放已分配的资源(例如堆内存、文件句柄等),也会导致内存泄漏。特别是在使用裸指针或手动资源管理时,容易漏掉释放的步骤。异常发生时未进行资源清理,常常会导致程序不稳定。

#include <iostream>
#include <stdexcept>

void functionWithException() {
    int* p = new int(20);  // 分配内存
    throw std::runtime_error("An error occurred");  // 异常发生
    delete p;  // 这行代码不会被执行
}

int main() {
    try {
        functionWithException();
    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }
    return 0;
}
  • 在 functionWithException 函数中,分配了堆内存并抛出异常。由于 delete p 没有执行,导致分配的内存没有被释放。

3)容器中指针未清理

C++ 容器(如 std::vector, std::list, std::map)存储的是指向对象的指针。如果在容器中存储指针类型数据而没有正确释放对象的内存,会导致内存泄漏。容器在销毁时并不会自动释放容器内存中的指针对象。

#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};

void containerMemoryLeak() {
    std::vector<MyClass*> vec;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(new MyClass());  // 分配内存
    }
    // 忘记调用 delete 删除指针
}

int main() {
    containerMemoryLeak();
    return 0;
}
  • 在 containerMemoryLeak 函数中,std::vector 存储了 MyClass 类型的裸指针。由于没有调用 delete 释放内存,这些指针指向的对象不会被销毁,导致内存泄漏。

4)循环引用

循环引用是指两个或多个对象互相持有对方的引用,导致它们的生命周期无法结束。这种情况通常发生在使用智能指针时,尤其是 std::shared_ptr,因为 shared_ptr 会引用计数,只要引用计数不为零,对象就不会被销毁。

#include <iostream>
#include <memory>

class A;

class B {
public:
    std::shared_ptr<A> a;
    ~B() { std::cout << "B destroyed" << std::endl; }
};

class A {
public:
    std::shared_ptr<B> b;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

void circularReference() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    
    a->b = b;  // A 拥有 B
    b->a = a;  // B 拥有 A
    // 循环引用导致 A 和 B 永远不会被销毁
}

int main() {
    circularReference();
    return 0;
}
  • 在 circularReference 函数中,A 类和 B 类相互持有 std::shared_ptr,造成了循环引用。由于 shared_ptr 会递增引用计数,导致 A 和 B 永远无法被销毁,内存泄漏发生。

5)系统资源泄漏

除此之外,程序中还可能发生其他类型的资源泄漏,特别是系统资源,如文件描述符、网络连接、数据库连接等。系统资源泄漏指的是在程序运行时,分配了某些资源但未能及时释放,导致这些资源无法再被使用,可能会导致系统性能下降或甚至崩溃。

常见的系统资源泄漏场景包括:

  • 文件描述符泄漏:在程序中打开文件后,没有及时关闭文件描述符,导致文件描述符在程序结束时依然保持打开状态。随着时间的推移,打开的文件描述符数量会累积,最终可能达到系统的最大文件描述符限制,导致程序无法再打开新的文件。
  • 网络连接泄漏:在程序中创建了网络连接(例如,套接字连接)后,如果没有适当关闭这些连接,会导致连接池资源被耗尽,从而影响程序的网络通信功能。
  • 数据库连接泄漏:在使用数据库时,如果没有关闭数据库连接池中的连接,会导致连接泄漏,最终导致数据库连接池中的连接数量达到上限,影响系统的数据库操作。
#include <iostream>
#include <fstream>

void fileLeak() {
    std::ofstream file("example.txt");  // 打开文件
    // 文件没有关闭,会导致文件描述符泄漏
    // 由于 file 对象是局部变量,程序结束时会自动析构,但如果没有显示调用 close(),会造成文件描述符泄漏
}

int main() {
    fileLeak();
    return 0;
}

三、内存泄漏避免

1) 使用智能指针

智能指针是 C++11 引入的一种指针类型,用于自动管理内存的分配和释放。std::unique_ptr 和 std::shared_ptr 是两种常用的智能指针,它们能够自动在超出作用域时释放内存,避免忘记释放的风险。

  • std::unique_ptr:用于独占所有权的智能指针。一个 unique_ptr 只能有一个所有者,因此当其作用域结束时,内存会自动释放。
  • std::shared_ptr:用于共享所有权的智能指针。多个 shared_ptr 可以共享同一个资源,只有当最后一个 shared_ptr 被销毁时,内存才会被释放。
#include <memory>

void exampleUniquePtr() {
    std::unique_ptr<int> p(new int(10));  // 使用 unique_ptr 自动管理内存
    // 不需要手动调用 delete,内存会在作用域结束时自动释放
}

void exampleSharedPtr() {
    std::shared_ptr<int> p1 = std::make_shared<int>(20);  // 使用 shared_ptr 自动管理内存
    std::shared_ptr<int> p2 = p1;  // p1 和 p2 共享内存
    // 内存会在 p1 和 p2 被销毁时自动释放
}

2) RAII 原则

RAII(Resource Acquisition Is Initialization)是 C++ 中的一种资源管理模式,意味着资源(如内存、文件句柄、网络连接等)在对象的构造函数中分配,并且在析构函数中释放。通过这种方式,可以确保资源在对象生命周期结束时被正确释放,从而避免内存泄漏。

#include <iostream>

class ResourceManager {
public:
    ResourceManager() {
        p = new int(10);  // 构造函数中分配资源
    }

    ~ResourceManager() {
        delete p;  // 析构函数中释放资源
    }

private:
    int* p;
};

void exampleRAII() {
    ResourceManager rm;  // 自动管理内存,RAII原理
    // 不需要手动释放内存,析构函数会自动调用
}

3) 避免裸指针长期持有资源

裸指针容易导致内存泄漏,特别是在长时间持有资源时。如果在某些情况下必须使用裸指针,确保有合适的释放逻辑。通常情况下,推荐使用智能指针来管理内存,避免裸指针长期持有资源。

#include <memory>

class MyClass {
public:
    void* ptr;
    MyClass() { 
        ptr = malloc(100);  // 分配内存
    }
    ~MyClass() {
        if (ptr) {
            free(ptr);  // 确保资源被释放
        }
    }
};

4) 规范编码

确保每个 new 对应一个 delete,每个 malloc 对应一个 free。为了避免内存泄漏,必须确保每次使用 new 或 malloc 分配内存时,都会有相应的 delete 或 free 语句释放内存。在复杂的函数中,特别是有多个分支的函数中,需要格外注意确保每条路径都能正确释放资源。

#include <iostream>

void safeMemoryManagement() {
    int* p = new int(10);  // 使用 new 分配内存
    if (p == nullptr) {
        // 内存分配失败的处理
        return;
    }

    // 确保在所有路径中都能释放内存
    delete p;  // 对应的 delete
}

5) 避免循环引用

循环引用通常发生在智能指针(特别是 std::shared_ptr)之间,导致对象永远不能被销毁。为了避免循环引用,可以使用 std::weak_ptr,它不会增加引用计数,从而避免循环引用带来的内存泄漏。

#include <iostream>
#include <memory>

class A;
class B {
public:
    std::weak_ptr<A> a;  // 使用 weak_ptr 避免循环引用
};

class A {
public:
    std::shared_ptr<B> b;
};

void fixCircularReference() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->b = b;
    b->a = a;  // 使用 weak_ptr 解决循环引用
}

int main() {
    fixCircularReference();
    return 0;
}

四、内存泄漏排查

内存泄漏的排查和定位是开发过程中重要的调试任务。尽早发现并修复内存泄漏能够提高程序的稳定性,减少系统资源的浪费。以下是一些常用的内存泄漏排查方法:

1) 使用内存检测工具

内存检测工具可以帮助开发人员检测和定位内存泄漏。最常用的工具包括:

  • Valgrind:Valgrind 是一个开源的内存调试工具,它能够在程序运行时监控内存的分配和释放,帮助开发者检测内存泄漏、越界访问、未初始化内存的使用等问题。Valgrind 提供了 memcheck 工具来检测内存泄漏。
  • AddressSanitizer:AddressSanitizer(简称 ASan)是一个快速的内存错误检测工具,能够检测内存泄漏、越界访问等问题。它通过编译时插桩,在程序运行时进行检查。
  • heaptrack:heaptrack 是一个专门用于跟踪 C++ 程序内存分配的工具,它能够提供详细的内存分配跟踪,并生成分析报告,帮助开发者精确定位内存泄漏问题。

2) 手动代码审查

除了工具,手动代码审查也是排查内存泄漏的重要手段。通过代码审查,开发人员可以查找可能的内存泄漏点,以下是常见的检查点:

  • 确保每个 new 或 malloc 都有相应的 delete 或 free: 对于每次动态内存分配,必须确保有对应的释放操作,避免遗忘释放。
  • 确保异常发生时的内存释放: 在函数中,特别是有异常处理的函数中,确保即使在异常发生时也能够释放已分配的内存。可以使用 RAII 原则(通过构造函数分配资源,析构函数释放资源)来避免异常未释放资源。
  • 查找是否有未释放的容器内存: 特别是在容器中存储动态分配的内存时,确保容器的析构函数正确释放内存。
  • 检查是否有裸指针的使用: 裸指针可能导致内存管理不当,使用智能指针(如 std::unique_ptr 和 std::shared_ptr)可以避免许多内存泄漏问题。
  • 检查 shared_ptr 是否存在循环引用: 在使用 std::shared_ptr 时,确保不存在循环引用的情况,循环引用会导致内存无法释放。可以使用 std::weak_ptr 来避免这种问题。

3) 日志和堆栈跟踪

通过在代码中加入日志打印,可以帮助开发者跟踪内存的分配和释放过程。特别是在调试复杂的程序时,堆栈跟踪信息也能帮助定位内存泄漏的根源。

  • 日志打印:可以在内存分配和释放的关键点添加日志,记录指针值和内存分配的上下文信息。

    #include <iostream>
    	 
    	 void* operator new(size_t size) {
    	     std::cout << "Allocating memory of size " << size << " bytes\n";
    	     return malloc(size);
    	 }
    	 
    	 void operator delete(void* pointer) noexcept {
    	     std::cout << "Freeing memory at " << pointer << "\n";
    	     free(pointer);
    	 }
    	 
    	 void exampleMemoryLeak() {
    	     int* p = new int(10);
    	     // 忘记释放内存
    	 }
    	 
    	 int main() {
    	     exampleMemoryLeak();
    	     return 0;
    	 }
    
  • 堆栈跟踪:使用调试器(如 GDB)时,启用调试符号并查看堆栈跟踪,帮助开发者更好地定位内存泄漏的根源。

    g++ -g your_program.cpp -o your_program
    gdb ./your_program
    

4) 内存管理策略的优化

通过优化内存管理策略,可以减少内存泄漏的发生:

  • 使用智能指针:在 C++ 中,尽量使用智能指针(如 std::unique_ptr 和 std::shared_ptr),它们能够自动管理内存的分配和释放,避免手动管理带来的内存泄漏问题。
  • 使用容器和算法:在适当的情况下,使用标准库中的容器和算法(如 std::vector, std::map, std::string 等),这些容器能够自动管理内存,减少内存泄漏的风险。

五、Valgrind

1)Valgrind使用

  • valgrind [选项] <可执行程序>

    valgrind --leak-check=full --show-leak-kinds=all ./your_program

    • --leak-check=full:启用内存泄漏检测,并显示详细的泄漏信息。full 选项会提供有关内存泄漏的详细报告,包括泄漏的内存量、泄漏发生的代码行以及调用栈等信息。
    • --show-leak-kinds=all:显示所有类型的内存泄漏,包括直接泄漏、间接泄漏、可能泄漏等。
    • --track-origins=yes:追踪未初始化内存的来源。这对于调试未初始化内存访问非常有帮助。
    • --tool=memcheck:选择 memcheck 工具来进行内存检查,memcheck 是 Valgrind 默认的内存检查工具。
    • --log-file=<file>:将 Valgrind 的输出记录到指定的文件中。对于大型程序或长时间运行的程序,这个选项非常有用。
  • 编译代码时要使用-g选项

2)Valgrind输出

Valgrind 的输出可以帮助开发者详细了解程序中的内存问题,特别是内存泄漏的情况。以下是一个典型的 Valgrind 输出示例:

==1208228== Memcheck, a memory error detector
==1208228== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1208228== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==1208228== Command: ./a.out
==1208228== 
==1208228== 
==1208228== HEAP SUMMARY:
==1208228==     in use at exit: 20 bytes in 1 blocks
==1208228==   total heap usage: 1 allocs, 0 frees, 20 bytes allocated
==1208228== 
==1208228== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1208228==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1208228==    by 0x1091B0: make_copy(char const*) (in /home/Raizeroko/code/MemoryLeak/a.out)
==1208228==    by 0x1091E4: main (in /home/Raizeroko/code/MemoryLeak/a.out)
==1208228== 
==1208228== LEAK SUMMARY:
==1208228==    definitely lost: 20 bytes in 1 blocks
==1208228==    indirectly lost: 0 bytes in 0 blocks
==1208228==      possibly lost: 0 bytes in 0 blocks
==1208228==    still reachable: 0 bytes in 0 blocks
==1208228==         suppressed: 0 bytes in 0 blocks
==1208228== 
==1208228== For lists of detected and suppressed errors, rerun with: -s
==1208228== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
  • HEAP SUMMARY:
    • in use at exit: 20 bytes in 1 blocks:表示程序退出时,有 20 字节的内存依然在使用中,且是 1 个内存块。
    • total heap usage: 1 allocs, 0 frees, 20 bytes allocated:表示程序共进行了 1 次内存分配操作,且没有进行内存释放,分配了 20 字节的内存。
  • 内存泄漏详细信息:
    • 20 bytes in 1 blocks are definitely lost in loss record 1 of 1:表明程序确实发生了 20 字节的内存泄漏,这块内存没有被释放。
    • at 0x4848899: malloc:泄漏发生的位置是 malloc 函数内存分配的地方。
  • 泄漏发生的调用堆栈:
    • by 0x1091B0: make_copy(char const*):表示泄漏的内存是在 make_copy 函数中分配的。
    • by 0x1091E4: main:泄漏发生在 main 函数中调用 make_copy 时。
  • LEAK SUMMARY:
    • definitely lost:真·泄漏,程序没有任何指针指向这块内存了
    • indirectly lost:间接泄漏,一块泄漏的内存引用了另一块
    • possibly lost:可能泄漏,Valgrind 不确定程序是否还保留了这块内存的地址
    • still reachable:程序结束时仍有指针指向,不一定是 bug,比如全局缓存
    • suppressed:被你通过配置 suppress 文件显式屏蔽的问题,Valgrind 不再报告它们。
  • ERROR SUMMARY:
    • 1 errors from 1 contexts (suppressed: 0 from 0):表明总共检测到 1 个错误,且没有被抑制。

definitely lost、indirectly lost、possibly lost 和 still reachable 之间的区别

  • definitely lost(确定泄漏)

    这表示程序没有任何指针指向该内存块,内存已经丢失。也就是说,程序员再也无法访问到这块内存,它完全“失去了控制”。

    int main() {
        int* ptr = (int*)malloc(sizeof(int));  // 动态分配内存
        // 这里忘了释放内存
        return 0;
    }
    
    • 泄漏信息

      ==12345== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
      ==12345==    at 0x4C2FB55: malloc (vg_replace_malloc.c:299)
      ==12345==    by 0x4011F3: main (main.cpp:5)
      
  • indirectly lost(间接泄漏)

    这种情况通常发生在某个指针指向的内存块无法释放,而该指针本身是由另一个对象或结构体所指向的。间接泄漏意味着某块内存可能是由于其他内存块的丢失而无法释放的。

     struct Node {
     int* data;
    };
    
     int main() {
     	Node* node = (Node*)malloc(sizeof(Node));
     	node->data = (int*)malloc(sizeof(int));
    	 // 这里忘了释放内存
     	free(node);
     	return 0;
     }
    
    • 泄漏信息

      ==12345== 4 bytes in 1 blocks are indirectly lost
      ==12345==    at 0x4C2FB55: malloc (vg_replace_malloc.c:299)
      ==12345==    by 0x4011F3: main (main.cpp:6)
      ==12345==    by 0x401207: free (vg_replace_malloc.c:299)
      
  • possibly lost(可能泄漏)

    这种情况表示程序中有一个指针指向了这块内存,但在程序运行时,Valgrind 无法确定是否程序仍然能访问到这块内存。程序可能仍然有机会访问它,但不确定。

    int main() {
        int* ptr = (int*)malloc(sizeof(int));  // 动态分配内存
        ptr = (int*)malloc(sizeof(int));  // 重新分配,原来的内存被覆盖
        free(ptr);
        return 0;
    }
    
    • 泄漏信息

      ==12345== 4 bytes in 1 blocks are possibly lost
      ==12345==    at 0x4C2FB55: malloc (vg_replace_malloc.c:299)
      ==12345==    by 0x4011F3: main (main.cpp:5)
      
  • still reachable(仍可访问)

    这种情况表示程序仍然有指针能够访问到这块内存,这块内存没有被泄漏,程序可以正常释放它。通常这是程序正常退出时的情况。

    int main() {
        int* ptr = (int*)malloc(sizeof(int));  // 动态分配内存
        free(ptr);  // 正常释放
        return 0;
    }
    
    • 泄漏信息

      ==12345== 4 bytes in 1 blocks are still reachable
      ==12345==    at 0x4C2FB55: malloc (vg_replace_malloc.c:299)
      ==12345==    by 0x4011F3: main (main.cpp:5)
      
  • supressed(被抑制的)

    很多时候,一些库或系统函数会在你无法控制的地方分配小量内存(比如系统内部的 glibc、C++标准库,或者 Qt、Boost、OpenSSL 等)。这些泄漏你也许无法修复,但每次都出现在报告里很烦,这时候可以写 suppression 规则文件,告诉 Valgrind 忽略这些。

    • suppression文件示例:

      {
         libc-start
         Memcheck:Leak
         fun:malloc
         fun:__libc_start_main
      }
      
    • 然后运行 valgrind 加上--suppressions=<supression文件>选项

      valgrind --suppressions=mysuppress.supp ./your_program
      

3)借助valgrind查询泄漏

  • 为了简化代码,部分泄漏处理不太合理,只是为了方便讲解。
1. 内存分配后忘记释放

在 C++ 中,如果你为一个对象或数组动态分配了内存,但没有在适当的位置释放这些内存,就会导致内存泄漏。示例:

class MyClass {
public:
    MyClass() {
        ptr = new int[100];
    }
    ~MyClass() {
        
    }

private:
    int* ptr;
};

int main() {
    MyClass* obj = new MyClass();

    return 0;
}   
  • valgrind输出:

    Ubuntu ❯❯❯ valgrind --leak-check=full ./a.out
    ==1245080== Memcheck, a memory error detector
    ==1245080== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==1245080== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
    ==1245080== Command: ./a.out
    ==1245080== 
    ==1245080== 
    ==1245080== HEAP SUMMARY:
    ==1245080==     in use at exit: 408 bytes in 2 blocks
    ==1245080==   total heap usage: 3 allocs, 1 frees, 73,112 bytes allocated
    ==1245080== 
    ==1245080== 408 (8 direct, 400 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
    ==1245080==    at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==1245080==    by 0x109201: main (leak1.cc:17)
    ==1245080== 
    ==1245080== LEAK SUMMARY:
    ==1245080==    definitely lost: 8 bytes in 1 blocks
    ==1245080==    indirectly lost: 400 bytes in 1 blocks
    ==1245080==      possibly lost: 0 bytes in 0 blocks
    ==1245080==    still reachable: 0 bytes in 0 blocks
    ==1245080==         suppressed: 0 bytes in 0 blocks
    ==1245080== 
    ==1245080== For lists of detected and suppressed errors, rerun with: -s
    ==1245080== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    
  • 分析

    • Heap Summary (堆内存总结):
      • in use at exit: 408 bytes in 2 blocks:程序结束时,仍有 408 字节的内存在使用中。这个值表明程序退出时存在未释放的内存块。
      • total heap usage: 3 allocs, 1 frees, 73,112 bytes allocated:在整个程序的执行过程中,进行了 3 次内存分配(allocs)和 1 次内存释放(frees)。这说明在程序结束时,分配的内存没有完全被释放。
    • Leak Details (泄漏详细信息):
      • 408 (8 direct, 400 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2:这里报告了内存泄漏的详细信息:
        • 8 字节的内存被“直接丢失”(direct lost)。
        • 400 字节的内存被“间接丢失”(indirect lost)。这通常是由于内存块引用了其他内存,而这些引用没有被释放,导致这些内存无法被正确回收。
        • 这一行提供了泄漏发生的精确位置,指出是某个内存块的丢失,且是由程序中的特定代码引起的。
    • 泄漏发生位置:
      • at 0x4849013: operator new(unsigned long):泄漏发生的位置是 new 操作符的调用,它显示了分配内存时的堆栈地址。
      • by 0x109201: main (in /home/Raizeroko/code/MemoryLeak/a.out):泄漏发生的具体代码位置是在 main 函数中。
    • Leaked Memory Summary (泄漏内存汇总):
      • definitely lost: 8 bytes in 1 blocks:这意味着 8 字节内存丢失的部分是“确定丢失”(definitely lost),即程序已经没有任何指针指向该内存区域,这块内存完全无法访问和释放。
      • indirectly lost: 400 bytes in 1 blocks:这块 400 字节的内存间接丢失,通常是因为它依赖于已丢失的内存块(例如通过指针引用)。间接丢失的内存一般是由内存泄漏的某个其他部分引起的。
  • 改进

    class MyClass {
    public:
        MyClass() {
            data = new int[100];
        }
        ~MyClass() {
            delete[] data; // 析构函数释放
        }
    
    private:
        int* data;
    };
    
    void createObject() {
        MyClass* obj = new MyClass();
        // 释放(实际一般不在这里delete,这里为了方便)
        delete obj;
    }
    
    int main() {
        for (int i = 0; i < 10; ++i) {
            createObject();
        }
        return 0;
    }
    
2. 异常路径导致的内存泄漏

如果在某个操作抛出异常,并且没有相应的内存释放机制,程序就会发生内存泄漏。示例:

void processData() {
    int* data = new int[50];
    if (rand() % 2 == 0) {
        throw std::runtime_error("Error during data processing");
    }
    delete[] data;
}

void createObject() {
    try {
        processData();
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
}

int main() {
    for (int i = 0; i < 5; ++i) {
        createObject();
    }
    return 0;
}
  • valgrind信息

    Ubuntu ❯❯❯ valgrind --leak-check=full ./a.out
    ==1245306== Memcheck, a memory error detector
    ==1245306== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==1245306== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
    ==1245306== Command: ./a.out
    ==1245306== 
    Caught exception: Error during data processing
    ==1245306== 
    ==1245306== HEAP SUMMARY:
    ==1245306==     in use at exit: 200 bytes in 1 blocks
    ==1245306==   total heap usage: 9 allocs, 8 frees, 74,925 bytes allocated
    ==1245306== 
    ==1245306== 200 bytes in 1 blocks are definitely lost in loss record 1 of 1
    ==1245306==    at 0x484A2F3: operator new[](unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==1245306==    by 0x109301: processData() (leak2.cc:5)
    ==1245306==    by 0x10939C: createObject() (leak2.cc:14)
    ==1245306==    by 0x10944D: main (leak2.cc:22)
    ==1245306== 
    ==1245306== LEAK SUMMARY:
    ==1245306==    definitely lost: 200 bytes in 1 blocks
    ==1245306==    indirectly lost: 0 bytes in 0 blocks
    ==1245306==      possibly lost: 0 bytes in 0 blocks
    ==1245306==    still reachable: 0 bytes in 0 blocks
    ==1245306==         suppressed: 0 bytes in 0 blocks
    ==1245306== 
    ==1245306== For lists of detected and suppressed errors, rerun with: -s
    ==1245306== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    
  • 分析

    • Heap Summary (堆内存总结):
      • in use at exit: 200 bytes in 1 blocks:程序退出时仍有 200 字节内存未被释放。这个值表明存在未释放的内存。
      • total heap usage: 9 allocs, 8 frees, 74,925 bytes allocated:程序总共进行了 9 次内存分配(allocs)和 8 次内存释放(frees)。这表明有一次内存分配未被释放。
    • 泄漏详细信息:
      • 200 bytes in 1 blocks are definitely lost in loss record 1 of 1:200 字节内存是“确定丢失”(definitely lost),说明这部分内存已经无法访问,也没有其他指针指向它。
      • at 0x484A2F3: operator new[](unsigned long):泄漏发生在 new[] 操作符的位置。
      • by 0x109301: processData() (leak2.cc:5):泄漏发生在 processData 函数中的第 5 行,即 new int[50] 这一行。
      • by 0x10939C: createObject() (leak2.cc:14):processData 被 createObject 函数调用,而 createObject 位于第 14 行。
      • by 0x10944D: main (leak2.cc:22):最终,createObject 函数被 main 函数调用,main 函数位于第 22 行。
  • 改正

    #include <memory>
    
    void processData() {
        auto data = std::make_unique<int[]>(50);  // 使用智能指针管理内存
        if (rand() % 2 == 0) {
            throw std::runtime_error("Error during data processing");
        }
        // 内存会在智能指针超出作用域时自动释放
    }
    
    void createObject() {
        try {
            processData();
        } catch (const std::exception& e) {
            std::cout << "Caught exception: " << e.what() << std::endl;
        }
    }
    
    int main() {
        for (int i = 0; i < 5; ++i) {
            createObject();
        }
        return 0;
    }
    
    
3. 循环引用

循环引用通常发生在两个或多个对象互相持有对方的引用,从而形成一个环形结构。当这种环形结构的对象没有显式的释放机制时,就会导致内存无法被回收,从而引发内存泄漏。示例:

class A;
class B {
public:
    A* a;
    B() : a(nullptr) {}
};

class A {
public:
    B* b;
    A() : b(nullptr) {}
};

void createObjects(std::vector<A*> &vecA, std::vector<B*> &vecB) {
    A* objA = new A();
    B* objB = new B();
    objA->b = objB;
    objB->a = objA;

    vecA.push_back(objA);
    vecB.push_back(objB);
}

int main() {
    std::vector<A*> objectsA;
    std::vector<B*> objectsB;

    for (int i = 0; i < 5; ++i) {
        createObjects(objectsA, objectsB);
    }
    return 0;
}
  • Valgrind信息

    Ubuntu ❯❯❯ valgrind --leak-check=full ./a.out
    ==1245671== Memcheck, a memory error detector
    ==1245671== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==1245671== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
    ==1245671== Command: ./a.out
    ==1245671== 
    A created
    B created
    ==1245671== 
    ==1245671== HEAP SUMMARY:
    ==1245671==     in use at exit: 64 bytes in 2 blocks
    ==1245671==   total heap usage: 4 allocs, 2 frees, 73,792 bytes allocated
    ==1245671== 
    ==1245671== 64 (32 direct, 32 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
    ==1245671==    at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==1245671==    by 0x10A929: __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<A, std::allocator<A>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (new_allocator.h:127)
    ==1245671==    by 0x10A622: std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<A, std::allocator<A>, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_ptr_inplace<A, std::allocator<A>, (__gnu_cxx::_Lock_policy)2> >&, unsigned long) (alloc_traits.h:464)
    ==1245671==    by 0x10A15F: std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<A, std::allocator<A>, (__gnu_cxx::_Lock_policy)2> > > std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<A, std::allocator<A>, (__gnu_cxx::_Lock_policy)2> > >(std::allocator<std::_Sp_counted_ptr_inplace<A, std::allocator<A>, (__gnu_cxx::_Lock_policy)2> >&) (allocated_ptr.h:98)
    ==1245671==    by 0x109E65: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<A, std::allocator<A>>(A*&, std::_Sp_alloc_shared_tag<std::allocator<A> >) (shared_ptr_base.h:648)
    ==1245671==    by 0x109DA5: std::__shared_ptr<A, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<A>>(std::_Sp_alloc_shared_tag<std::allocator<A> >) (shared_ptr_base.h:1342)
    ==1245671==    by 0x109CA4: std::shared_ptr<A>::shared_ptr<std::allocator<A>>(std::_Sp_alloc_shared_tag<std::allocator<A> >) (shared_ptr.h:409)
    ==1245671==    by 0x109AF5: std::shared_ptr<A> std::allocate_shared<A, std::allocator<A>>(std::allocator<A> const&) (shared_ptr.h:863)
    ==1245671==    by 0x109781: std::shared_ptr<A> std::make_shared<A>() (shared_ptr.h:879)
    ==1245671==    by 0x109290: main (leak4.cc:21)
    ==1245671== 
    ==1245671== LEAK SUMMARY:
    ==1245671==    definitely lost: 32 bytes in 1 blocks
    ==1245671==    indirectly lost: 32 bytes in 1 blocks
    ==1245671==      possibly lost: 0 bytes in 0 blocks
    ==1245671==    still reachable: 0 bytes in 0 blocks
    ==1245671==         suppressed: 0 bytes in 0 blocks
    ==1245671== 
    ==1245671== For lists of detected and suppressed errors, rerun with: -s
    ==1245671== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    
  • 分析

    • Heap Summary (堆内存总结):
      • in use at exit: 64 bytes in 2 blocks:程序退出时仍然有 64 字节内存未被释放。这表示程序创建了 2 个对象,但没有正确释放。
      • total heap usage: 4 allocs, 2 frees, 73,792 bytes allocated:程序进行了 4 次内存分配和 2 次内存释放,意味着有 2 次内存分配没有相应的释放。
    • 泄漏详细信息:
      • 64 (32 direct, 32 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2:内存泄漏发生在两个对象之间的循环引用中,其中一个对象(A)和另一个对象(B)互相引用,导致它们的内存无法被释放。
      • at 0x4849013: operator new(unsigned long):泄漏发生在 new 操作符的位置,即在内存分配时。
      • 该泄漏涉及的是 A 和 B 类的实例,它们通过直接和间接引用(循环引用)互相持有指针
  • 改进

    class B; // 前向声明
    
    class A {
    public:
        std::shared_ptr<B> b_ptr;
        ~A() { std::cout << "A destroyed\n"; }
    };
    
    class B {
    public:
        std::weak_ptr<A> a_ptr; // 用 weak_ptr 打破循环
        ~B() { std::cout << "B destroyed\n"; }
    };
    

4)进阶泄漏解决

1. 构造函数中隐式形成引用环(通过回调)
#include <iostream>
#include <memory>
#include <functional>

class CallbackOwner;

class Callback {
public:
    std::function<void()> func;
    ~Callback() { std::cout << "~Callback()\n"; }
};

class CallbackOwner : public std::enable_shared_from_this<CallbackOwner> {
public:
    std::shared_ptr<Callback> cb;

    void bind() {
        cb = std::make_shared<Callback>();
        cb->func = [self = shared_from_this()]() {
            std::cout << "Using CallbackOwner\n";
        };
    }   

    ~CallbackOwner() {
        std::cout << "~CallbackOwner()\n";
    }
};

int main() {
    auto owner = std::make_shared<CallbackOwner>();
    owner->bind();
    return 0;
}
  • valgrind输出

    Ubuntu ❯❯❯ valgrind --leak-check=full ./a.out
    ==1233846== Memcheck, a memory error detector
    ==1233846== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==1233846== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
    ==1233846== Command: ./a.out
    ==1233846== 
    ==1233846== 
    ==1233846== HEAP SUMMARY:
    ==1233846==     in use at exit: 112 bytes in 3 blocks
    ==1233846==   total heap usage: 4 allocs, 1 frees, 72,816 bytes allocated
    ==1233846== 
    ==1233846== 112 (48 direct, 64 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
    ==1233846==    at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==1233846==    by 0x10C832: __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<CallbackOwner, std::allocator<CallbackOwner>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (new_allocator.h:127)
    ==1233846==    by 0x10C464: std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<CallbackOwner, std::allocator<CallbackOwner>, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_ptr_inplace<CallbackOwner, std::allocator<CallbackOwner>, (__gnu_cxx::_Lock_policy)2> >&, unsigned long) (alloc_traits.h:464)
    ==1233846==    by 0x10C031: std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<CallbackOwner, std::allocator<CallbackOwner>, (__gnu_cxx::_Lock_policy)2> > > std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<CallbackOwner, std::allocator<CallbackOwner>, (__gnu_cxx::_Lock_policy)2> > >(std::allocator<std::_Sp_counted_ptr_inplace<CallbackOwner, std::allocator<CallbackOwner>, (__gnu_cxx::_Lock_policy)2> >&) (allocated_ptr.h:98)
    ==1233846==    by 0x10BAEB: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<CallbackOwner, std::allocator<CallbackOwner>>(CallbackOwner*&, std::_Sp_alloc_shared_tag<std::allocator<CallbackOwner> >) (shared_ptr_base.h:648)
    ==1233846==    by 0x10B83B: std::__shared_ptr<CallbackOwner, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<CallbackOwner>>(std::_Sp_alloc_shared_tag<std::allocator<CallbackOwner> >) (shared_ptr_base.h:1342)
    ==1233846==    by 0x10B45E: std::shared_ptr<CallbackOwner>::shared_ptr<std::allocator<CallbackOwner>>(std::_Sp_alloc_shared_tag<std::allocator<CallbackOwner> >) (shared_ptr.h:409)
    ==1233846==    by 0x10AF25: std::shared_ptr<CallbackOwner> std::allocate_shared<CallbackOwner, std::allocator<CallbackOwner>>(std::allocator<CallbackOwner> const&) (shared_ptr.h:863)
    ==1233846==    by 0x10AA59: std::shared_ptr<CallbackOwner> std::make_shared<CallbackOwner>() (shared_ptr.h:879)
    ==1233846==    by 0x10A2D0: main (leak6.cc:30)
    ==1233846== 
    ==1233846== LEAK SUMMARY:
    ==1233846==    definitely lost: 48 bytes in 1 blocks
    ==1233846==    indirectly lost: 64 bytes in 2 blocks
    ==1233846==      possibly lost: 0 bytes in 0 blocks
    ==1233846==    still reachable: 0 bytes in 0 blocks
    ==1233846==         suppressed: 0 bytes in 0 blocks
    ==1233846== 
    ==1233846== For lists of detected and suppressed errors, rerun with: -s
    ==1233846== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    
  • 分析

    • 从输出中可以看到,在程序退出时,存在 112 字节的内存没有被释放,并且 Valgrind 确认这是直接泄漏。泄漏的内存块包括了 CallbackOwner 和 Callback 对象的内存。特别是,在CallbackOwner 的内存分配过程中(具体在 shared_ptr 的分配阶段),我们发现内存的引用计数始终无法归零。

      在调用 shared_from_this() 获取 CallbackOwner 的共享指针时,CallbackOwner 会持有一个 Callback 的 shared_ptr,而 Callback 中的回调函数又持有 CallbackOwner 的 shared_ptr。这就形成了一个循环引用,使得这两个对象的引用计数始终无法减为 0,因此内存无法被释放。

    • 循环引用:CallbackOwner 和 Callback 对象相互持有 shared_ptr,形成了一个闭环。在 CallbackOwner 的构造函数中,bind 函数通过 shared_from_this() 获取 CallbackOwner 的共享指针并将其赋值给 Callback 的回调函数。而在回调函数中,Callback 又持有一个指向 CallbackOwner 的 shared_ptr,这就导致了 CallbackOwner 和 Callback 相互引用,永远不会释放内存。

    • 引用计数无法归零:由于循环引用的存在,CallbackOwner 和 Callback 对象的引用计数无法归零,导致它们的内存不会被释放,从而造成内存泄漏。

  • 解决

    void bind() {
        cb = std::make_shared<Callback>();
        // 使用weak_ptr防止循环引用
        std::weak_ptr<CallbackOwner> weak_self = shared_from_this();
        cb->func = [weak_self]() {
            std::cout << "Using CallbackOwner\n";
        };
    }
    
内存泄漏及解决方案 首先,需要了解浏览器自身的内存回收机制。每个浏览器会有自己的一套回收机制,当分配出去的内存不使用的时候便会回收;内存泄露的根本原因就是你的代码中分配了一些‘顽固的’内存,浏览器无法进行回收,如果这些’顽固的’内存还在一直不停地分配就会导致后面所用内存不足,造成泄露。 阅读详情

相关推荐

Java 中发生内存泄漏 5 个场景以及解决方法

前言 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和 Java 联系起来。在 Java 中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM 来处理。顾名思义,垃圾回收就是释放垃圾占用的空间,但垃圾回收器并不是万能的,它能够处理大部分场景下的内存清理、内存泄露以及内存优化。但它也并不是万能的,不然我们在项目实践过程中也不会出现那么多的内存泄漏的问题,很多的内存泄漏都是因为开发人员操作不当导致的。 本篇文章我们就来聊聊内存泄露的原因是什么,以及如何在

养歌的博客 1万+

Android之内存泄漏以及解决办法(持更)

Android之常见内存泄漏以及解决办法文章链接知识点: 单例造成的内存泄漏原因和解决方法; Handler造成的内存泄漏原因和解决方法; 耗时线程造成的内存泄漏原因和解决方法; 非静态内部类造成的内存泄漏原因和解决方法; WebView引起的内存泄漏原因和解决方法; 资源未关闭造成的内存泄漏原因和解决方法; 新名词记录{内存泄漏;LeakCanary:检测APP内存泄漏的第三方工具;} 概述内存泄

姚镜堂 8072

【C++】内存泄漏详解

内存泄漏内存泄漏是什么内存泄漏造成的后果内存泄漏的原因内存泄漏的解决方法 内存泄漏是什么 内存泄漏指的是由于疏忽或错误造成了程序未能释放掉不再使用的内存。 内存泄漏造成的后果 性能不良,内存会耗尽 内存泄漏的原因   对于C++的内存泄漏,总结一句话:就是new出来的内存没有通过delete合理的释放掉! 下面总结几种常见的内存泄漏的场景: 程序循环new创建出来的对象没有及时的delete掉,导致了内存的泄露 #include <iostream> #include <new.h&g

Vcrossover的博客 880

常见的【内存泄漏】姿势

关注公众号【高性能架构探索】,第一时间获取干货;回复【pdf】,免费获取计算机经典书籍 本文节选自文章: 内存泄漏-原因、避免以及定位 本文总结常见内存泄漏的几种方式,留意到这几点,可以避免95+%以上的内存泄漏 未释放 这种是很常见的,比如下面的代码: int fun() { char * pBuffer = malloc(sizeof(char)); /* Do some work */ return 0; } 上面代码是非常常见的内存泄漏场景(也可以使用new来.

高性能架构探索 7158

C++内存泄漏排查实战:Valgrind与AddressSanitizer工具详解

内存管理是C++编程的核心基础,其核心原理在于程序运行时对堆内存的分配与释放。不当的内存管理会导致内存泄漏,即已分配的内存无法被访问和释放,长期积累将耗尽系统资源,引发程序崩溃或性能下降。从技术价值看,高效的内存泄漏排查能显著提升软件稳定性与资源利用率,是现代软件开发中保障服务质量的必备技能。在应用场景上,无论是大型服务器后端、嵌入式系统还是高性能计算领域,系统性的内存问题排查都至关重要。本文聚焦于C++开发中两大主流工具——Valgrind和AddressSanitizer,通过对比其插桩模拟与编译时检测

weixin_26911099的博客 305

AD620单电源应变片测量电路分析

笔者最近在用AD620做应变片的压力检测电路,正好AD620的datasheet中有对于应变片压力测量的应用电路,参考该电路进行设计及实验后发现该电路存在些许问题,并对其进行了优化改进。本文纯属个人理解,供大家参考,欢迎批评指正。 AD620应用电路分析及优化 应用电路分析 AD620手册中原电路如下(加了部分标注以便分析): 该电路中,前半部分比较明显,为经典的AD620放大电路结构:四个应变...

拙诚电子工作室 1万+

json.cpp内存管理详解:如何避免常见的内存泄漏问题

在C++开发中,内存管理一直是开发者需要重点关注的问题。json.cpp作为一个高性能的JSON解析库,通过巧妙的内存管理设计,不仅实现了2-3倍的性能提升,还从根本上避免了常见的内存泄漏问题。本文将深入解析json.cpp的内存管理机制,帮助你理解如何在实际项目中避免内存泄漏。 ## 🔍 json.cpp内存管理架构概览 json.cpp采用了一种简洁而高效的内存管理策略,核心设计理念是"

gitblog_00943的博客 427

硬件笔记——HDMI接口

HDMI可以同时传输视频信号、音频信号和控制信号。其中高速信号有四组:从Data TX到Data RX有3组差分数据信号(TMDS_DATA0~2)和一组差分时钟信号(TMDS_CLOCK)。以及若干低速线。脚编号信号名称信号类型功能说明1热插拔检测连接状态检测。接收端通过此引脚向源端发送高电平信号,通知其已连接并可以开始读取EDID。2UTILITY车载用途悬空3TMDS数据通道2差分信号正极,用于传输视频像素、音频和数据包。4TMDS数据2屏蔽。

2501_94625908的博客 539

C++ 内存泄漏详解

内存泄漏是指程序在堆上动态分配的内存,在失去所有有效指针引用后,无法被释放并重新利用的现象。问题类型解决方案优先级忘记释放使用智能指针⭐⭐⭐异常泄漏RAII + 智能指针⭐⭐⭐循环引用weak_ptr打破循环⭐⭐⭐容器裸指针容器存智能指针⭐⭐⭐new/delete不匹配用make_unique替代⭐⭐⭐核心原则:让编译器帮你管理内存,而不是手动管理!// ❌ 旧式C++// ... 担心各种泄漏场景delete[] p;// ✅ 现代C++int。

专注于前沿技术的探索与实践,热衷于从代码逻辑、架构设计到行业应用等多维度深挖技术本质。期待在技术之路上与你共同成长~ 679

红米12C刷机避坑指南:NV数据损坏的修复与系统版本选择

本文提供了红米12C刷机过程中避免NV数据损坏的详细指南,涵盖系统版本选择、SP刷机工具使用和fastboot命令修复。重点分析了NV数据损坏的原因与表现,并给出通过工程固件和深度修复技巧解决基带未知、IMEI丢失等问题的实操方案,帮助用户安全完成刷机。

cuda7parallel的博客 1075

保姆级教程:在华为云Ubuntu服务器上快速搭建RAGflow开发环境(避坑指南)

本文提供了一份在华为云Ubuntu服务器上快速搭建RAGflow开发环境的保姆级教程。针对网络拉取镜像等常见问题,详细介绍了从服务器选型、系统优化、Docker配置到RAGflow部署与验证的全流程,并给出生产环境调优建议,帮助开发者高效避坑,快速构建企业级应用环境。

weixin_29250403的博客 86

【C++详解】C++ 智能指针:使用场景、实现原理与内存泄漏防治

C++ 智能指针:使用场景、实现原理与内存泄漏防治

2501_90265152的博客 2303

欢乐熊大话蓝牙知识16:蓝牙是怎么找设备的?扫描与广播的“对话内幕”

蓝牙是怎么找设备的?扫描与广播的对话技术介绍

hoony123的博客 1381

C++ - 内存泄漏篇详解01

C++ - 内存泄漏篇详解01 内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。 用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。 本文会结合案例讲解利用VStudio提供的_CrtDumpMemoryLeaks()函数进行内存泄漏检测学习 一、内存泄漏出现的常见情况简介 指针重新赋值:原内存变成孤立内存

君子之学必日新,日新者日进也。不日新者必日退,未有不进而不退者 924

AD9361 Fast Lock实战:Python脚本自动生成Profile寄存器配置(附完整代码)

本文详细介绍了利用Python脚本实现AD9361 Fast Lock Profile寄存器自动化配置的实战方案。通过解析频率合成公式、VCO约束及查表机制,脚本能根据目标频率一键生成完整的16进制寄存器序列,大幅提升射频系统开发中多频点切换的效率和准确性,并支持CSV、JSON、C头文件等多种输出格式,便于与FPGA/嵌入式系统集成。

weixin_29194693的博客 475

如何在飞腾平台上安装Ubuntu操作系统

注意: 若主板上配置的是NVME硬盘,在进行内核配置时,需要将内核的NEME驱动勾选上,否则移植内核时,无法找到文件系统,进不去操作系统。可以通过rufus软件将镜像做到U盘中,本次使用版本为Rufus 3.9.124 ,其他版本也可以。不要下载live版本的,没有准备网络环境,是一个头疼的事情,笔者暂时没有实现网络安装。将U盘接到主板usb口上,接上串口,显示器,,必需串口有打印才可以安装。可以通过dd命令,将iso安装到U盘。安装的是最小系统,缺少很多命令,需要联网更新,或则更换飞腾专用内核。

技术联盟 2645

C++ 内存泄漏详解:成因、检测与防范

根本原因:分配的内存丢失了所有指向它的指针,无法访问也无法释放。常见成因包括忘记释放、提前返回/异常跳过释放、指针被覆盖、循环引用、析构函数不正确等。检测方法Valgrind(Linux):最经典的内存泄漏检测工具(GCC/Clang):编译时插桩,精确报告泄漏位置Heaptrack:分析堆内存使用模式Visual Studio 诊断工具(Windows):集成内存分析防范策略(按优先级)首选 RAII:用类封装资源,构造时获取,析构时释放智能指针unique_ptr独占,shared_ptr。

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 1136

STM32开发 -- GPS模块开发详解

一、了解硬件 我使用的GPS模块型号为UBX-M8030 参看:UBX-M8030 系列 查看一下它的特性: 多用途 GNSS 芯片,提供三种产品等级 最多可并发接收 3 个 GNSS(GPS、伽利略、GLONASS、北斗) 行业领先的 -167 dBm 导航灵敏度 业界最低电流消耗 在城市峡谷中具有绝佳的定位精度 安全性和完整性保护 支持所有的卫星增强系统 车载级芯片的工作...

不积跬步,无以至千里 3万+

7 大类 30 多种 C++ 内存泄漏场景详解,建议收藏!

7 大类 30 多种 C++ 内存泄漏场景详解,建议收藏!

Henry313的博客 1105

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

C/C++内存管理及内存泄漏详解

栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

专注于前后端学习 1298

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

C/C++指针内存泄漏的原因详解及如何避免与问题定位

C/C++之所以复杂、效率高,是因为其灵活性,可用直接访问操作系统API,而正因为其灵活性,就很容易出问题,团队成员必须愿意按照一定的规则来进行开发,有完整的review机制,将问题暴露在上线之前。这样才可以把经历放在业务本身,而不是查找这些问题上,有时候往往一个小问题就能消耗很久的时间去定位解决,所以,一定要有一个良好的开发习惯。

huanxiajioabu的博客 2141

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

C++ & Linux 中 GDB 调试与内存泄漏检测详解

工具/功能GDB 调试Valgrindmtrace核心用途定位崩溃、逻辑错误全面内存检测(泄漏+错误)快速内存检测(泄漏+错误)简单内存泄漏检测编译要求需-g选项无(加-g更好)需需-g选项运行速度接近原程序慢(10-50倍)较快(2-5倍)接近原程序适用场景开发/调试阶段测试阶段开发/测试阶段简单程序/嵌入式环境最佳实践开发 C++ 程序时,编译时加,实时检测内存错误和泄漏。程序崩溃时,开启 core dump,用 GDB 分析core文件定位崩溃点。

XiaoBinbest的博客 1352

C++内存泄漏及检测工具详解

首先我们需要知道程序有没有内存泄露,然后定位到底是哪行代码出现内存泄露了,这样才能将其修复。最简单的方法当然是借助于专业的检测工具,比较有名如BoundsCheck,功能非常强大,相信做C++开发的人都离不开它。此外就是不使用任何工具,而是自己来实现对内存泄露的监控,分如下两种情况:一. 在 MFC 中检测内存泄漏假如是用MFC的程序的话,很简单。默认的就有内存泄露检测的功能。我们用VS2005生...

fanx021的博客 2105

C++ 自身函数详解及应用:智能指针如何管理内存泄漏?

提供了 unique_ptr 和 shared_ptr 两种智能指针类型。shared_ptr 允许共享所有权,核心函数包括 make_shared、get、use_count 和 reset。智能指针使内存管理更加健壮,通过合理选择 unique_ptr 或 shared_ptr 可编写出可靠高效的代码。在实际开发中,基于具体需求灵活选择 unique_ptr 或 shared_ptr,让代码更加可靠高效。C++ 标准库提供了两种主要的智能指针:unique_ptr 和 shared_ptr。

tbapi1980的博客 349

C++内存管理机制详解及常见内存泄漏问题排查方法

代码示例: cpp // 循环引用导致泄漏 struct Node { std::shared_ptr next;// 引用计数永远为1 }// 推荐替代方案 std::vector<int> safe_buffer(1024);堆区:通过new/malloc手动分配,需显式delete/free释放,支持动态扩展但易泄漏。异常安全:确保异常发生时资源仍能释放(推荐使用std::unique_ptr)

2509_93420891的博客 368
上一篇: 【CPP】学习路线
下一篇: 【形象解析】ptmalloc、tcmalloc与jemalloc对比
Raizeroko
博客等级 码龄6年 1913粉丝 27原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值