C语言
C语言内存分配回顾
我们先回顾一下C语言的内存分配:
解析:
int globalVar = 1;
在main外部,将
globalVar
定义在了全局,放在了C静态区。
static int staticGlobalVar = 1;
在main外部,也是将
staticglobalVar
定义在了全局,也存储在C静态区。
两者区别:
没有被
static
修饰的全局变量在整个程序中都是可见的,可以被其他文件访问和修改;而被static
修饰的全局变量只能在定义该变量的文件内部可见,无法被其他文件访问。
static int staticVar = 1;
用
static
修饰的局部变量会在整个程序执行过程中保留其值,直到程序结束。而staticVar
也被存到的C静态区。
int localVar = 1;
一个普通的局部变量定义,存储在A栈区。
int num1[10] = { 1, 2, 3, 4 };
是一个数组定义过程,也是一个普通变量,被存储在A栈区。
char char2[] = "abcd";
是一个数组定义过程,不过是在数组内部承载了一个字符串,此时会将字符串一个一个字符拷贝进数组中,所以最后
char2
是一个指针,而*char2
是一个数组或者是首元素。都被存在A栈区。
const char* pChar3 = "abcd";
用一个const指针接收了字符串
"abcd"
的地址,对于pChar3
本身,是一个指针,存储在A栈区。对于*pChar3
,则是这个常量字符串"abcd"
,存储在D常量区。
int* ptr1 = (int*)malloc(sizeof(int) * 4);
是一个动态内存分配过程,
ptr1
本身是一个指针,存储在A栈区。而*ptr1
则是指向一块动态内存,存放在B堆区。
sizeof(num1)
num1
是一个数组,此时得到的是数组的大小4*10 = 40
sizeof(char2)
char2
是一个数组,内部存储着”abcd“
被一个一个字符拷贝后的结果,由于字符串末尾有一个’\0‘
,所以数组长度比实际长1。最后大小为1 * (4 + 1)=5。
strlen(char2)
strlen
用于统计字符串的长度,遇到’\0‘
时停止同统计,所以结果为4。
sizeof(pChar3)
pChar3
是一个指针,在32位计算机中指针大小为4,64位计算机中指针大小为8。所以答案为4/8。
strlen(pChar3)
pChar3
指向字符串”abcd”,长度为4,结果为4。
sizeof(ptr1)
ptr1
是一个指针,在32位计算机中指针大小为4,64位计算机中指针大小为8。所以答案为4/8。
答案:
malloc & calloc & realloc & free
在C语言中,动态内存管理主要是通过malloc
, calloc
, realloc
, free
四个函数完成的。
我们简单回顾一下它们的作用与区别:
malloc,calloc和realloc是C语言中用于动态内存分配的函数。
- malloc函数:
- 作用:malloc函数用于在程序运行时动态分配指定大小的内存空间。
- 使用方法:malloc函数的原型为
void *malloc(size_t size)
,其中size
参数表示需要分配的内存空间大小(以字节为单位)。函数返回一个void
指针,指向分配的内存空间的起始地址。 - 示例:
int *ptr; ptr = (int *)malloc(10 * sizeof(int));
- calloc函数:
- 作用:calloc函数用于在程序运行时动态分配指定数量、指定大小的内存空间,并将分配的内存空间初始化为零。
- 使用方法:calloc函数的原型为
void *calloc(size_t num, size_t size)
,其中num
参数表示需要分配的元素个数,size
参数表示每个元素的大小(以字节为单位)。函数返回一个void
指针,指向分配的内存空间的起始地址。 - 示例:
int *ptr; ptr = (int *)calloc(10, sizeof(int));
- realloc函数:
- 作用:realloc函数用于修改之前动态分配的内存空间的大小。
- 使用方法:realloc函数的原型为
void *realloc(void *ptr, size_t size)
,其中ptr
参数是之前由malloc或calloc分配的内存空间的指针,size
参数表示新的内存空间大小。函数返回一个void
指针,指向修改后的内存空间的起始地址。如果返回空指针,则表示内存分配失败。 - 示例:
int *ptr; ptr = (int *)realloc(ptr, 20 * sizeof(int));
三者的异同点如下:
malloc
和calloc
都用于动态分配内存空间,而realloc
用于调整动态分配的内存空间的大小。malloc
和calloc
都返回分配的内存空间的起始地址,而realloc
返回修改后的内存空间的起始地址。malloc
和calloc
分配的内存空间不会被初始化,而realloc
可能会保留之前分配的内存内容。realloc
函数可能会将之前分配的内存空间内容复制到新的内存空间中,所以在使用realloc
时要小心,以免丢失之前分配的内存中的数据。realloc
函数还可以用于分配新的内存空间,如果之前的指针是空指针,则realloc的操作相当于malloc
。
C++
new & delete
在C++中,new
和delete
是用于动态分配和释放内存的关键字。动态分配内存是指在程序运行时按需分配所需的内存,而不是在编译时固定分配内存。
new
关键字用于动态分配单个对象的内存,并返回指向该对象的指针。其语法如下:
pointer = new type;
其中,pointer
是一个指针,用于存储分配的内存地址,type
是要分配内存的对象类型。
例如,以下代码分配了一个整数的内存,并将地址存储在ptr
指针中:
int* ptr = new int;
此时,ptr
指向一个未初始化的整数对象。
此外,如果希望分配的内存被初始化,可以用以下语法:
pointer = new type(值);
比如以下代码:
int* ptr = new int(10);
就是开辟了一个int的空间,并赋值为10。
要释放动态分配的内存,可以使用delete
关键字。其语法如下:
int* pointer = new int; delete pointer;
需要注意的是,使用delete
释放指针指向的内存后,该指针将不再有效,因为内存已经被释放。为了避免悬挂指针的问题,可以在释放内存后将指针设置为nullptr
,以防止后续误用。
delete ptr; ptr = nullptr;
但是到此为止,好像C++的动态内存和C语言的功能没什么区别。其实C++的new和delete与C语言的malloc和free的区别体现在类上。
当使用new关键字来创建对象时,会调用对象的构造函数,而malloc不会。
比如以下代码:
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 = (A*)malloc(sizeof(A)); A* p2 = new A; free(p1); delete p2; return 0; }
我们定义了一个A的类,然后分别用malloc
和new
的方式开辟了内存,来存放一个A的对象。
对于p1
而言,只是开辟了一个A
类需要的大小,并把void*
指针转化为了A*
的指针。
而对于p2
,不仅开辟了空间,而且调用了A
类的构造函数,把a
初始化为0。
所以我们在开辟类的动态内存时,最好使用new,来调用构造函数。
此外,delete
也会调用类的析构函数,而free
不会。
new[ ] & delete[ ]
new[ ]
如果需要动态分配一个数组,可以使用以下语法:
pointer = new type[size];
其中,size
是要分配内存的数组的大小。
例如,以下代码分配了一个包含10个整数的数组的内存,并将地址存储在ptr
指针中:
int* ptr = new int[10];
此时,ptr
指向一个包含10个未初始化的整数对象的数组。
此外,你还可以对这个数组进行初始化,用大括号即可:
int* ptr = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
这样就可以将数组初始化为{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
。
delete[ ]
同样地,如果之前使用new
分配了一个数组,可以使用以下语法释放内存:
delete[] pointer;
方括号内无需填入任何值。
比如:
int* ptr = new int[10]; delete[] ptr;
同样的new[ ]
& delete[ ]
如果作用与于类,那么会调用相应的构造函数与析构函数。
定位new
那么假如我们用malloc开辟了一个类的空间,还能不能对这个类初始化,调用其构造函数?
是可以的,这就需要定位new了。
在C++中,定位new是一种特殊的new表达式,它允许我们在指定的内存位置上创建对象。通常情况下,new表达式会自动分配内存,并在该内存上创建对象。但是,有时我们希望将对象放置在已经分配的内存中,这就需要使用定位new。
定位new的语法如下:
new (指针) 类型(参数列表);
其中,指针是一个指向已经分配的内存的指针,类型是要创建的对象的类型,参数列表是对象构造函数的参数。
示例:
class MyClass { public: int value; MyClass(int v) : value(v) { std::cout << "构造函数被调用了" << value << std::endl; } }; int main() { // 分配内存 void* memory = malloc(sizeof( MyClass)); // 在已分配的内存上创建对象 MyClass* obj = new (memory) MyClass(10); //销毁对象 obj->~MyClass(); return 0; }
在上面的示例中,首先我们使用malloc
分配了足够的内存以容纳一个MyClass
对象。然后,我们使用定位new在已分配的内存中创建了一个MyClass
对象,并传递了一个参数值10给构造函数。我们可以看到构造函数被调用,并输出了相应的消息。
需要注意的是,使用定位new创建的对象必须手动调用析构函数进行销毁,并手动释放相应的内存。
定位new在一些特定的情况下非常有用,例如在实现自定义的内存管理时,或者在某些嵌入式系统中,需要将对象放置在特定的内存地址上。但是在一般的编程中,几乎用不上。
new & delete原理
其实new和delete本质上还是malloc和free,但是C++在两者基础上做了很多优化,最后才得到的new和delete,接下来我将对new和delete进行拆解,带大家看清两者的原理。
对于new来说,其要完成的工作有:
- 开辟指定大小的空间
- 如果开辟空间失败,抛出异常(对malloc而言是返回空指针)
- 如果开辟的空间用于存放对象,那么调用对应的析构函数
我们看看以上三个步骤中,谁是可以通过malloc
完成的?
当然是第一步,malloc
就可以完成指定的空间的开辟。
抛出异常是C++相比于C语言特有的步骤,可以简单理解为报错。那么C++要如何检测开辟内存失败?
malloc
开辟内存失败就会返回空指针,所以我们可以通过malloc
的返回值确定是否要抛出异常。所以抛出异常这个步骤也与malloc
紧密关联,于是C++将第一步与第二步封装成了一个函数operator new
,它可以同时完成内存开辟和抛出异常,而两者都基于malloc
实现。
而我们平常使用的new就是operator new
函数+构造函数。
这也就是new的底层原理:将malloc进行封装,完成内存开辟与抛出异常,再额外调用构造函数,完成对象的初始化。
搞懂了new
的原理,那么delete
的原理也就差不多了:
delete
也将free
进行了一个封装,构成了一个operator delete
函数,其完成内存的释放,此外还会调用析构函数,完成对象调用的资源的释放。
不过要注意,delete
是先调用析构函数释放对象调用的资源,再调用operator delete
完成内存释放。
我们再加上new[]与delete[]不额外讲解了,我们现在对四个操作符进行一次总结:
new
的原理
- 调用
operator new
函数申请空间- 在申请的空间上执行构造函数,完成对象的构造
delete
的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用
operator delete
函数释放对象的空间
new []
的原理
- 调用
operator new[]
函数,在operator new[]
中实际调用operator new
函数完成N个对象空间的申请- 在申请的空间上执行N次构造函数
delete[]
的原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用
operator delete[]
释放空间,实际在operator delete[]
中调用operator delete
来释放空间
malloc / free 与 new / delete对比
malloc/free
和new/delete
的共同点是:
都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
malloc
和free
是函数,new
和delete
是操作符malloc
申请的空间不会初始化,new
可以初始化malloc
申请空间时,需要手动计算空间大小并传递,new
只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可malloc
的返回值为void*
, 在使用时必须强转,new
不需要,因为new
后跟的是空间的类型malloc
申请空间失败时,返回的是NULL
,因此使用时必须判空,new
不需要,但是new
需要捕获异常- 申请自定义类型对象时,
malloc/free
只会开辟空间,不会调用构造函数与析构函数,而new
在申请空间后会调用构造函数完成对象的初始化,delete
在释放空间前会调用析构函数完成空间中资源的清理