-
c语言入门之论C/C++函数间动态内存的传递
大连华信二系/徐兆元
FLxyzsby@163.com/xuzy@dhc.com.cn
当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道.这些往往会使人受尽折磨.所以如果你想深入C/C++编程,你必须静下心来,好好苦一番.
现在我们将讨论C/C++里我认为哪一本书都没有完全说清楚,也是涉及概念细节最多,语言中最难的技术之一的动态内存的传递.并且在软件开发中很多专业人员并不能写出相关的合格的代码.
【引入】 看下面的例子,这是我们在编写库函数或者项目内的共同函数经常希望的.
void MyFunc(char *pReturn, size_t size)
{
………
pReturn = (char *)malloc(sizeof(char) * num);
………
}
我们可以很明显地看出代码作者的意图,他想在函数调用处声明一个指针
char *pMyReturn=NULL;
然后调用MyFunc处理并返回一段长度为size的一段动态内存.
那么作者能达到预期的效果吗?
那么我可以告诉作者,他的程序在编译期很幸运地通过了,可是在运行期他的程序崩溃终止.
原因何在,是他触犯了系统不可侵犯的条款:错误地操作内存.
【内存操作及问题相关知识点】为了能彻底解决动态内存传递的问题,我们先回顾一下内存管理的知识要点.
(1)内存分配方式有三种:
●从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
●在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
●从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活。
(2)指针的操作流程
申请并初始化或设置为空:int *pInt=NULL;
开辟空间或者使其指向对象:pInt=new Int(3);或者int i=3;pint=&i;
用指针(更确切地说是操作内存,在使用之前加if(pint!=NULL)或者assert(pInt!=NULL)后再使用,以防内存申请失败的情况下使用指针):
if(p!=NULL) {use pint};
释放使用完的内存.free(pInt);
置指针为空pInt=NULL;(避免野指针的出现)
(3) 在函数的参数传递中,编译器总是要为函数的每个参数制作临时副本,如果参数为p的话,那么编译器会产生p的副本_p,使_p=p; 如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因.
【问题分析】
根据上面的规则我们可以很容易分析例子中失败的原因.
void MyFunc(char *pReturn, size_t size)
{
………
pReturn = (char *)malloc(sizeof(char) * num);
………
}
void main(void){
char *pMyReturn=NULL;
MyFunc(pMyReturn,10);
}
在MyFunc(char *pReturn, size_t size)中_pMyReturn真实地申请到了内存, _pMyReturn申请了新的内存,只是把_pMyReturn 所指的内存地址改变了,但是pMyReturn丝毫未变。所以函数MyFunc并不能输出任何东西。事实上,每执行一次MyFunc就会泄露一块内存,因为没有用free释放内存。
【问题解决方案】
函数间传递动态数据我们可以有三种解决方法.
方法一.如果我们是用C++编程,我们可以很方便地利用引用这个技术.我也极力推荐你用引用,因为它会使你少犯一些错误.以下是一个例子.
void MyFunc(char* &pReturn,size_t size){
pReturn=(char*)malloc(size);
memset(pReturn,0x00,size);
if(size>=13)
strcpy(pReturn,"Hello World!");
}
void main(){
char *pMyReturn=NULL;
MyFunc(pMyReturn,15);
if(pMyReturn!=NULL)
{
char *pTemp=pMyReturn;
while(*pTemp!=''\0'')
cout<<*pTemp++;
pTemp=NULL;
strcpy(pMyReturn,"AAAAAAAA");
free(pMyReturn);
pMyReturn=NULL;
}
}
方法二.利用二级指针
void MyFunc (char ** pReturn, size_t size)
{
* pReturn = (char *)malloc(size);
}
void main(void)
{
char * pMyReturn = NULL;
MyFunc (&pMyReturn, 100);// 注意参数是 & pMyReturn
if(pMyReturn!=NULL){
strcpy(pMyReturn, "hello");
cout<< pMyReturn << endl;
free(pMyReturn);
pMyReturn=NULL;
}}
为什么二级指针就可以了.原因通过函数传递规则可以很容易地分析出来.我们将& pMyReturn传递了进去,就是将双重指针的内容传递到了函数中.函数过程利用改变指针的内容,这样pMyReturn很明显指向了开辟的内存 .
方法三. 用函数返回值来传递动态内存
char * MyFunc (void)
{
char *p =new char[20];
memset(p,0x00,sizeof(p));
return p;
}
void main(void)
{
char *str = NULL;
str = MyFunc();
if(str!=NULL)
{
strcpy(str,"Hello,baby");
cout<< str << endl;
free(str);
str=NULL;
}
}
请注意的是函数写成这样的话
char * MyFunc (void)
{
char *p =”Hello World”
return p;
}
的话,你是不能返回什么动态内存的,因为p指向的是字符串常量.内存在位于静态存储区
上分配,你无法改变.(你想要得到动态内存我们一定要看到malloc或者new).
【结束语】
操作内存是C/C++一个难点,我们作为专业的软件开发人员.应该深入理解并能灵活地掌握指针和内存的操作.
FLxyzsby@163.com/xuzy@dhc.com.cn
当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道.这些往往会使人受尽折磨.所以如果你想深入C/C++编程,你必须静下心来,好好苦一番.
现在我们将讨论C/C++里我认为哪一本书都没有完全说清楚,也是涉及概念细节最多,语言中最难的技术之一的动态内存的传递.并且在软件开发中很多专业人员并不能写出相关的合格的代码.
【引入】 看下面的例子,这是我们在编写库函数或者项目内的共同函数经常希望的.
void MyFunc(char *pReturn, size_t size)
{
………
pReturn = (char *)malloc(sizeof(char) * num);
………
}
我们可以很明显地看出代码作者的意图,他想在函数调用处声明一个指针
char *pMyReturn=NULL;
然后调用MyFunc处理并返回一段长度为size的一段动态内存.
那么作者能达到预期的效果吗?
那么我可以告诉作者,他的程序在编译期很幸运地通过了,可是在运行期他的程序崩溃终止.
原因何在,是他触犯了系统不可侵犯的条款:错误地操作内存.
【内存操作及问题相关知识点】为了能彻底解决动态内存传递的问题,我们先回顾一下内存管理的知识要点.
(1)内存分配方式有三种:
●从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
●在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
●从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活。
(2)指针的操作流程
申请并初始化或设置为空:int *pInt=NULL;
开辟空间或者使其指向对象:pInt=new Int(3);或者int i=3;pint=&i;
用指针(更确切地说是操作内存,在使用之前加if(pint!=NULL)或者assert(pInt!=NULL)后再使用,以防内存申请失败的情况下使用指针):
if(p!=NULL) {use pint};
释放使用完的内存.free(pInt);
置指针为空pInt=NULL;(避免野指针的出现)
(3) 在函数的参数传递中,编译器总是要为函数的每个参数制作临时副本,如果参数为p的话,那么编译器会产生p的副本_p,使_p=p; 如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因.
【问题分析】
根据上面的规则我们可以很容易分析例子中失败的原因.
void MyFunc(char *pReturn, size_t size)
{
………
pReturn = (char *)malloc(sizeof(char) * num);
………
}
void main(void){
char *pMyReturn=NULL;
MyFunc(pMyReturn,10);
}
在MyFunc(char *pReturn, size_t size)中_pMyReturn真实地申请到了内存, _pMyReturn申请了新的内存,只是把_pMyReturn 所指的内存地址改变了,但是pMyReturn丝毫未变。所以函数MyFunc并不能输出任何东西。事实上,每执行一次MyFunc就会泄露一块内存,因为没有用free释放内存。
【问题解决方案】
函数间传递动态数据我们可以有三种解决方法.
方法一.如果我们是用C++编程,我们可以很方便地利用引用这个技术.我也极力推荐你用引用,因为它会使你少犯一些错误.以下是一个例子.
void MyFunc(char* &pReturn,size_t size){
pReturn=(char*)malloc(size);
memset(pReturn,0x00,size);
if(size>=13)
strcpy(pReturn,"Hello World!");
}
void main(){
char *pMyReturn=NULL;
MyFunc(pMyReturn,15);
if(pMyReturn!=NULL)
{
char *pTemp=pMyReturn;
while(*pTemp!=''\0'')
cout<<*pTemp++;
pTemp=NULL;
strcpy(pMyReturn,"AAAAAAAA");
free(pMyReturn);
pMyReturn=NULL;
}
}
方法二.利用二级指针
void MyFunc (char ** pReturn, size_t size)
{
* pReturn = (char *)malloc(size);
}
void main(void)
{
char * pMyReturn = NULL;
MyFunc (&pMyReturn, 100);// 注意参数是 & pMyReturn
if(pMyReturn!=NULL){
strcpy(pMyReturn, "hello");
cout<< pMyReturn << endl;
free(pMyReturn);
pMyReturn=NULL;
}}
为什么二级指针就可以了.原因通过函数传递规则可以很容易地分析出来.我们将& pMyReturn传递了进去,就是将双重指针的内容传递到了函数中.函数过程利用改变指针的内容,这样pMyReturn很明显指向了开辟的内存 .
方法三. 用函数返回值来传递动态内存
char * MyFunc (void)
{
char *p =new char[20];
memset(p,0x00,sizeof(p));
return p;
}
void main(void)
{
char *str = NULL;
str = MyFunc();
if(str!=NULL)
{
strcpy(str,"Hello,baby");
cout<< str << endl;
free(str);
str=NULL;
}
}
请注意的是函数写成这样的话
char * MyFunc (void)
{
char *p =”Hello World”
return p;
}
的话,你是不能返回什么动态内存的,因为p指向的是字符串常量.内存在位于静态存储区
上分配,你无法改变.(你想要得到动态内存我们一定要看到malloc或者new).
【结束语】
操作内存是C/C++一个难点,我们作为专业的软件开发人员.应该深入理解并能灵活地掌握指针和内存的操作.
最新更新
Objective-C语法之代码块(block)的使用
VB.NET eBook
Add-in and Automation Development In VB.NET 2003 (F
Add-in and Automation Development In VB.NET 2003 (8
Add-in and Automation Development in VB.NET 2003 (6
Add-in and Automation Development In VB.NET 2003 (5
AddIn Automation Development In VB.NET 2003 (4)
AddIn And Automation Development In VB.NET 2003 (2)
Addin and Automation Development In VB.NET 2003 (3)
AddIn And Automation Development In VB.NET 2003 (1)
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
SQL Server -- 解决存储过程传入参数作为s
武装你的WEBAPI-OData入门
武装你的WEBAPI-OData便捷查询
武装你的WEBAPI-OData分页查询
武装你的WEBAPI-OData资源更新Delta
5. 武装你的WEBAPI-OData使用Endpoint 05-09
武装你的WEBAPI-OData之API版本管理
武装你的WEBAPI-OData常见问题
武装你的WEBAPI-OData聚合查询
OData WebAPI实践-OData与EDM
OData WebAPI实践-Non-EDM模式