有關C++指針與安全闡述

文件目錄

一、內存的分配方式

1、堆與棧

在程序中定義一個變量,他的值會被放入內存中,如果沒有申請動態分配,它的值將會被放入棧中。棧中的變量所屬的內存大小是無法改變的,他們的產生與消亡也與變量定義的位置和存儲方式有關.堆是一種與棧相對應的動態分配方式的內存。當我們申請使用動態分配方式存儲某個變量時,這個變量就會被放入堆中,根據需要,這個變量的內存大小可以發生改變,內存申請和銷燬的時機則由編程者來操作。

2、關鍵字new與dalete

在獲取變量之前,編譯器並沒有獲取到變量的名稱,而只是具有指向該變量的指針。這時申請變量的堆內存即是申請自身指向堆。new是c++語言中申請動態內存的關鍵字,形如:

int* PI=new int;

這樣,PI指針就申請了動態方式,使用它在堆內申請內存存儲int型的值。
例;動態分配空間

#include"stdafx.h"
#include<iostream>
using namespace std;
int main(){
int* PI1=NULL;
PI1=new int;   //申請動態分配
*PI=111//動態分配內存存儲的內容變成111的整型變量
cout<<"PI內存的內容"<<*PI<<",PI所指向的地址"<<PI1<<endl;
int* PI2;
//*PI2=222;  //直接賦值會導致錯誤!!!
int k;   //棧中的變量
PI2=&k;  //分配棧內存
*PI2=222;   //分配內存後方可賦值
cout<<"PI內存的內容"<<*PI2<<",PI所指向的地址"<<PI2<<endl;
return 0;
}

從程序中可以看出,指針PI1被創建後就申請了動態分配,程序自動給它分配了一塊堆內存。而指針PI2則是獲取了棧中的內存地址,屬於靜態分配。
動態分配方式雖然很靈活,但是也帶來了新的問題。當申請一塊堆內存後,系統不會在程序執行時依據情況自動銷燬它,若想釋放該內存空間,則需要使用關鍵字delete。
例:動態內存的銷燬

#include"stdafx.h"
#include<iostrteam>
using std::cout;
using std::endl;
int* newPointerGet(int* p1){
int k1=55;
p1=new int ;
*p1=k1;
return p1;
}
int* PointerGet(int *p2){
int k2=55;
p2=&k2;
return p2;
}
int main(){
cout<<"輸出函數各自返回指針所指向的內存的值"<<endl;
int* p=NHLL;
p=newPointerGet(p);
int* i=NULL;
i=PointerGet(i);
cout<<"newGet:"<<*P<<",get:"<<*i<<endl;
cout<<"i所指向的內存沒有被立即銷燬,執行一個輸出語句後:”<<endl;
cout<<"newGet:"<<*P<<",get:"<<*i<<endl;
delete p;
cout<<"銷燬堆內存後:"<<endl;
cout<<"*p:"<<*p<<endl;
return 0;
}

從程序可知,變量p接收newGet返回指針的堆內存地址,所以內存的內容並沒被銷燬,而棧內存則由系統控制。程序最後使用delete語句釋放堆內存。

內存安全

指針是c++提供的強大而又靈活的工具,如何安全地使用它們是編程者必須掌握的技能之一。前面我們已討論過指針所指向內存的銷燬問題,當一塊內存被銷燬時,該區域不可複用。若有指針指向該區域,則需要該指針置爲空值(NULL)或者之相未被銷燬的內存。
內存銷燬實質上是系統判定該內存不是編程人員正常使用的空間,系統也會將他分配給別的任務,若擅自使用被銷燬內存的指針更改該內存的數據,則可能會造成意想不到的後果。
例:被銷燬的內存

#include"stdafx.h"
#include<iostream>
using std::cout;
using std::endl;
int* sum(int a,int b){
int* ps=NULL;
int c=a+b;
ps=&c;
return ps;
}
int main(){
int* pi=NULL;
int k1=3;
int k2=5;
pi=sum(k1,k2);
cout<<"*pi的值:"<<*pi<<endl;
cout<<"也許*pi還保留着i的值,但它已經被程序認定爲銷燬"<<endl;
cout<<"*pi的值:"<<*pi<<endl;
cout<<"嘗試修改*pi"<<endl;
*pi=3;
for(int i=0;i<3;i++){
cout<<"修改被銷燬的內存後*pi的值:"<<*pi<<endl;
}
}

從程序可知,指針PI從sum函數中得到了一個臨時指針,該指針是指針ps的臨時複製品,操作完成後就消失,而它做保留的地址交給了pi。在函數sum執行完畢後,該域使用棧內存會被系統銷燬或者挪用。本程序嘗試通過pi繼續使用、修改它,結果是系統會再次銷燬它。在某些場合下,該程序也會引起內存報錯,甚至造成多個系統崩潰。所以對於棧內存的指針一定要明白其何時被銷燬,不再重複利用它。
於此相對應的另一個安全問題是內存泄漏。如我們所知,在申請動態分配內存後,系統不會主動銷燬該堆內存,而需要編程者使用delete關鍵字通知系統銷燬。如果不這樣做,系統將浪費很多資源,是程序執行時變得臃腫,如只需佔用數十MB內存的程序可能爲此佔用上百MB的內存。可見,回收堆內存空間是很有必要的。銷燬內存時,需要保留指向該堆內存的指針。當沒有指針指向一塊沒被回收的堆內存時,此塊內存猶如丟失一般,我們稱之爲內存
例:丟失的內存

//******這是一個反例,它會造成內存泄漏**********//
#include"stdafx.h"
#include<iostream>
using namespace std;
int main(){
float* pf=NULL;
pf=new float;
*pf=4.321f;
float f2=5.321f;
cout<<"pf指向的地址"<<pf<<endl;
cout<<"*pf的值:"<<*pf<<endl;
pf=&pf;
cout>>"pf指向了f2的地址:"<<pf<<endl;
if(*pf>5){
cout<<"*pf的值:"<<*pf<<endl;
}
return 0;
}

程序中動態分配的內存開始有pf指向,當pf改變指向後,此塊內存就再也無法回收了。
一般情況下,我們無法通過調試程序發現內存泄漏。所以,使用動態分配時一定要注意形成良好習慣
例:回收動態內存的一般處理步驟

#include"stdafx.h"
#include<iostream>
void swap(int* a,int* b){
   int temp=*a;
   *a=*b;
   *b=temp;
}
int main(){
int* pi=new int;
*pi=3;
int k=5;
swap(pi,&k);
std::cout<<"*pi:"<<*pi<<std::end;   //使用std命名空間
std::cout<<"k:"<<k<<std::endl;
delete pi;    //回收動態內存
pi=NULL;   //將pi置空,防止使用已銷燬的內存。不可與上一語句顛倒,否者將造成內存泄漏
return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章