【C++】深入了解C++内存管理

news2024/11/24 14:22:00

个人主页:救赎小恶魔

欢迎大家来到小恶魔频道

好久不见,甚是想念

今天我们要深入讲述类与对象的初始化列表以及隐式类型转换

目录

1.C++的内存分布

2.C/C++言中动态内存管理方式

1.C语言的管理方式

 2.C++的管理方式

new 

delete

3.operator new与operator delete函数

operator new

operator delete

4.new和delete的实现原理

new的实现原理

delete的实现原理

new T[N]的原理

 delete[]的原理

5.概念辨析

1.malloc/free和new/delete的区别

1. 内存泄漏的定义

2. 内存泄漏的原因

3. 内存泄漏的后果

4. 内存泄漏的检测与解决


引言:

当我们学习完类与对象,我们就要进一步学习C++,和C语言一样,我们学完了一些基础知识,就要了解他的内部管理,所以这一节讲解的是C++的内存管理机制

1.C++的内存分布

首先先看一下C++的内存分类图

数据段就是我们所说的全局变量,代码段是我们所说的常量区,我们需要重点关注的是堆区因为这部分是由我们自己控制的 

 

让我们做几个题去了解一下对内存分布的掌握

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
  static int staticVar = 1;
  int localVar = 1;
  int num1[10] = { 1, 2, 3, 4 };
  char char2[] = "abcd";
  const char* pChar3 = "abcd";
  int* ptr1 = (int*)malloc(sizeof(int) * 4);
  int* ptr2 = (int*)calloc(4, sizeof(int));
  int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
  free(ptr1);
  free(ptr3);
}

 讲解:

  1. 第一个globalVar,它是全局变量,全局变量和静态变量放到数据段(静态区),而常量放到常量区。所以是    C
  2. 第二个staticGlobalVar是静态变量,生命周期在函数之前就创建好了,同样的放到数据段(静态区)。所以也是       C
  3. 第三个staticVar是函数内的静态变量,同上存放到数据段(静态区),并且它的生命周期贯穿整个程序执行周期。所以也是         C
  4. 第四个localVar是局部变量,存放到栈上,是     A
  5. 第五个num1是局部变量,但是它是数组,存放到栈上。也就是        A
  6. 第六个char2根num1一样都是数组,是局部变量,尽管没有规定大小,但能观察到有五个字节,放到栈上。            A
  7. 第七个*char2,这个用图讲解一下,首先是字符串存放到了常量区,我们在栈上创建了数组,然后吧常量区的字符串复制到了数组在栈中开辟的空间里面,然后数组名就是首元素的地址,*char2就是a。                  A
  8. 第八个pChar3,这里的const修饰的是指向的内容,这里的pchar3是可以改变的,是局部指针变量,存储的是常量区的地址,存储在栈上。             A
  9. 第九个*pChar3,它指向的是首个字符串也就是a,所以在常量区 。    D
  10. 第十个ptr1,它是局部变量指针,存储在栈上    A
  11. 第十一个*ptr1,它是解引用了,利用malloc在堆上开辟了空间,解引用就指向了存储在堆数据的首地址,所以他在堆上。              B

所以,我们要想清楚它是解引用还是单独建立,是创建空间,还是指向空间中的首元素地址

当我们讨论变量存储在哪里时,通常涉及到几个关键区域:栈(Stack)、堆(Heap)、数据段(Data Segment,又称静态区)、和代码段(Code Segment,又称常量区)。每种类型的变量根据其特性和声明周期被存储在这些区域中的相应位置

  • 是用于存储局部变量、函数参数等的内存区域。当一个函数被调用时,其局部变量和一些书keeping信息被推入栈中;当函数执行完成,这些信息被从栈上弹出。栈是自动管理的,开发者无需手动分配或释放内存。
  • 是用于动态内存分配的内存区域。不同于栈,开发者需要显式地从堆上分配内存(如使用malloc或new),并在不再需要时释放这些内存(如使用free或delete)。
  • 数据段,又称为静态区,用于存储全局变量、静态变量等。这些变量的生命周期贯穿整个程序执行期,因此它们被存储在一个特定的、持久的内存区域中
  • 代码段,又称为常量区,用于存储程序的执行代码和常量数据,如字符串字面量。这部分内存是只读的,用来保证程序代码的安全性。

2.C/C++言中动态内存管理方式

1.C语言的管理方式

在C语言中,动态内存管理是通过一组标准库函数完成的,包括malloccallocrealloc, 和 free这些函数允许程序在运行时动态地分配、调整和释放堆内存,这是对于管理变化的数据量和大小特别有用的能力。下面是这些函数的基本用法和它们之间的区别:

malloc

  • 用法
    void* malloc(size_t size);
  • 功能:分配指定字节数的未初始化内存。它返回一个指向分配的内存的指针。如果分配失败,返回NULL
  • 示例
    #include <iostream>
    #include <cstdlib>
    using namespace std;
    
    int main() {
    
      // allocate memory of int size to an int pointer
      int* ptr = (int*) malloc(sizeof(int));
    
      // assign the value 5 to allocated memory
      *ptr = 5;
    
      cout << *ptr;
    
      return 0;
    }
    
    // Output: 5

calloc

  • 用法
    void* calloc(size_t num, size_t size);
  • 功能:为指定数量的元素分配内存,每个元素的大小也在参数中指定,并自动初始化所有位为0。如果分配失败,返回NULL
  • 示例
    #include <cstdlib>
    #include <iostream>
    using namespace std;
    
    int main() {
       int *ptr;
       ptr = (int *)calloc(5, sizeof(int));
       if (!ptr) {
          cout << "Memory Allocation Failed";
          exit(1);
       }
       cout << "Initializing values..." << endl
            << endl;
       for (int i = 0; i < 5; i++) {
          ptr[i] = i * 2 + 1;
       }
       cout << "Initialized values" << endl;
       for (int i = 0; i < 5; i++) {
          /* ptr[i] and *(ptr+i) can be used interchangeably */
          cout << *(ptr + i) << endl;
       }
       free(ptr);
       return 0;
    }

relloc

  • 用法
    void* realloc(void* ptr, size_t new_size);
  • 功能调整之前调用malloccalloc分配的内存块的大小。如果新的大小大于原始大小,可能会移动内存块到新的位置以提供足够的连续空间。如果realloc的第一个参数是NULL,它的行为就像malloc
  • 示例
    #include <iostream>
    #include <cstdlib>
    using namespace std;
    int main()
    {
    	float *ptr, *new_ptr;
    	ptr = (float*) malloc(5*sizeof(float));
    	if(ptr==NULL)
    	{
    		cout << "Memory Allocation Failed";
    		exit(1);
    	}
    
    	/* Initializing memory block */
    	for (int i=0; i<5; i++)
    	{
    		ptr[i] = i*1.5;
    	}
    
    	/* reallocating memory */
    	new_ptr = (float*) realloc(ptr, 10*sizeof(float));
    	if(new_ptr==NULL)
    	{
    		cout << "Memory Re-allocation Failed";
    		exit(1);
    	}
    	
    	/* Initializing re-allocated memory block */
    	for (int i=5; i<10; i++)
    	{
    		new_ptr[i] = i*2.5;
    	}
    	cout << "Printing Values" << endl;
    	
    	for (int i=0; i<10; i++)
    	{
    		cout << new_ptr[i] << endl;
    	}
    	free(new_ptr);
    	
    	return 0;
    }

free

  • 用法
    void free(void* ptr);

  • 功能:释放之前通过malloccalloc, 或 realloc分配的内存。一旦内存被释放,那块内存就不能再被访问了。
  • 注意
    #include <iostream>
    #include <cstdlib>
    using namespace std;
    
    int main()
    {
    	int *ptr;
    	ptr = (int*) malloc(5*sizeof(int));
    	cout << "Enter 5 integers" << endl;
    
    	for (int i=0; i<5; i++)
    	{
    	// *(ptr+i) can be replaced by ptr[i]
    		cin >> *(ptr+i);
    	}
    	cout << endl << "User entered value"<< endl;
    
    	for (int i=0; i<5; i++)
    	{
    		cout << *(ptr+i) << " ";
    	}
    	free(ptr);
    
    	/* prints a garbage value after ptr is free */
    	cout << "Garbage Value" << endl;
    
    	for (int i=0; i<5; i++)
    	{
    		cout << *(ptr+i) << " ";
    	}
    	return 0;
    }

 2.C++的管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理

new 

在C++中,new是一个操作符,用于在堆(heap)上动态分配内存。它允许程序在运行时根据需要创建对象,而不是在编译时确定所有对象的内存需求。new操作符返回指向新分配的内存的指针,这个指针可以被用来访问和操作该内存区域。

Type* variable = new Type(arguments);

  • Type:要分配的对象类型
  • variable:指向分配的内存的指针
  • arguments:传递给构造函数的参数(如果需要的话)

例子1

