谈C/C++指针精华(二)
当前位置:以往代写 > C/C++ 教程 >谈C/C++指针精华(二)
2019-06-13

谈C/C++指针精华(二)

谈C/C++指针精华(二)

副标题#e#

1.3指针与内存打点

操作指针你可以将数据写入内存中的任意位置,可是,一旦你的措施中有一个野指针("wild“pointer),即指向一个错误位置的指针,你的数据就危险了—存放在堆中的数据大概会被粉碎,用来打点堆的数据布局也大概会被粉碎,甚至操纵系统的数据也大概会被修改,有时,上述三种粉碎环境会同时产生。所以公道的正确的分派指针的地点长短常重要的。

1.3.1内存分派的方法

内存分派方法有三种:

(1)从静态存储区域分派。内存在措施编译的时候就已经分派好,这块内存在措施的整个运行期间都存在。譬喻全局变量,static变量。

(2)在栈上建设。在执行函数时,函数内局部变量的存储单位都可以在栈上建设,函数执行竣事时这些存储单位自动被释放。栈内存分派运算内置于处理惩罚器的指令会合,效率很高,可是分派的内存容量有限。

(3) 从堆上分派,亦称动态内存分派。措施在运行的时候用malloc或new申请任意几多的内存,措施员本身认真在何时用free或delete释放内存。动态内存的保留期由我们抉择,利用很是机动,但问题也最多,以下我们重点讲授动态内存分派。

1.3.2 malloc/free 的利用要点

malloc与free是C/C++语言的尺度库函数,它用于申请动态内存和释放内存。

函数malloc的原型如下:

void * malloc(size_t size);

用malloc申请一块长度为length的整数范例的内存,措施如下:

int *ip = (int *) malloc(sizeof(int) * length);

我们该当把留意力会合在两个要素上:“范例转换”和“sizeof”。

malloc函数返回值的范例是void *,所以在挪用malloc时要显式地举办范例转换,将void * 转换成所需要的指针范例。

malloc函数自己并不识别要申请的内存是什么范例,它只体贴内存的总字节数。譬喻int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。这个你可以用sizeof(范例)去测试。

在malloc的“()”中利用sizeof运算符是精采的气势气魄,但要当心有时我们会昏了头,写出 ip = malloc(sizeof(ip))这样的措施来。

函数free的原型如下:

void free( void * memblock );

为什么free函数不象malloc函数那样巨大呢?这是因为指针p的范例以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。假如p是NULL指针,那么free对p无论操纵几多次都不会出问题。假如p不是NULL指针,那么free对p持续操纵两次就会导致措施运行错误。


#p#副标题#e#

1.3.3 new/delete 的利用要点

对付非内部数据范例的工具而言,光用maloc/free无法满意动态工具的要求。工具在建设的同时要自动执行结构函数,工具在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器节制权限之内,不可以或许把执行结构函数和析构函数的任务强加于malloc/free.

因此C++语言需要一个能完成动态内存分派和初始化事情的运算符new,以及一个能完成清理与释放内存事情的运算符delete.留意new/delete不是库函数,只是C++的运算符。我们来看如下例子就知道怎么回事了。

class Object
{
public :
Object(void){std::cout << “Initialization”<< std::endl; }
~Object(void){std::cout << “Destroy”<< std::endl; }
void Initialize(void){std:: cout << “Initialization”<< std::endl; }
void Destroy(void){ std::cout << “Destroy”<< std::endl; }
}
void UseMallocFree(void)
{
Object *ip = (Object *)malloc(sizeof(Object));    // 申请动态内存
ip->Initialize();  // 初始化
//…
ip->Destroy();   // 排除事情
free(ip);   // 释放内存
}
void UseNewDelete(void)
{
Object *ip = new Object;  // 申请动态内存而且初始化
//…
Delete ip;   // 排除而且释放内存
}

用malloc/free和new/delete如何实现工具的动态内存打点

类Object的函数Initialize模仿告终构函数的成果,函数Destroy模仿了析构函数的成果。函数UseMallocFree中,由于malloc/free不能执行结构函数与析构函数,必需挪用成员函数Initialize和Destroy来完成初始化与排除事情。函数UseNewDelete则简朴得多。

所以我们不要诡计用malloc/free来完成动态工具的内存打点,应该用new/delete.由于内部数据范例的“工具”没有结构与析构的进程,对它们而言malloc/free和new/delete是等价的。new内置了sizeof、范例转换和范例安详查抄成果, ,对付非内部数据范例的工具而言,new在建设动态工具的同时完成了初始化事情。

new/delete 常利用的要领如下:

typeof *ip = new typeof[length];

类/布局 *ip = new 类布局;

一般释放如下:delete ip;

数组的释放如下:delete [] ip;

1.3.4内存耗尽怎么办?

假如在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。凡是有三种方法处理惩罚“内存耗尽”问题。

(1)判定指针是否为NULL,假如是则顿时用return语句终止本函数。譬喻:

void Func(void)
{
A *a = new A;
if(a == NULL)
{
return;
}

}

#p#副标题#e#

(2)判定指针是否为NULL,假如是则顿时用exit(1)终止整个措施的运行。譬喻:

#p#分页标题#e#

void Func(void)
{
A *a = new A;
if(a == NULL)
{
std::cout << “Memory Exhausted” << std::endl;
exit(1);
}

}

(3)为new和malloc配置异常处理惩罚函数。譬喻Visual C++可以用_set_new_hander函数为new配置用户本身界说的异常处理惩罚函数,也可以让malloc享用与new沟通的异常处理惩罚函数。具体内容请参考C++利用手册。

有一个很重要的现象要汇报各人。对付32位以上的应用措施而言,无论奈何利用malloc与new,险些不行能导致“内存耗尽”。因为32位操纵系统支持“虚存”,内存用完了,自动用硬盘空间顶替。我不想误导读者,必需强调:不加错误处理惩罚将导致措施的质量很差,千万不行因小失大。

1.3. 5杜绝“野指针”

“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判定。可是“野指针”是很危险的,if语句对它不起浸染。 “野指针”的原因主要有如下几种:

(1)指针变量没有被初始化。任何指针变量刚被建设时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在建设的同时该当被初始化,要么将指针配置为NULL,要么让它指向正当的内存。譬喻

char *ip = NULL;

char *ip = new char;

(2)指针ip被free可能delete之后,没有置为NULL,让人误觉得ip是个正当的指针。

(3)指针操纵逾越了变量的浸染范畴。这种环境让人防不胜防,示例措施如下:

class A
{
public:
void Func(void){ std::cout << “Func of class A” << std::endl; }
};
void Test(void)
{
A *p;
{
A a;
p = &a; // 留意 a 的生命期
}
p->Func(); // p是“野指针”
}

函数Test在执行语句p->Func()时,工具a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是有些编译器运行这个措施时居然没有堕落,这大概与编译器有关。

1.3.6指针参数是如何通报内存的?

假如函数的参数是一个指针,不要指望用该指针去申请动态内存。见如下例子:

void GetMemory(char *ip, int num)
{
ip = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
char *str = NULL;
GetMemory(str, 100); // str 仍然为 NULL
strcpy(str, "hello"); // 运行错误
}

#p#副标题#e#

试图用指针参数申请动态内存

短处出在函数GetMemory中。编译器老是要为函数的每个参数建造姑且副本,指针参数ip的副本是 _ip,编译器使 _ip = ip.假如函数体内的措施修改了_ip的内容,就导致参数ip的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_ip申请了新的内存,只是把_ip所指的内存地点改变了,可是ip丝毫未变。所以函数GetMemory并不能输出任何对象。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

假如非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见如下示例:

void GetMemory(char **p, int num)
{
*ip = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100); // 留意参数是 &str,而不是str
strcpy(str, "hello");
std::cout<< str << std::endl;
free(str);
}

