C/C++内存管理

C/C++内存分布

示例图:
在这里插入图片描述

栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
数据段:存放的是全局数据、与静态数据,全局数据又可分为已初始化的全局区(data)和未初始化的全局区(BSS);
代码段:可执行代码和只读常量(文字常量区);
内存映射段:文件映射、动态库、匿名映射

C语言内存开辟(堆上)的三大函数

面试题:malloc、calloc、realloc区别

三个函数的声明分别是:
void* realloc(void* ptr, unsigned newsize);
void* malloc(unsigned size);
void* calloc(size_t numElements, size_t sizeOfElement);
相同点:都在stdlib.h函数库内它们的返回值都是请求系统分配的地址,如果请求失败就返回NULL

**malloc用于申请一段新的地址,参数size为需要内存空间的长度,如:
char
p;
p=(char
)malloc(20);

calloc与malloc相似,参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数,如:
char* p;
p=(char*)calloc(20,sizeof(char));

realloc是给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度
例如:
char* p;
p=(char*)malloc(sizeof(char)20);
p=(char
)realloc(p,sizeof(char)*40);

注意,这里的空间长度都是以字节为单位。

C语言的标准内存分配函数:malloc,calloc,realloc,free等。
malloc与calloc的区别为1块与n块的区别:
malloc调用形式为(类型*)malloc(size):在内存的动态存储区中分配一块长度为“size”字节的连续区域,返回该区域的首地址。
calloc调用形式为(类型*)calloc(n,size):在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址。
realloc调用形式为(类型*)realloc(ptr,size):将ptr内存大小增大(减少)到size。类似于调整,可调大也可调小。
free的调用形式为free(void
ptr):释放ptr所指向的一块内存空间。

探究realloc底层实现。

realloc()函数指的是在原有内存的基础上进行操作,对该内存的大小进行扩容或者是减容,底层实现:
只是从代码层面,没有从底层说

void* my_realloc(void* des, size_t size)
{
	void* new_space = malloc(size);//1.申请空间
	memcpy(new_space, des, size);//2.对原空间size大小个数据复制,此时size为新开辟的空间大小
	free(des);//3释放原空间
	des = NULL;
	return new_space;//返回新开辟的空间


}

realloc(void*des,size_t size)会做四件事:
1.根据size申请一段新的空间
2.对原空间的内容拷贝原空间size个字节大小。(假如说缩容的话,则可能会造成数据丢失).
3.释放掉原空间(这点很关键),这块的realloc会有两种处理策略,一个是在原空间上进行操作(扩容或者减容)不去寻找新空间 ,第二种情况是在新空间中开辟一段空间。(因为这是操作系统做决定的,所以我们无法模拟出来)。
4.在开辟成功的基础上返回新开辟的空间首地址。

void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}

看一下这个需要free(p2)吗?答案是不需要的,因为根据上面那个realloc底层实现上来看,他会在封装realloc内部进行操作,free()掉原空间,所以此时入去再去free()的话就相当于对一块原空间free了两次(以上陈述建立在新空间中开辟)。

C++内存开辟与释放函数

内存开辟函数new与new[]操作符

new

new的底层实现:

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}

new什么时候会开辟失败?
内存不足的时候会开辟失败,此时它会进入上面的那个if(_callnewh(size)==0)这个判断语句,然后对内存中的资源进行回收,若回收到了的话分配,回收不到的话会抛出 bad_alloc类型异常
new的使用:

void test()
{
	int* p = new int;
	*p = 100;
	cout << *p << endl;
}
int main()
{
	test();
	system("pause");
	return 0;
}

在这里插入图片描述

new[]

底层相当于循环去调用malloc(),这个函数一般用来开辟数组
实现原理:
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申

2. 在申请的空间上执行N次构造函数

void* operator new[](size_t size=sizeof(type)*n);

void test()
{
	int* p = new int[10];
	for (int i = 0; i < 10; i++)
	{
		p[i] = i * 10;
	}
	for (int i = 0; i < 10; i++)
	{
		cout << p[i] << endl;
	}
}
int main()
{
	test();
	system("pause");
	return 0;
}

看底层
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

内存释放delete操作符

delete p;//释放p,释放单个变量

#define free(p) _free_dbg(p, _NORMAL_BLOCK)
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)//对传入指针判空
return;
_mlock(_HEAP_LOCK); /* block other threads */ //加锁,避免正在使用的释放
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );//可以看到底层实现是用free做的
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}

delete [] p ;//释放p所指向的数组空间
实现原理:
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

测试用例:

void test()
{
	int* p = new int;
	int* p1 = new int[10];
	delete p;
	delete[]p1;
}
int main()
{
	test();
	return 0;
}

c语言内存管理与C++内存管理的区别

1.C语言的三个在堆上的内存开辟函数的返回值需要进行强转(C++下),new的返回值不需要强转,因为在申请的时候编译器已经将其类型转好了
int *p=new int;//告诉编译器你要的是个int类型的数据。
2.C语言的开辟出来的空间的值除了calloc()函数之外,其与开辟出来空间的值皆为随机值。而C++的new可以初始化值。C++11中提出来的。