int* p = new int;  // 在堆上分配一个整数大小的内存,并返回指向它的指针  
*p = 10;           // 通过指针设置该内存区域的值  
// ... 使用p指向的内存 ...  
delete p;          // 释放内存,防止内存泄漏

在这个例子中,new int会在堆上分配足够的内存来存储一个整数,并返回指向这块内存的指针。然后,我们可以使用这个指针来访问和操作这块内存。

例子2:

int* arr = new int[10];  // 在堆上分配一个包含10个整数的数组  
// ... 使用数组 ...  
delete[] arr;            // 释放数组内存

这个是使用new来动态分配数组,最后用delete去释放。

当使用new[]分配数组时,必须使用对应的delete[]来释放内存。使用错误的delete形式也是未定义行为。

注意事项:

  1. 内存泄漏使用new分配的内存必须显式释放,否则会导致内存泄漏。你可以使用delete(对于单个对象)或delete[](对于数组)来释放内存。
  2. 异常处理:当使用new分配大量内存或系统内存不足时,可能会抛出std::bad_alloc异常。因此,最好将内存分配放在try-catch块中以处理可能的异常。
  3. 初始化使用new分配的内存不会自动初始化。如果你需要初始化内存,可以使用构造函数、初始化列表或使用std::memset等函数。
  4. 智能指针:在现代C++中,推荐使用智能指针(如std::unique_ptrstd::shared_ptr)来管理动态分配的内存。这些智能指针会在适当的时候自动释放内存,从而减少内存泄漏的风险。
  5. 内存碎片频繁地使用newdelete可能会导致内存碎片,这是堆内存管理的一个常见问题。合理规划和设计内存使用策略可以帮助减少内存碎片的影响。
  6. 性能考虑:动态内存分配通常比栈内存分配慢,因为涉及系统调用和可能的内存页错误等。因此,在性能关键的应用中,应谨慎使用动态内存分配。

delete

在C++中,delete操作符用于释放之前使用new操作符在堆上动态分配的内存。这是防止内存泄漏的关键步骤,因为堆上的内存不会自动被回收,必须由程序员显式地管理

 例子1:当你使用new分配了一块内存后,你应该在不再需要这块内存时使用delete来释放它。

int* p = new int;  // 动态分配一个整数大小的内存  
// ... 使用这块内存 ...  
delete p;          // 释放内存

在上面的代码中,new int分配了一个整数的内存,并且delete p释放了这块内存。一旦内存被释放,指针p就变成了悬挂指针(dangling pointer),意味着它指向的内存已经被释放,不再有效。为了避免悬挂指针导致的问题,通常在delete之后将指针设置为nullptr

delete p;  
p = nullptr;

例子2:如果你使用new[]分配了一个数组,你需要使用delete[]来释放它。

int* arr = new int[10];  // 动态分配一个包含10个整数的数组  
// ... 使用数组 ...  
delete[] arr;           // 释放数组内存

注意事项

  1. 不要重复删除:已经被deletedelete[]的内存不应该再次被删除,否则会导致未定义行为。

  2. 悬挂指针:使用deletedelete[]后,相关的指针不会自动置为nullptr,因此你需要手动将其设置为nullptr以避免悬挂指针问题。

  3. 匹配newdelete:如果你使用new分配内存,就应该使用delete来释放;如果你使用new[]分配数组,就应该使用delete[]来释放。不匹配的使用可能导致未定义行为。

  4. 内存泄漏:如果你忘记释放使用newnew[]分配的内存,那么就会发生内存泄漏,即程序占用的内存会不断增长,直到耗尽系统资源。

  5. 析构函数:当使用deletedelete[]时,会调用对象的析构函数。确保析构函数正确实现,以避免资源泄漏或其他问题。

  6. 异常安全性:在复杂的程序中,内存管理可能涉及异常处理。确保在可能抛出异常的代码中正确处理内存分配和释放,以避免内存泄漏。

例如:

class MyClass {  
public:  
    MyClass() { /* 构造函数代码 */ }  
    ~MyClass() { /* 析构函数代码 */ }  
    // ... 其他成员函数 ...  
};  
  
int main() {  
    MyClass* obj = new MyClass();  // 分配MyClass对象  
    // ... 使用obj ...  
    delete obj;  // 释放内存  
    obj = nullptr;  // 避免悬挂指针  
    return 0;  
}

在这个例子中,我们创建了一个MyClass的动态实例,并在使用完毕后使用delete释放了内存,并将指针设置为nullptr以避免悬挂指针问题。

3.operator new与operator delete函数