用指向指针的指针申请动态内存

虽然,我们也可以用函数返回值来通报动态内存。这种要领越发简朴,见如下示例:

#p#分页标题#e#

char *GetMemory(int num)
{
char *ip = (char *)malloc(sizeof(char) * num);
return ip;
}
void Test(void)
{
char *str = NULL;
str = GetMemory(100);
strcpy(str, "hello");
std::cout<< str << std::endl;
free(str);
}

用函数返回值来通报动态内存

用函数返回值来通报动态内存这种要领固然好用,可是经常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数竣事时自动消亡,见如下示例:

char *GetString(void)
{
char p[] = "hello world";
return p; // 编译器将提出告诫
}
void Test(void)
{
char *str = NULL;
str = GetString(); // str 的内容是垃圾
std::cout<< str << std::endl;
}

return语句返回指向“栈内存”的指针

最后,按照以上叙述,我们总结如下利用法则供各人参考:

【法则1】用malloc或new申请内存之后,应应当即查抄指针值是否为NULL.防备利用指针值为NULL的内存。

【法则2】不要健忘为数组和动态内存赋初值。防备将未被初始化的内存作为右值利用。

【法则3】制止数组或指针的下标越界,出格要当心产生“多1”可能“少1”操纵。

【法则4】动态内存的申请与释放必需配对,防备内存泄漏。

【法则5】用free或delete释放了内存之后,当即将指针配置为NULL,防备发生“野指针”。

    关键字:

在线提交作业