动态内存
1、new和malloc的区别。(1)
• new是运算符,malloc()是一个库函数; • new会调用构造函数,malloc不会; • new返回指定类型指针,malloc返回void*指针,需要强制类型转换; • new会自动计算需分配的空间,malloc不行; • new可以被重载,malloc不能。 |
2、智能指针(1)
|
3、new、delete、malloc、free之间的关系(2)
new/delete/malloc/free都是动态内存分配的方式
1)new/delete是运算符,malloc/free是库函数,不能把构造函数和析构函数强加给malloc/free 2)new返回指定类型指针,malloc返回void*指针,需要强制类型转换; 3)new会自动计算需分配的空间,malloc不行,需要指定分配空间大小 4)new/delete可以被重载,malloc/free不能。 |
4、delete和delete[]的区别(2)
|
内存管理
1、C++的内存分区(1)
在C++中,内存分成6个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区、代码区。 栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。 自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。 代码区:存放程序的二进制代码。 |
2、内存泄漏怎么产生的?如何避免?(1)
内存泄露怎么产生的? |
|
如何避免? |
|
如果程序结束就自动回收,为什么还要防止内存泄漏? |
因为大部分程序是长时间运行的,比如酒店管理系统,长时间内存泄漏会影响机器性能。 |
3、C/C++中堆和栈的区别(1)
数据结构中的堆和栈 | |
栈(stack):像装数据的桶或箱子 stack一种先进后出的数据结构,允许新增元素、移除元素、获取最顶端元素,但是不允许有遍历行为; 可以以某种既有容器作为底部结构,将其接口改变,使之符合“先进后出”的特点,形成一个stack;
栈(heap):像倒立的树
heap是一个complete binary tree(完全二叉树),也就是,整棵binary tree除了最底层的叶节点之外,是填满的,而最底层的叶节点由左至右不得有空隙。 根据元素排列方式,heap可分为max-heap和min-heap,前者每个节点的键值都大于或等于其子节点键值,后者每个节点键值都小于等于其子节点键值。 |
|
内存分配中的栈区 | |
内存中的栈区处于相对较高的地址以地址的增长方向为上的话,栈地址是向下增长的;堆是向上增长的
|
4、溢出,越界,泄漏(1)
5、C/C++中分配内存的方法(1)
6、堆和栈的区别(2)
7、什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?(2)
8、C++的内存管理(2)
9、C++中内存泄漏的几种情况(2)
10、描述内存分配方式以及它们的区别?(3)
11、栈内存与文字常量区(3)
C++primer
第12章 动态内存
4.3、智能指针
对象分类:
全局变量:定义在函数体外的对象,存在于程序的整个执行过程,程序启动时创建,结束时销毁
局部变量:函数形参与函数体内定义的变量,其声明周期依赖于定义的方式
局部对象: |
自动对象:只存在于块执行期间的对象;当函数的控制路径经过变量定义语句时创建该对象,到达定义所在的块末尾时销毁; 形参也是自动变量,函数开始时为形参申请存储空间 初始化方式: 形参用传递给函数的实参初始化对应的自动对象 函数体内:(1)如果变量本身存在初始值,用该初始值进行初始化;(2)不含初始值,进行默认初始化(函数体内内置变量不被初始化,产生未定义的值)
|
局部静态对象:在程序执行路径第一次经过对象定义语句时初始化,并且直到程序终止时才销毁 初始化方式:如果没有显式初始值,执行值初始化,内置变量的局部静态变量初始化为0; |
内存 |
静态内存:保存局部static对象、类static数据成员、全局变量 |
栈内存:定义在函数内的非static对象 |
静态内存与栈内存中的对象由编译器自动创建和销毁;栈对象:定义的程序块运行时才存在;static对象在使用之前分配,程序结束时销毁 |
堆内存:每个程序拥有一个内存池,自由空间,用来存储动态分配的对象 why?1、容器不知道自己需要使用多少对象(eg:容器类) 2、程序不知道所需数据的准确类型 3、程序需要在多个对象间共享数据(等到最后一个使用者被销毁时释放数据) |
类的静态成员:
类的静态成员 |
why?需要一些成员与类本身直接相关,而不是与类的各个对象保持关联(eg:一个银行账户可能需要一个数据成员来表示当前的基准利息); |
方式:在成员声明之前加上static,存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据 |
动态分配对象
动态分配对象 |
程序运行时分配的对象,声明周期由程序控制,必须显式销毁 |
管理:(1)运算符new和delete:new,在动态内存中为对象分配空间并返回一个指向该对象的指针;delete,接受一个动态对象的指 针,销毁该对象,并释放与之关联的内存(缺点:忘记释放内存会导致内存泄漏) (2)智能指针,自动释放所指向对象;shared_ptr允许多个指针指向同一对象;unique_ptr"独占"所指向的对象 |
shared_ptr
初始化方式 |
默认初始化,保存着一个空指针
|
用new返回的内置指针来初始化智能指针,必须使用直接初始化(因为接受指针参数的智能构造函数时explicit,阻止隐式转换,因此不能将一个内置指针隐式转换为一个智能指针) 同理,一个返回shared_ptr的函数不能在返回语句中隐式转换一个普通指针,必须显示绑定到一个想要返回的指针上 |
shared_ptr<string> p1; //默认初始化
shared_ptr<int> p1 = new int(1024); //错误:拷贝初始化
shared_ptr<int> p2(new int(1024)); //正确:直接初始化,相当于显示绑定
shared_ptr<int> clone(int p){
return new int(p); //错误:存在隐式转换
}
shared_ptr<int> clone(int p){
return shared_ptr<int> (new int(p)); //正确:显式绑定
}
shared_ptr和unique_ptr都支持的操作 |
shared_ptr<T> sp 空指针,可以指向类型为T的对象 |
p 条件判断,判断是否为空指针 |
*p 解引用,获得所指对象 |
p->mem 等价于(*p).men |
swap(p,q) 交换p和q中的指针, 等同于p.swap(q) |
shared_ptr独有的操作 | |
make_shared<int> (args) | 返回一个shared_ptr,在动态内存中分配一个对象并用args初始化它;如果不传递任何参数,对象会进行值初始化 |
shared_ptr<T> p(q) | p是shared_ptr q的拷贝;会递增q的计数器 |
p=q | 会递减p的引用次数,递增q的引用次数 |
shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<int> p4 = make_shared<int>(); //值初始化为0
拷贝和赋值 |
每个shared_ptr中都有一个关联的计数器,称为引用计数;计数器变为0,自动释放自己所管理的对象。 |
引用次数递增:用一个shared_ptr初始化另一个shared_ptr/将它作为参数传递给一个函数/作为函数返回值 |
引用次数递减:给一个shared_ptr赋予一个新值/局部的shared_ptr离开其作用域 |
auto r = make_shared<int>(42);
r = q; //给r赋值,指向另一个地址;
//增加q指向对象的引用计数
//递减r指向对象的引用计数
//r原来指向的对象已没有引用者,自动释放
销毁 |
析构函数完成销毁工作,释放对象所分配的资源;析构函数会递减它指向对象的引用计数
默认使用delete释放它关联对象 |
直接内存管理 | |||||
使用运算符new分配内存,delete释放New分配的内存 | |||||
使用new动态分配和初始化对象 | |||||
在自由空间分配的内存是无名的,因为New无法为其分配的对象命名,而实返回一个指向该对象的指针
动态分配对象进行默认化,所以内置类型或者组合类型的对象的值将是未定义的,类类型对象用默认构造函数进行初始化
直接初始化
|
|||||
动态分配的const对象 | |||||
一个动态分配的const对象必须进行初始化(对于定义了默认构造函数的类类型,其const动态对象可以隐式初始化;其他类型对象必须显式初始化)
|
|||||
内存耗尽 | |||||
|
|||||
释放动态内存 | |||||
delete:接受一个指针,指向我们想要释放的对象 执行两个动作:销毁给定的指针指向的对象;释放对应的内存
传递给delete的指针必须指向动态分配的对象,或者是一个空指针
对于一个由内置指针管理的动态对象(new分配的),直到被显式释放之前都是存在的; 由shared_ptr管理的内存在最后一个shared_ptr销毁时会被自动释放
|
|||||
delete之后重置指针值 | |||||
delete之后,指针变成空悬指针:指向一块曾经保存数据但现在已经无效的内存的指针
避免方法:
!!!但是重置指针保护有限,问题是有可能多个指针指向相同内存(坚持只使用智能指针,就能避免问题了) |
|||||
智能指针和异常 | |||||
如果使用内置指针管理内存,且在new之后在对应的delete之前发生了异常, 则内存不会被释放
为了正确使用智能指针,需要坚持一些基本规范:
|
unique_ptr
unique_ptr | ||
1、定义一个unique_ptr时,需要将其绑定到一个new返回的指针上,且必须使用直接初始化 2、一个unique_ptr拥有其指向的对象,不支持普通拷贝或赋值操作
3、虽然不能拷贝、赋值unique_ptr,但是可以调用release\reset将指针的所有权从一个unique_ptr转移到另一个unique_ptr
|
||
不能拷贝unique_ptr的例外 | ||
可以拷贝或赋值一个将要被销毁的unique_ptr(最常见的例子是函数返回一个unique_ptr,因为编译器知道要返回的对象即将被销毁)
|
weak_ptr
weak_ptr(弱共享) | |
1、一种不控制所指对象生存周期的智能指针,指向一个shared_ptr管理的对象; 2、将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用次数 3、对象释放跟有没有weak_ptr指向没有关系,最后一个shared_ptr被销毁,对象会被释放
4、由于对象可能不存在,不能使用weak_ptr直接访问对象,必须调用lock; lock检查weak_ptr指向的对象是否存在,如果存在,lock返回一个指向共享对象的shared_ptr
|
|
为啥有了shared_ptr还要使用weak_ptr??? | |
|
|
std::shared_ptr 和 std::weak_ptr的用法以及引用计数的循环引用问题(见程序) | |
在Man类内部会引用一个Woman,Woman类内部也引用一个Man。当一个man和一个woman是夫妻的时候,他们直接就存在了相互引用问题。man内部有个用于管理wife生命期的shared_ptr变量,也就是说wife必定是在husband去世之后才能去世。同样的,woman内部也有一个管理husband生命期的shared_ptr变量,也就是说husband必须在wife去世之后才能去世。这就是循环引用存在的问题:husband的生命期由wife的生命期决定,wife的生命期由husband的生命期决定,最后两人都死不掉,违反了自然规律,导致了内存泄漏。 解决std::shared_ptr循环引用问题的钥匙在weak_ptr手上。weak_ptr对象引用资源时不会增加引用计数,但是它能够通过lock()方法来判断它所管理的资源是否被释放。 |
#include <iostream>
#include <memory>
class Woman;
class Man{
private:
std::weak_ptr<Woman> _wife;
//std::shared_ptr<Woman> _wife;
public:
void setWife(std::shared_ptr<Woman> woman){
_wife = woman;
}
void doSomthing(){
if(_wife.lock()){
}
}
~Man(){
std::cout << "kill man\n";
}
};
class Woman{
private:
//std::weak_ptr<Man> _husband;
std::shared_ptr<Man> _husband;
public:
void setHusband(std::shared_ptr<Man> man){
_husband = man;
}
~Woman(){
std::cout <<"kill woman\n";
}
};
int main(int argc, char** argv){
std::shared_ptr<Man> m(new Man());
std::shared_ptr<Woman> w(new Woman());
if(m && w) {
m->setWife(w);
w->setHusband(m);
}
return 0;
}
动态数组
动态数组 | ||
分配一个对象数组的方法:
|
||
new和数组 | ||
类型名后面跟一对方括号,指明要分配的对象的数目,大小必须是整型,但不必是常量; 返回指向第一个对象的指针
可以用一个表示数组类型的类型别名来分配一个数组
!!!动态数组并不是数组类型,用new分配一个数组,并未得到一个数组类型的对象,而是得到一个数组元素类型的指针 因此不能对动态数组调用begin\end,也不能调用范围for语句处理动态数组中的元素
|
||
初始化动态分配对象的数组 | ||
默认情况下进行默认初始化;可以对数组中的元素进行值初始化,方法是在大小之后跟一对空括号
新标准中,可以使用元素初始化器的花括号列表
|
||
动态分配一个空数组是合法的 | ||
不能创建一个大小为0的静态数组,但是可以用new分配一个大小为0的数组,返回一个合法的非空指针,像尾后指针
|
||
释放动态数组 | ||
delete——在指针前面加一个空方括号对(必须的,指示编译器此指针指向一个对象数组的第一个元素;若忽略了方括号,其行为是未定义的)
|
||
智能指针和动态数组 | ||
标准库提供了一个可以管理New分配的数组的unique_ptr
shared_ptr不直接支持动态数组,必须提供自己定义的删除器
|
allocator类 | ||||
new分配动态数组的局限性: 1、内存分配和对象构造组合在一起了,有时候会导致赋值两次 2、没有默认构造函数的类不能动态分配数组
allocator: 模板,定义在memory中,将内存分配和对象构造分离开; 必须指明这个allocator可以分配的对象类型
|
||||
allocator分配未构造的内存 | ||||
construct成员函数结构一个指针和零个或多个额外参数,在给定位置构造一个元素
使用未构造的内存,其行为是未定义的
对构造的元素使用destroy销毁(!!!销毁的是元素,没有释放内存,可以用来保存其他string)
释放内存通过调用deallocator完成
|
C与指针 第11章 动态内存分配
malloc和free | |
malloc:从内存池中取一块合适的内存,返回一个指向被分配内存块起始位置的指针,参数是需要分配的内存字节数(分配的是一块连续的内存,如果系统无法向malloc提供更多的内存,malloc返回一个NULL指针) free:归还分配的内存
为啥返回的是void*??? 因为malloc不知道申请的内存是要存储什么类型,而void*可以转换为其他任意类型的指针 |
|
calloc和realloc | |
calloc:在返回指向内存的指针之前把申请到的内存的每一位(bit)都初始化为 0;输入参数为所需元素个数和每个元素的字节数
realloc:用于修改一块已经分配的内存块的大小(扩大或缩小); 扩大内存块时,这块内存原先的内容依然保留,新增加的内容添加到原先内存块后面,新内存并未以任何方法进行初始化; 缩小内存块时,内存块尾部内容被拿掉,剩余部分内存的原先内容依然保留;
在使用realloc之后,不能再使用指向旧内存的指针,应该使用realloc返回的新指针(因为原先的内存块无法改变大小时,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的快上)
|
|
alloc | |
动态内存分配常见的错误:忘记检查所请求的内存是否分配成功;解决:alloc MALLOC宏接收元素的数目及元素的类型,计算需要的内存节数,并调用alloc获取内存; alloc调用malloc并进行检查,确保返回的指针不是NULL
程序见C和指针 P224 |
|
free注意事项 | |
1、传递给free的指针必须是一个从malloc\calloc\realloc\alloc函数返回的指针(即动态分配的内存) 2、释放一块内存的一部分也是不允许的,动态分配的内存必须整块一起释放
3、释放后将指针置空
|
C++ primer19.1 控制内存分配
|
|
重载new和delete | |
应用程序可以在全局作用域中定义operator new函数和operator delete函数,也可以定义为成员函数
当编译器发现一条new表达式之后,将在程序中查找可以调用的operator函数
::new只在全局作用域中查找匹配的 |
19.1.2定位new表达式
。。。。(待补充)
内存分配方式:(摘抄:C++内存管理(超长,例子很详细,排版很好))
分配方式简介 | ||
在C++中,内存分成6个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区、代码区。 栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。 自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。 代码区:存放程序的二进制代码。 |
||
明确区分堆与栈 | ||
在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。 首先,我们举一个例子:
这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下:(汇编需结合深入理解操作系统)
这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是delete p么?澳,错了,应该是delete []p,这是为了告诉编译器:我删除的是一个数组,VC6就会根据相应的Cookie信息去进行释放内存的工作。 |
||
堆和栈究竟有什么区别? | ||
好了,我们回到我们的主题:堆和栈究竟有什么区别? 主要的区别由以下几点: 1、管理方式不同; 2、空间大小不同; 3、能否产生碎片不同; 4、生长方向不同; 5、分配方式不同; 6、分配效率不同; 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改: 打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。 注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。
生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。 从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。 虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难的:) |
||
重载全局的new和delete操作符 |
||
可以很容易在全局作用域重载new和delete操作符,如下:
也可以在类中进行重载new和delete,如下:
所有TestClass 对象的内存分配都采用这段代码。更进一步,任何从TestClass 继承的类也都采用这一方式,除非它自己也重载了new 和 delete 操作符。通过重载new 和 delete 操作符的方法,你可以自由地采用不同的分配策略,从不同的内存池中分配不同的类对象。 |
||
为单个的类重载 new[ ]和delete[ ] |
||
必须小心对象数组的分配。你可能希望调用到被你重载过的new 和 delete 操作符,但并不如此。内存的请求被定向到全局的new[ ]和delete[ ] 操作符,而这些内存来自于系统堆。
|
||
常见的内存错误及对策 | ||
解决办法:在使用之前,检查指针是否未NULL,如果指针P是函数的参数,在函数入口处使用(assert(p != NULL));(如果系统无法向malloc提供更多的内存,malloc返回一个NULL指针;类似地,如果分配失败,new返回一个空指针,且抛出std::bad_alloc)
犯这种错误的原因:一是没有初始化的概念;二是以为内存的缺省初值都为0;内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
|
||
指针与数组的对比
修改内容 | ||||
1、数组名是指向数组首地址的指针,是常量指针,不可修改,但是数组内容可以修改;
“hello”是一个初始化字符数组的初始化列表;"world"是字符串常量; 分辨字符串常量和初始化列表快速记法: 当它初始化一个字符数组时,它就是一个初始化列表;其他任何地方,它都是一个字符串常量(位于静态存储区,内容不可修改)
|
||||
内容复制与比较 | ||||
1、不能将数组内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值,应该用标准库函数strcpy进行复制。同理,比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。
b == a; //运算符会比较数组的开始内存地址,而不是数组的内容。
2、可以使用C语言标准库提供的一组函数来操纵C风格字符串(primer p109) C风格字符串:从C继承而来的,为了表达和使用字符串而形成的一种约定俗成的写法。按此习惯书写的字符串存放在字符数组中并以空字符结束。空字符结束的意思是在字符串最后一个字符后面跟着一个空字符('\0').(字符串字面值是一种C风格字符串)
传入此类函数的指针必须指向以空字符作为结束的数组:
3、语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数malloc为p申请一块容量为strlen(a)+1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。
|
||||
计算内存容量 | ||||
用运算符sizeof可以计算出数组的容量(字节数)。如下示例中,sizeof(a)的值是12(注意别忘了’’)。指针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。如下示例中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。
|
指针参数如何传递内存的?
如果函数的参数是一个指针,不要指望用该指针去申请动态内存。如下示例中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?
因为传递给GetMemory的相当于指针p的拷贝_p,都指向同一块内存;如果修改了_p指向的内容,p指向的内容也会相应的改变;但是malloc,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。
解决方式:
|
杜绝野指针
野指针不是NULL指针,是指向垃圾内存的指针,成因主要有两种:
|
有了malloc、free为啥还要有new\delete
malloc\free是C++\C的标准库函数,new\delete是c++运算符 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。 因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。我们先看一看malloc/free和new/delete如何实现对象的动态内存管理
既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。 |
malloc\free的使用要点
函数malloc的原型如下:
用malloc申请一块长度为length的整数类型的内存,程序如下:
我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。 * malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。 * malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。 |
来源:CSDN
作者:a1059682127
链接:https://blog.csdn.net/a1059682127/article/details/103751589