简介:
本文简要说明在释放动态数组需要注意的地方,主要通过以下两个实例的练习,来加深理解数组空间释放:
示例一:根据输入参数动态创建数组。
示例二:从标准输入设备读入的元素数据建立一个int 型 vector 对象,然后动态创建一个与该 vector 对象大小一致的数组,把 vector 对象的所有元素复制给新数组。
关于创建动态数组的详细说明,可参考《C++Primer Fourth Edition-Section 4.3.1》。
本文也是基于书中习题(Exercise 4.28)的一些学习与思考,比较重要的地方引用书中的原话,在此做出声明。
实现:
示例一:根据输入的数组容量参数,实现int型动态数组创建,输出数组的内容,然后就释放了数组空间。
C++为指针提供“delete []表达式”释放指针所指向的数组空间。在关键字 delete 和指针之间的空方括号对是必不可少的: 它告诉编译器该指针指向的是自由存储区中的数组,而并非单个对象。 如果遗漏了空方括号对,这是一个编译器无法发现的错误,将导致程序在运行时出错。
#include <cstring>
#include <iostream>
#include <vector>
#include <stdlib.h>
using namespace std;
int main(int argc, char* argv[])
{
if(2!=argc)
{
cout<<"参数错误, 请使用:"<<argv[0]<<" 数字"<<endl;
return 1;
}
int arr_size=atoi(argv[1]);
cout<<"定义动态数组ip,空间大小为:"<<arr_size<<",且初始化为:0"<<endl;
int *ip=new int[arr_size](); //圆括号的作用是实现数组的初始化,int型数据默认值0。
如果为类类型,则按默认构造函数初始化
for(int i=0; i<arr_size; ++i)
{
cout<<"ip["<<i<<"]="<<*(ip+i)<<endl;
}
cout<<"释放数组空间"<<endl;
delete [] ip;
ip=NULL;
return 0;
}
实例二:实现将vector对象保存的数据复制到动态创建的数组中。下面的代码在编译时不会报错,在执行的时候会提示错误
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char* argv[])
{
vector<int> ivect;
int input=0;
while(cin>>input)
{
ivect.push_back(input);
if(0==input)break; // 方便gdb调试
}
int *ip=new int[ivect.size()];
for(vector<int>::iterator iter=ivect.begin(); iter!=ivect.end(); ++iter,++ip)
{
*ip=*iter;
cout<<*ip;
}
cout<<endl;
delete [] ip;
ip=NULL;
return 0;
}
*** glibc detected *** ./ar: free(): invalid pointer: 0x094f6080 ***
======= Backtrace: =========
/lib/libc.so.6[0x77c261]
/usr/lib/libstdc++.so.6(_ZdlPv+0x22)[0x35f89f2]
/usr/lib/libstdc++.so.6(_ZdaPv+0x1e)[0x35f8a4e]
./ar[0x8048bad]
/lib/libc.so.6(__libc_start_main+0xe6)[0x724bb6]
./ar[0x80489f1]
======= Memory map: ========
005b6000-005d3000 r-xp 00000000 fd:00 51325 /lib/libgcc_s-4.4.2-20091027.so.1
005d3000-005d4000 rw-p 0001c000 fd:00 51325 /lib/libgcc_s-4.4.2-20091027.so.1
006ec000-0070a000 r-xp 00000000 fd:00 56668 /lib/ld-2.11.so
0070a000-0070b000 r--p 0001d000 fd:00 56668 /lib/ld-2.11.so
0070b000-0070c000 rw-p 0001e000 fd:00 56668 /lib/ld-2.11.so
0070e000-0087c000 r-xp 00000000 fd:00 56669 /lib/libc-2.11.so
0087c000-0087d000 ---p 0016e000 fd:00 56669 /lib/libc-2.11.so
0087d000-0087f000 r--p 0016e000 fd:00 56669 /lib/libc-2.11.so
0087f000-00880000 rw-p 00170000 fd:00 56669 /lib/libc-2.11.so
00880000-00883000 rw-p 00000000 00:00 0
008a8000-008d0000 r-xp 00000000 fd:00 56684 /lib/libm-2.11.so
008d0000-008d1000 r--p 00027000 fd:00 56684 /lib/libm-2.11.so
008d1000-008d2000 rw-p 00028000 fd:00 56684 /lib/libm-2.11.so
00e0a000-00e0b000 r-xp 00000000 00:00 0 [vdso]
03541000-0362f000 r-xp 00000000 fd:00 56729 /usr/lib/libstdc++.so.6.0.13
0362f000-03633000 r--p 000ed000 fd:00 56729 /usr/lib/libstdc++.so.6.0.13
03633000-03635000 rw-p 000f1000 fd:00 56729 /usr/lib/libstdc++.so.6.0.13
03635000-0363b000 rw-p 00000000 00:00 0
08048000-0804b000 r-xp 00000000 fd:00 152332 /home/kang/Desktop/CPPprimer/ar
0804b000-0804c000 rw-p 00002000 fd:00 152332 /home/kang/Desktop/CPPprimer/ar
094f6000-09517000 rw-p 00000000 00:00 0 [heap]
b78c3000-b78c5000 rw-p 00000000 00:00 0
b78da000-b78dd000 rw-p 00000000 00:00 0
bfc2f000-bfc44000 rw-p 00000000 00:00 0 [stack]
Aborted (core dumped)
内存泄露,指针操作不当,就会看到上面的一堆代码。在gdb调试时,发现使用ctrl+d命令结束数据输入时直接退出调试,为了调试方便,增加了判断输入是否为0的判断。
单步到倒数第三行代码:
cout<<endl;
//其实错误在此前已经悄悄的出现,只在等待delete时爆发啦
delete [] ip;
上面的一堆错误就出来了,难道是中括号前后的空格导致的?它怎么地就内存溢出了呢?
再看看数组的创建:
int *ip=new int[ivect.size()];
此new表达式分配了一个含有 ivect.size() 个 int 型元素的数组,并返回指向该数组第一个元素的指针,此返回值初始化了指针 ip
对,ip指向数组的第一个元素。那delete时的ip指针也是指向第一个元素么?
delete [] ip;
根据前面提到的,delete语句应该是释放ip指针所指向的数组空间,所以此时的ip还是指向数组的第一个元素。那错误出在那里了呢?
难道是ip指针被指向的不是第一个元素,再看看for循环
for(vector<int>::iterator iter=ivect.begin(); iter!=ivect.end(); ++iter,++ip)
++ip,整个循环结束后,ip指向了数组的最后一个元素,后面没有将ip指针移回原处。噢,原来是在这里修改了ip指针。
错误终于找到了。好了,修改下试试。将
delete [] ip;
修改为:
delete [] (ip - ivect.size());
编译,运行,OK啦。
其实提倡的做法是,再定义一个int指针ip_temp,指向ip所指的空间,在整个操作过程中使用ip_temp,只是在最后释放空间的时候使用ip就可以啦。
感谢zdd的提醒,在释放空间后,将ip=NULL,在复杂的程序中,在使用该指针前通过判断指针是否为NULL来确定指针的合法性,防止使用不存在的内存空间。
总结:
1.指针和数组太危险,推荐使用vector,verctor的优势还是很多的
2.出现上面的错误最大的原因还是在自己没有理解透指针和数组的本质意义,还是需要多做练习。像这些简单的练习,动手啦,才能知道哪里会错,错啦才知道改,改啦就会记忆深刻。