1、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.选择题: 选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____ staticVar在哪里?____ localVar在哪里?____ num1 在哪里?____
char2在哪里?____ *char2在哪里?___ pChar3在哪里?____ *pChar3在哪里?____ ptr1在哪里?____ *ptr1在哪里?____
2.填空题:
sizeof(num1) = ____;sizeof(char2) = ____; strlen(char2) = ____; sizeof(pChar3) = ____; strlen(pChar3) = ____; sizeof(ptr1) = ____;
3.sizeof 和 strlen 区别?
解析:
1
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堆区。
2
sizeof(num1)
num1是一个数组,此时得到的是数组的大小4*10 = 40
sizeof(char2)
char2是一个数组,内部存储着”abcd“被一个一个字符拷贝后的结果,由于字符串末尾有一个’strlen(char2)‘,所以数组长度比实际长1。最后大小为1 * (4 + 1)=5。
strlen用于统计字符串的长度,遇到’sizeof(pChar3)‘时停止同统计,所以结果为4。
pChar3是一个指针,在32位计算机中指针大小为4,64位计算机中指针大小为8。所以答案为4/8。
strlen(pChar3)
pChar3指向字符串”abcd”,长度为4,结果为4。
sizeof(ptr1)
ptr1是一个指针,在32位计算机中指针大小为4,64位计算机中指针大小为8。所以答案为4/8。
sizeof 和 strlen 区别?
sizeof()
3、 是一个运算符,而
代码段--可执行的代码/只读常量。
strlen()
是一个函数。sizeof()
计算的是变量或类型所占用的内存字节数,而strlen()
计算的是字符串中字符的个数。sizeof()
可以用于任何类型的数据,而strlen()
只能用于以空字符 'sizeof() 计算字符串的长度,包含末尾的 '说明:
',strlen() 计算字符串的长度,不包含字符串末尾的 '栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。'。' 结尾的字符串。内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
堆用于程序运行时动态内存分配,堆是可以上增长的。- 数据段--存储全局数据和静态数据。
,
2、C语言动态内存管理方式:malloc/calloc/realloc/free
- 在C语言中,动态内存管理主要是通过
- malloc
- ,
- calloc
realloc ,
free四个函数完成的。
我们简单回顾一下它们的作用与区别:malloc,calloc和realloc是C语言中用于动态内存分配的函数。
2.1、malloc函数:
作用:malloc函数用于在程序运行时动态分配指定大小的内存空间。
void *malloc(size_t num, size_t size)
size
void
指针,指向分配的内存空间的起始地址。- 例如:
- 使用方法:malloc函数的原型为
int *ptr; ptr = (int *)malloc(10 * sizeof(int));
,其中2.2、calloc函数
参数表示需要分配的内存空间大小(以字节为单位)。函数返回一个作用:calloc函数用于在程序运行时动态分配指定数量、指定大小的内存空间,并将分配的内存空间初始化为零。 void *calloc(size_t num, size_t size) - num
size
int *ptr; ptr = (int *)calloc(10, sizeof(int));
- 使用方法:calloc函数的原型为
2.3、realloc函数:
,其中作用:realloc函数用于修改之前动态分配的内存空间的大小。参数表示需要分配的元素个数,void *realloc(void *ptr, size_t size)参数表示每个元素的大小(以字节为单位)。函数返回一个void指针,指向分配的内存空间的起始地址。 - ptr
size
int *ptr; ptr = (int *)realloc(ptr, 20 * sizeof(int));
- 使用方法:realloc函数的原型为
三者的异同点:
,其中malloc和calloc都用于动态分配内存空间,而realloc用于调整动态分配的内存空间的大小。 参数是之前由malloc或calloc分配的内存空间的指针,malloc和calloc都返回分配的内存空间的起始地址,而realloc返回修改后的内存空间的起始地址。参数表示新的内存空间大小。函数返回一个void指针,指向修改后的内存空间的起始地址。如果返回空指针,则表示内存分配失败。 - malloc和calloc分配的内存空间不会被初始化,而realloc可能会保留之前分配的内存内容。
realloc函数可能会将之前分配的内存空间内容复制到新的内存空间中,所以在使用realloc时要小心,以免丢失之前分配的内存中的数据。
3、C++内存管理方式
- new
- delete
- new
- delete
new关键字用于动态分配单个对象的内存,并返回指向该对象的指针。其语法如下:
pointer = new type;
和pointer
在C++中,例如,以下代码分配了一个整数的内存,并将地址存储在ptr指针中:
和int* ptr = new int;
是用于动态分配和释放内存的关键字。动态分配内存是指在程序运行时按需分配所需的内存,而不是在编译时固定分配内存。
ptr
此外,如果希望分配的内存被初始化,可以用以下语法:
其中,pointer = new type(值);
是一个指针,用于存储分配的内存地址,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
不会。
4、new[ ] & delete[ ]
如果需要动态分配一个数组,可以使用以下语法:4.1、new[ ]
其中,pointer = new type[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}
。
4.2、delete[ ]
同样地,如果之前使用
new分配了一个数组,可以使用以下语法释放内存:
delete[] pointer;
例如:
同样的int* ptr = new int[10];
delete[] ptr;
new[ ]
&
delete[ ]
如果作用与于类,那么会调用相应的构造函数与析构函数。
4.3、定位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在一些特定的情况下非常有用,例如在实现自定义的内存管理时,或者在某些嵌入式系统中,需要将对象放置在特定的内存地址上。但是在一般的编程中,几乎用不上。
4.5、new & delete原理
其实new和delete本质上还是malloc和free,但是C++在两者基础上做了很多优化,最后才得到的new和delete,接下来我将对new和delete进行拆解,带大家看清两者的原理。
对于new来说,其要完成的工作有:
开辟指定大小的空间
如果开辟空间失败,抛出异常(对malloc而言是返回空指针)
如果开辟的空间用于存放对象,那么调用对应的析构函数抛出异常是C++相比于C语言特有的步骤,可以简单理解为报错。那么C++要如何检测开辟内存失败?
malloc
- 开辟内存失败就会返回空指针,所以我们可以通过
- malloc
- 的返回值确定是否要抛出异常。所以抛出异常这个步骤也与
malloc
紧密关联,于是C++将第一步与第二步封装成了一个函数
operator new ,它可以同时完成内存开辟和抛出异常,而两者都基于
malloc实现。
而我们平常使用的new就是operator new
函数+构造函数。这也就是new的底层原理:
将malloc进行封装,完成内存开辟与抛出异常,再额外调用构造函数,完成对象的初始化
。delete
也将
free进行了一个封装,构成了一个operator delete
函数,其完成内存的释放,此外还会调用析构函数,完成对象调用的资源的释放。
不过要注意,
delete是先调用析构函数释放对象调用的资源,再调用
operator 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
来释放空间
5、malloc / free 与 new / delete对比
都是
相同点:
从堆上申请空间
,并且需要用户手动释放。
不同点:
mallocnew和free是函数,
- 和delete是操作符malloc申请的空间不会初始化,new可以初始化malloc
- 申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可malloc
- 的返回值为void*, 在使用时必须强转,new
- 不需要,因为new后跟的是空间的类型malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new
- 不需要,但是new需要捕获异常申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理