在C++中,operator newoperator delete两个特殊的操作符函数,它们用于动态内存的分配和释放。这些操作符通常在底层被newdelete表达式所调用。不过,程序员也可以重载这些操作符来自定义内存管理的行为。

operator new

operator new用于动态分配内存。当你使用new关键字创建一个对象时,operator new会被调用以分配所需的内存。默认的operator new实现会调用C语言的malloc函数或类似机制来分配内存。

你可以重载operator new以提供自定义的内存分配机制。例如,你可能希望跟踪内存分配、实现特定的内存池管理,或者为特定类型的对象提供特殊的分配策略。

 重载operator new的基本形式如下:

void* operator new(size_t size) {  
    // 自定义的内存分配逻辑  
    // 返回一个指向新分配内存的指针  
}  
  
// 也可以重载数组版本的 operator new[]  
void* operator new[](size_t size) {  
    // 自定义的内存分配逻辑  
    // 返回一个指向新分配内存的指针  
}

operator delete

operator delete用于释放由operator new分配的内存。当你使用delete关键字释放一个对象时,operator delete会被调用。默认的operator delete实现会调用C语言的free函数或类似机制来释放内存。

operator new类似,你也可以重载operator delete以提供自定义的内存释放机制

重载operator delete的基本形式如下:

void operator delete(void* ptr) noexcept {  
    // 自定义的内存释放逻辑  
    // ptr 指向要释放的内存块  
}  
  
// 也可以重载数组版本的 operator delete[]  
void operator delete[](void* ptr) noexcept {  
    // 自定义的内存释放逻辑  
    // ptr 指向要释放的内存块  
}

注意事项

  1. 重载全局版本:你可以在全局范围内重载operator newoperator delete,这将影响所有类型的内存分配和释放。

  2. 重载类特定版本:你也可以在类内部重载这些操作符,以为该类的实例提供特定的内存管理行为。类特定的重载将优先于全局重载。

  3. 异常安全性operator new在无法分配内存时应该抛出一个std::bad_alloc异常,除非它是一个不抛出的版本(nothrow版本)。operator delete通常不应该抛出异常,并且应该被标记为noexcept

  4. 内存对齐在重载operator new时,需要考虑对象的内存对齐要求。C++17引入了std::align_val_tstd::aligned_alloc等工具来帮助处理对齐的内存分配。

  5. 与C语言的互操作性:如果你正在与C语言的库互操作,可能需要确保你的自定义内存管理函数与C语言的mallocfree等函数兼容

  6. 性能考虑:自定义的内存管理可以提供更好的性能,但也可能引入额外的复杂性和开销。务必谨慎评估是否真的需要自定义这些操作符。

4.new和delete的实现原理

在C++中,newdelete是操作符,它们用于动态内存管理。了解newdelete的实现原理有助于我们更好地理解它们的行为和如何高效地使用它们。虽然具体的实现可能因编译器和库的不同而有所差异,但以下是一个通用的概述:

new的实现原理

  1. 内存分配
    • new操作符首先会计算所需内存的大小,这通常基于所创建对象的类型。
    • 然后,它会调用内存分配函数来分配足够大小的内存块。在标准的C++库中,这个内存分配函数通常是operator new它可以被重载以提供自定义的内存分配机制。
    • operator new最终会调用底层的系统调用,如Unix/Linux系统中的sbrkmmap,或在Windows中的VirtualAlloc,来从操作系统获取内存。
  2. 对象构造
    • 一旦内存被成功分配,new操作符会调用对象的构造函数来初始化这块内存中的对象。
    • 构造函数的调用确保了对象在内存中的正确初始化,包括成员变量的初始化和可能执行的任何其他初始化逻辑。
  3. 返回指针
    • 最后,new操作符返回一个指向新分配并初始化的对象的指针。

拿栈来举例:

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = new A(1);
	delete p1;
	return 0;
}

delete的实现原理

  1. 对象析构
    • 当使用delete操作符时,它首先会调用对象的析构函数。
    • 析构函数负责执行任何必要的清理工作,如释放资源、关闭文件句柄等。
    • 析构函数的正确实现对于防止资源泄漏至关重要。
  2. 内存释放
    • 一旦对象被析构,delete操作符会调用内存释放函数来释放对象占用的内存。在C++库中,这个内存释放函数通常是operator delete,它也可以被重载。
    • operator delete最终会调用底层的系统调用来将内存返回给操作系统,如Unix/Linux中的munmap或在Windows中的VirtualFree
  3. 指针失效
    • 在内存被释放后,之前指向该内存的指针将变为悬挂指针(dangling pointer)。这意味着指针仍然指向之前的内存地址,但该地址上的内容可能已经被操作系统用于其他目的。
    • 为了避免悬挂指针导致的问题,通常在delete之后将指针设置为nullptr

 