void test()
{
	int* p =(int *) malloc(sizeof(int) * 10);
	int* p1 = (int*)calloc(10, sizeof(int));
	//底下这个语句给初始值就从0号下标开始依次往下赋值,没有初始化的元素赋值为0;
	//若没有给初始值的话,比如 int *p2=new int[10];//此时里面的值全为随机值
	int* p2 = new int[10]{ 1 };
	free(p);
	free(p1);
	delete[]p2;

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

在这里插入图片描述
可以看到 malloc出现的数组的值全为随机值,calloc为0,new中因为我只给了1个初始化变量,所以除了第一个元素外,其余9个全为0.
3.C语言的内存开辟函数开辟出来的类对象无法自动调用类的构造函数。而new出来的对象可以。同时,free()自动调用类的析构函数,而delete可以,

class date
{
public:
	date()
	{
		year = 1900;
		month = 0;
		day = 0;
		cout << "构造函数调用" << endl;
	}
	~date()
	{
		cout << "析构函数调用" << endl;
	}
private:
	int year;
	int month;
	int day;
};
void test01()
{
	date* m1 = (date*)malloc(sizeof(date) * 5);
	cout << "free调用" << endl;
	free(m1);
	
}
void test02()
{
date* m2 = new date[5];
cout << "delete调用" << endl;
delete[]m2;
}
int main()
{
	cout << "malloc开辟" << endl;
	test01();
	cout << "new 开辟" << endl;
	test02();
	return 0;
}

在这里插入图片描述
4.如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
5.在自定义方法分析:首先看C语言的内存开辟函数,它只开辟了与对象同等大小的空间,但是它不是对象。C++提供的new开辟内存,干了两件事,第一件事开辟内存,第二件事调用构造函数,完成对类对象变量的初始化。它开辟出来的是一个对象。对应的delete会调用析构函数,若你有析构函数,则调用你自身的析构,若没有,则调用系统自动生成的析构。

在使用的时候必须对应使用

malloc()对应free() new对应delete new[]对象 delete[]
必须做到匹配使用
如果不匹配使用,轻则的话内存泄露,重则的话程序会崩溃。
1.我们试一下malloc开辟让delete去释放。

class student
{
public:
	student()
	{
		cout << "构造调用" << endl;
		age = 20;
		char* name = new char[20];
	}
	~student()
	{
		if (name)
		{
			delete[]name;
		}
		cout << "析构调用" << endl;
	}
private:
	int age;
	char* name;
};
void test()
{
	student* m1 = (student*)malloc(sizeof(student));
	if (m1 == NULL)
	{
		cout << "内存开辟失败,退出" << endl;
		return;
	}
	delete m1;
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述

2.用new开辟,free()释放

class student
{
public:
	student()
	{
		cout << "构造调用" << endl;
		age = 20;
		char* name = new char[20];
	}
	~student()
	{
		if (name)
		{
			delete[]name;
		}
		cout << "析构调用" << endl;
	}
private:
	int age;
	char* name;
};
void test()
{
	student* m1 = new student;
	free(m1);
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
3。new开辟,delete []释放

class student
{
public:
	student()
	{
		cout << "构造调用" << endl;
		age = 20;
		char* name = new char[20];
	}
	~student()
	{
		if (name)
		{
			delete[]name;
		}
		cout << "析构调用" << endl;
	}
private:
	int age;
	char* name;
};
void test()
{
	student* m1 = new student;
	delete[]m1;
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
4.new[]开辟,free释放,崩溃
5.new[]开辟,delete释放,崩溃
注:一定要匹配使用。不然可能要不出现内存泄露,要不然程序崩溃。

同一个类,自定义析构函数比不自定义析构函数多4字节?

前提:new[]开辟,开辟类数组
是因为

class student
{
public:
	student()
	{
		cout << "构造调用" << endl;
		age = 100;
		
	}
	~student()
	{
		
		cout << "析构调用" << endl;
	}
private:
	int age;
	
};
void test()
{
	//student* m1 = new student;
	//delete m1;
	student* m2 = new student[10];
	delete[]m2;
}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
为了对比:
此时我们注释掉显式定义出的析构函数
此时我们再来看内存

在这里插入图片描述

重载new与delete操作符

class student
{
public:
	student()
	{
		age = 12;
	}
	~student()
	{

	}
	void* operator new(size_t size)
	{
		return malloc(size);
	}
	void operator delete(void* p)
	{
		if (p)
		{
			free(p);
		}
	}
private:

	int age;
};
int main()
{
	student* m1 = new student;
	delete m1;
}

在这里插入图片描述
在这里插入图片描述
不过这样重载感觉很蠢,我们一般会在固定需求时使用。

malloc()

malloc(size)为C语言开辟内存函数,它一般开辟size个大小,那么它真的就占这么大吗?当然不是,因为你要管理该空间,管理是不是要存储这部分的起始地址,和结束地址,所以在C/C++中有一个结构体,专门管理这部分空间。

#define nNoMansLandSize 4
typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
   struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
   struct _CrtMemBlockHeader *pBlockHeaderPrev;
   char *szFileName;    // File name
   int nLine;                  // Line number
   size_t nDataSize;      // Size of user block
   int nBlockUse;         // Type of block
   long lRequest;          // Allocation number
// Buffer just before (lower than) the user's memory:
   unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;

这是管理malloc的结构体的,共占32个字节。
// Pointer to the block allocated just before this one:
struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
struct _CrtMemBlockHeader *pBlockHeaderPrev;
可以看到开辟的空间是用双向链表组织起来的。

定位new

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义
类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

class Test
{
public:
Test()
: _data(0)
{
cout<<"Test():"<<this<<endl;
}
~Test()
{
cout<<"~Test():"<<this<<endl;
}
private:
int _data;
};
void Test()
{
// pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
Test* pt = (Test*)malloc(sizeof(Test));
new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
}

new(pt) Test;没有去开辟空间,因为空间已经malloc出来了,只是让其去调用test的构造函数而已。

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
### 回答1: 在C/C++编程语言中,内存是一个非常重要的概念。内存是计算机用于存储和访问数据的地方,它可以被看作是一个巨大的存储器数组,每个元素都有一个独特的地址。 在C/C++中,我们可以使用指针来访问和操作内存。指针是一个特殊类型的变量,它存储了一个内存地址。通过指针,我们可以间接访问和修改内存中的数据。 当我们在程序中声明一个变量时,系统会为该变量分配一块内存空间,并将其地址存储在变量名中。我们可以通过使用变量名来访问和修改该内存空间中的值。 另外,我们可以使用动态内存分配函数来在运行时动态地分配内存。这在需要在程序中创建变量长度的数组或者临时存储空间时非常有用。动态内存分配函数包括malloc、calloc和realloc。在使用这些函数分配内存后,我们需要记得通过使用free函数来释放这些内存空间。 值得注意的是,C/C++中的内存管理是程序员的责任。这意味着我们在使用指针和动态内存分配函数时需要小心,以避免内存泄漏和悬挂指针等问题。我们需要确保我们在使用完内存后及时释放它,以避免浪费内存资源。 总结来说,C/C++中的内存是一个重要的概念,我们可以使用指针来访问和操作内存。通过动态内存分配函数,我们可以在程序运行时动态地分配内存。然而,我们也需要负责管理内存,以避免出现内存泄漏和悬挂指针等问题。 ### 回答2: C/C++中的内存填空题是指填写一段代码,完成特定的内存操作。以下是一个例子: ```c #include <stdio.h> int main() { int array[5]; // 声明一个包含5个整数的数组 int *p = array; // 声明一个指向数组首元素的指针 // 使用循环将数组中的元素赋值为0到4 for (int i = 0; i < 5; i++) { *(p + i) = i; } // 打印数组中的元素 for (int i = 0; i < 5; i++) { printf("%d ", array[i]); } return 0; } ``` 在这个例子中,我们声明了一个包含5个整数的数组`array`,然后使用指针`p`指向数组的首元素。接下来,通过循环遍历数组,利用指针`p`对数组元素进行赋值操作,赋值的值为数组下标。最后,再通过循环遍历数组,利用数组`array`打印出各个元素的值。这段代码展示了C/C++中的指针和数组的使用,以及对内存空间的操作。 ### 回答3: C/C++ 内存填空题一般涉及指针和内存管理的知识。下面给出一个例子以300字来回答: 以下是一道关于C/C++ 内存填空题的解答。 ```c #include <stdio.h> #include <stdlib.h> int main() { int* ptr = (int*)malloc(sizeof(int)); int* arr = (int*)calloc(5, sizeof(int)); *ptr = 10; for (int i = 0; i < 5; i++) { arr[i] = i; } printf("Ptr: %d\n", *ptr); printf("Arr: "); for (int i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf("\n"); free(ptr); free(arr); return 0; } ``` 上述代码中包含了两个关于内存的填空处,首先是通过`malloc(sizeof(int))`来分配存储 int 类型数据的内存空间,并将其地址赋值给`ptr`指针;另一个是通过`calloc(5, sizeof(int))`来分配存储 5 个 int 类型数据的连续内存空间,并将其地址赋值给`arr`指针。 接着通过`*ptr = 10`给指针 `ptr` 所指向的内存位置赋值为 10。并用一个 for 循环给数组 `arr` 赋值为 0 到 4。 最后通过`printf`打印结果。Ptr 输出为 10, Arr 输出为 0 1 2 3 4,表示内存填空处正确。 最后需要调用`free`函数手动释放内存,以避免内存泄漏。 在实际编程中,动态内存分配是一个常见的操作,合理地申请内存并及时释放内存对于提高程序的性能和效率十分重要。因此对于这类题目要熟悉`malloc`、`calloc`、`realloc`、`free`等函数的使用规则和注意事项,以及指针的正确使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值