class Stack
{
public:
	Stack()
	{
		_a = (int*)malloc(sizeof(int) * 4);
		_top = 0;
		_capacity = 4;
	}

	~Stack()
	{
		free(_a);
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack* pst = new Stack;
	delete pst;
	return 0;
}

我们就很清楚的能看到,现需要调用析构函数再进行释放 

注意事项:

  • newdelete应该成对出现,以避免内存泄漏。每个使用new分配的内存块都应该使用delete来释放。
  • 重载operator newoperator delete时需要谨慎,以确保与现有的内存管理机制兼容并避免引入内存泄漏或其他问题。
  • 在多线程环境中使用newdelete时需要特别注意线程安全,以避免竞态条件和数据损坏。
  • 在使用智能指针(如std::unique_ptrstd::shared_ptr)时,内存的分配和释放会被自动管理,这有助于减少因手动管理内存而导致的错误。

new T[N]的原理

在C++中,new T[N]用于动态地分配一个类型为T的数组,其中包含N个元素。这个表达式的原理涉及内存分配、对象构造和数组的管理。

以下是new T[N]操作的大致原理:

  1. 内存分配
    • 首先,new T[N]会计算需要分配的内存总量。这个总量是N乘以类型T的大小(sizeof(T)),可能还需要加上一些额外的内存用于存储数组的大小或其他元数据(这取决于编译器的具体实现)。
    • 接下来,会调用operator new[]来分配所需的内存块。这个操作符可以像operator new一样被重载,以提供自定义的内存分配机制。默认情况下,它会使用堆内存来分配所需的空间。
  2. 对象构造
    • 与单个对象的new表达式不同,new T[N]在分配内存后不会自动调用每个数组元素的构造函数相反,它通常会为内置类型(如intchar等)或者没有明确构造函数(或默认构造函数)的类类型分配“原始”内存。
    • 如果类型T有非平凡的构造函数(即不是默认构造函数或者拷贝/移动构造函数),那么编译器会生成代码来逐个调用数组中每个对象的构造函数。这通常是通过一个循环来实现的,循环N次,每次调用一个对象的构造函数。
  3. 返回指针
    • 一旦内存分配(和可能的对象构造)完成,new T[N]会返回一个指向数组第一个元素的指针。这个指针的类型是T*,可以用来访问和操作数组中的元素。
  4. 数组析构与内存释放
    • 当数组不再需要时,应该使用delete[]操作符来释放内存。这个操作符会确保数组中每个对象的析构函数都被调用(如果类型T有析构函数的话),然后释放整个数组占用的内存。
    • 使用delete[]而不是delete来释放通过new T[N]分配的数组是非常重要的,因为delete不会正确地调用数组中每个对象的析构函数,也不会释放整个数组的内存

需要注意的是,如果类型T有非平凡的构造函数或析构函数,那么使用new T[N]delete[]可能会比使用原始内存分配函数(如mallocfree)更加高效和安全因为编译器会自动处理对象的构造和析构。然而,这也会带来一些额外的开销,因为需要为每个对象调用构造函数和析构函数。

 delete[]的原理

delete[]是C++中的一个操作符,主要用于释放通过new[]分配的动态数组内存。它的工作原理主要涉及到内存的回收和析构函数的调用。

在C++中,使用new[]操作符可以为数组动态分配内存。例如,int* arr = new int[10];会在堆上分配一个包含10个整数的数组,并返回指向它的指针。当这个数组不再需要时,我们必须显式地释放这块内存,以防止内存泄漏。这时,我们就可以使用delete[]操作符,如delete[] arr;

delete[]的工作原理大致如下:

  1. 析构函数调用对于类类型的数组,delete[]会依次调用数组中每个对象的析构函数。这是为了确保每个对象都能正确地清理其资源。对于内置类型(如int、float等),这一步会被跳过,因为内置类型没有析构函数。
  2. 内存释放在析构函数调用完成后,delete[]会释放数组所占用的内存,将其返回给操作系统。这一步对于所有类型的数组都是必须的,无论是类类型还是内置类型。

需要注意的是,使用delete[]时必须确保指针是有效的,并且指向的是由new[]分配的内存如果指针无效,或者指向的不是由new[]分配的内存,那么使用delete[]可能会导致未定义的行为,包括但不限于程序崩溃、数据损坏等。

另外,如果你使用new[]分配的内存被delete(而不是delete[])释放,或者反过来,都可能导致内存泄漏、数据损坏或其他不可预知的问题。因此,一定要确保new[]delete[]成对出现,newdelete成对出现。

5.概念辨析

1.malloc/free和new/delete的区别

mallocfreenewdelete是两对用于内存管理的函数/操作符,它们分别来自C语言和C++语言,并各自承载着不同的特性和用途。以下是它们之间的主要区别:

  1. 来源与语言
    • malloc 和 free 来自C语言的标准库 <stdlib.h>(或 <cstdlib> 在C++中)。
    • new 和 delete 是C++的操作符,是C++语言内建的一部分。
  2. 初始化
    • malloc 仅仅分配内存,不会对内存进行初始化返回的是指向分配内存的指针,若分配成功,则返回非空指针,否则返回NULL。
    • new 不仅分配内存,还会调用对象的构造函数来初始化内存中的对象如果分配成功,new 返回的是指向新创建对象的指针;如果失败,则抛出 std::bad_alloc 异常。
  3. 释放内存
    • free 释放之前通过 malloc(或相关函数如 callocrealloc)分配的内存它不会调用析构函数。
    • delete 释放之前通过 new 分配的内存并且会调用对象的析构函数。
  4. 错误处理
    • malloc 在内存分配失败时返回NULL,需要程序员手动检查并处理。
    • new 在内存分配失败时会抛出异常,这可以使用try-catch块来处理。
  5. 类型安全
    • malloc 和 free 是基于C语言的,因此不具备类型安全。返回的是 void* 类型,需要显式地转换为适当的指针类型。
    • new 和 delete 是类型安全的,它们自动处理指针类型,无需显式转换。
  6. 构造函数和析构函数调用
    • malloc 和 free 不关心对象的构造函数和析构函数
    • new 在分配内存后会调用构造函数,delete 在释放内存前会调用析构函数。
  7. 分配数组
    • 对于C语言,如果要分配数组,通常使用 malloc 分配足够的内存,然后可能需要手动进行初始化。
    • C++中的 new[] 可以用来分配对象数组,并自动调用每个对象的构造函数。相应地,delete[] 用来释放数组,并会调用每个对象的析构函数
  8. 内存对齐
    • malloc 仅仅考虑内存分配,而不考虑对象的对齐要求(尽管许多现代的实现会考虑平台特定的对齐)。
    • new 操作符会确保对象按照其类型所需的对齐进行分配。
  9. 可移植性与兼容性
    • malloc 和 free 是C语言标准库的一部分,因此在跨平台或混合语言编程环境中可能更受欢迎。
    • new 和 delete 是C++特有的,提供了更好的类型安全和构造/析构语义。

总的来说,malloc/free 和 new/delete 在功能上有所不同,主要是因为C++引入了面向对象的概念,并需要在内存管理中考虑对象的生命周期和构造/析构过程。在C++中,推荐使用 new 和 delete,因为它们提供了更高级别的抽象和安全性。然而,在某些情况下,比如在与C语言交互或进行底层内存管理时,malloc 和 free 可能仍然是有用的。

2.内存泄露

内存泄漏(Memory Leak)是计算机科学中的一个概念,指的是在程序运行过程中,已经动态分配的内存由于某种原因未能被程序释放,从而造成系统内存的浪费。这种现象会导致程序运行速度减慢,甚至可能引发系统崩溃等严重后果。以下是对内存泄漏的详细讲解:

1. 内存泄漏的定义

内存泄漏通常发生在程序使用动态分配(如C/C++中的malloccallocreallocnew等函数)为变量或数据结构分配堆内存,但在使用完毕后没有通过相应的函数(如freedelete)来释放这部分内存时。这样,这部分内存就无法被操作系统回收再利用,造成了内存资源的浪费。

2. 内存泄漏的原因

内存泄漏的原因有多种,包括但不限于:

  • 数据量过于庞大,导致内存占用持续增加。
  • 程序中存在死循环,不断申请新的内存而未释放。
  • 静态变量和静态方法过多,占用了大量内存。
  • 递归调用过深,可能导致栈内存溢出。
  • 无法确定某些对象是否仍被引用,从而无法安全地释放这些对象占用的内存。
  • 虚拟机或垃圾回收机制未能有效回收不再使用的内存。

3. 内存泄漏的后果

内存泄漏的后果可能包括:

  • 性能下降:随着内存泄漏的积累,可用内存逐渐减少,导致程序运行速度变慢。
  • 系统崩溃:在极端情况下,如果内存泄漏过多,可能导致系统没有足够的内存资源分配给其他进程或任务,从而引发系统崩溃。
  • 资源浪费:泄漏的内存无法被系统回收再利用,造成了宝贵的内存资源的浪费。

4. 内存泄漏的检测与解决

为了检测和解决内存泄漏问题,可以采取以下措施:

  • 代码审查:通过仔细审查代码逻辑,确保所有动态分配的内存都在使用完毕后被正确释放。
  • 使用工具检测:利用专门的内存泄漏检测工具(如Valgrind、Dmalloc等)来帮助发现潜在的内存泄漏问题。
  • 优化数据结构:合理设计数据结构,避免循环引用等情况的发生,从而减少内存泄漏的风险。
  • 及时释放内存:在程序中及时释放不再使用的内存资源,避免内存泄漏的积累。

总之,内存泄漏是一个需要高度重视的问题。通过提高编程水平、加强代码审查和测试、以及利用专业工具进行检测和调试等措施,我们可以有效地预防和解决内存泄漏问题,确保程序的稳定运行和性能表现。

OK,今天的内容讲解到这里

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1634084.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何删除BigKey

1.2.3、如何删除BigKey BigKey内存占用较多&#xff0c;即便时删除这样的key也需要耗费很长时间&#xff0c;导致Redis主线程阻塞&#xff0c;引发一系列问题。 redis 3.0 及以下版本 如果是集合类型&#xff0c;则遍历BigKey的元素&#xff0c;先逐个删除子元素&#xff0c;…

为什么选择OpenNJet?OpenNJet下一代云原生应用引擎!OpenNJet开发实战!

前言导读 在当今这个数字化转型加速的时代&#xff0c;云原生技术已成为企业和开发者构建现代应用的首选路径。OpenNJet作为新一代云原生应用引擎&#xff0c;在国内外技术社区受到了广泛关注。 本文将深入探讨OpenNJet的特点、优势以及在开发实践中的应用&#xff0c;带您全…

深度学习基础之《TensorFlow框架(16)—神经网络案例》

一、mnist手写数字识别 1、数据集介绍 mnist数据集是一个经典的数据集&#xff0c;其中包括70000个样本&#xff0c;包括60000个训练样本和10000个测试样本 2、下载地址&#xff1a;http://yann.lecun.com/exdb/mnist/ 3、文件说明 train-images-idx3-ubyte.gz: training s…

vscode 配置与插件记录

vscode插件 python PythonPython DebuggerruffisortPylanceJupyterJupyter KeymapJupyter Slide ShowJupyter Cell TagsautoDocstring - Python Docstring Generator ruff isort pylance autodocsting 在setting.json里这么配置&#xff0c;这样你保存时就会自动format…

【酱浦菌-爬虫项目】爬取学术堂论文信息

1. 首先&#xff0c;代码定义了一个名为 url 的变量&#xff0c;它是一个包含三个网址的集合&#xff08;或者说是一个集合的字典&#xff09;。这些网址分别是&#xff1a; - ‘http://www.xueshut.com/lwtimu/127966.html’ - ‘http://www.xueshut.com/lwtimu/12…

您可知道如何通过`HTTP2`实现TCP的内网穿透???

可能有人很疑惑应用层 转发传输层&#xff1f;&#xff0c;为什么会有这样的需求啊&#xff1f;&#xff1f;&#xff1f;哈哈技术无所不用其极&#xff0c;由于一些场景下&#xff0c;对于一个服务器存在某一个内部网站中&#xff0c;但是对于这个服务器它没有访问外网的权限&…

《ElementPlus 与 ElementUI 差异集合》el-dialog 显示属性有差异

ElementPlus 用属性 v-model ElementUI 用属性 visible 其实也是 Vue2/Vue3 的差异&#xff1a;v-model 指令在组件上的使用已经被重新设计&#xff0c;替换掉了 v-bind.sync

新质生产力实践,我用chatgpt开发网站

是的&#xff0c;我用chatgpt开发了一个网站&#xff0c;很轻松。 我之前一点不懂前端&#xff0c;也没有网站开发的代码基础&#xff0c;纯正的0基础。 从0开始到最后成品上线&#xff0c;时间总计起来大致一共花了2-3周的时间。 初始想法我是想给我公司开发一个网站&#…

C# wpf 运行时替换方法实现mvvm自动触发刷新

文章目录 前言一、如何实现&#xff1f;1、反射获取属性2、定义替换方法3、交换属性的setter方法 二、完整代码1、接口2、项目 三、使用示例1、倒计时&#xff08;1&#xff09;、继承ViewModelBase&#xff08;2&#xff09;、定义属性&#xff08;3&#xff09;、属性赋值&am…

【Redis 开发】Lua语言

Lua Lua语法 Lua语法 Lua是一种小巧的脚本语言&#xff0c;底层用C语言实现&#xff0c;为了嵌入式应用程序中 官网&#xff1a;https://www.lua.org/ 创建lua文件 touch hello.lua 运行lua文件 lua hello.lua 输出语句 print("Hello World!")数据类型 可以通过t…

python与上位机开发day04

模块和包、异常、PyQt5 一、模块和包 1.1 模块 Python中模块就是一个.py文件&#xff0c;模块中可以定义函数&#xff0c;变量&#xff0c;类。模块可以被其他模块引用 1.1.1 导入模块 """ 导入格式1&#xff1a; import 模块名 使用格式&#xff1a; …

【算法基础实验】图论-最小生成树Prim的延迟实现

最小生成树-Prim的延迟实现 理论基础 树的基本性质 用一条边连接树中的任意两个顶点都会产生一个新的环&#xff1b; 从树中删去一条边将会得到两棵独立的树。 切分定理的定义 定义。图的一种切分是将图的所有顶点分为两个非空且不重叠的两个集合。横切边 是一条连接两个属…

认识认识DHCP

文章目录 认识认识DHCP一、什么是DHCP&#xff1f;1.1、为什么要使用DHCP&#xff1f;1.2、DHCP是怎么工作的&#xff1f;1.2.1、客户端首次接入网络的工作原理1.2.2、客户端重用曾经使用过的地址的工作原理1.2.3、客户端更新租期的工作原理 二、配置DHCP Server&#xff0c;为…

嵌入式开发二:搭建开发环境

工欲善其事必先利其器&#xff0c;本节我们从嵌入式开发的搭建环境开始学习&#xff0c;掌握最基本的搭建环境技能&#xff0c;了解每一部分的作用&#xff0c;以及如何使用是关键所在&#xff01; 目录 一、常用开发工具简介 二、如何安装KEIL5(MDK) 2.1认识MDK-ARM 2.2 …

Spring基于AspectJ实现验签切点

文章目录 引言I AspectJ 依赖II 验签切点2.1 匹配方法执行的连接点2.2 设置带有CustomAnnotation注解的方法为切点III 案例:验签2.1 用法2.2 定义注解2.3 定义切面和切点引言 需求:验签 实现:基于AspectJ实现验签切点 I AspectJ 依赖 AspectJ 是一个基于 Java 语言的 AOP …

windos蓝屏分析工具

BlueScreenView中文版 - Windows蓝屏分析工具 BlueScreenView中文版是一款非常方便的蓝屏诊断工具。它可以帮助您快速定位蓝屏问题&#xff0c;并提供详细的故障转储信息。该软件可以自动扫描您机器上的minidump文件夹&#xff0c;同时还支持根据路径查找蓝屏文件。 windos发…

Coursera: An Introduction to American Law 学习笔记 Week 06: Civil Procedure (完结)

An Introduction to American Law Course Certificate Course Introduction 本文是 https://www.coursera.org/programs/career-training-for-nevadans-k7yhc/learn/american-law 这门课的学习笔记。 文章目录 An Introduction to American LawInstructors Week 06: Civil Pro…

Flink checkpoint 源码分析

序言 最近因为工作需要在阅读flink checkpoint处理机制&#xff0c;学习的过程中记录下来&#xff0c;并分享给大家。也算是学习并记录。 目前公司使用的flink版本为1.11。因此以下的分析都是基于1.11版本来的。 在分享前可以简单对flink checkpoint机制做一个大致的了解。 …

docker学习笔记3:VmWare CentOS7安装与静态ip配置

文章目录 一、安装CentOS71、下载centos镜像2、安装二、设置静态ip三、xshell连接centos本专栏的docker环境是在centos7里安装,因此首先需要会安装centos虚拟机。 本篇博客介绍如何在vm虚拟机里安装centos7。 一、安装CentOS7 1、下载centos镜像 推荐清华源,下载如下版本 …

【C++】学习笔记——string_1

文章目录 四、模板初阶2. 类模板 五、STL简介1. 什么是STL2. STL的六大组件3. 如何学习STL 六、string类1. string类对象的容量操作 未完待续 四、模板初阶 2. 类模板 函数模板就是&#xff1a;模板 函数&#xff1b;类模板就是&#xff1a;模板 类。和函数模板用法基本相同…