1. 程式人生 > >經典分享 C++記憶體管理詳解 1(共2部分)

經典分享 C++記憶體管理詳解 1(共2部分)

C++記憶體管理。程式設計師們經常編寫記憶體管理程式,往往提心吊膽。如果不想觸雷,唯一的解決辦法就是發現所有潛伏的地雷並且排除它們,躲是躲不了的。本文的內容比一般教科書的要深入得多,讀者需細心閱讀,做到真正地通曉記憶體管理。

1、記憶體分配方式

記憶體分配方式有三種:

(1)從靜態儲存區域分配。

記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。

(2)在棧上建立。

在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。

(3) 從堆上分配,亦稱動態記憶體分配。

程式在執行的時候用malloc或new申請任意多少的記憶體,程式設計師自己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。

2、常見的記憶體錯誤及其對策

發生記憶體錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程式執行時才能捕捉到。而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時使用者怒氣衝衝地把你找來,程式卻沒有發生任何問題,你一走,錯誤又發作了。 常見的記憶體錯誤及其對策如下:

(1)記憶體分配未成功,卻使用了它。

程式設計新手常犯這種錯誤,因為他們沒有意識到記憶體分配會不成功。常用解決辦法是,在使用記憶體之前檢查指標是否為null。如果指標p是函式的引數,那麼在函式的入口處用assert(p!=null)進行

檢查。如果是用malloc或new來申請記憶體,應該用if(p==null) 或if(p!=null)進行防錯處理。

(2) 記憶體分配雖然成功,但是尚未初始化就引用它。

犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為記憶體的預設初值全為零,導致引用初值錯誤(例如陣列)。 記憶體的預設初值究竟是什麼並沒有統一的標準,儘管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式建立陣列,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。

(3) 記憶體分配成功並且已經初始化,但操作越過了記憶體的邊界。

例如在使用陣列時經常發生下標“多1”或者“少1”的操作。特別是在for迴圈語句中,迴圈次數很容易搞錯,導致陣列操作越界。

(4) 忘記了釋放記憶體,造成記憶體洩露。

含有這種錯誤的函式每被呼叫一次就丟失一塊記憶體。剛開始時系統的記憶體充足,你看不到錯誤。終有一次程式突然死掉,系統出現提示:記憶體耗盡。

動態記憶體的申請與釋放必須配對,程式中malloc與free的使用次數一定要相同,否則肯定有錯誤(new/delete同理)。

(5)釋放了記憶體卻繼續使用它。

有三種情況:

  • 程式中的物件呼叫關係過於複雜,實在難以搞清楚某個物件究竟是否已經釋放了記憶體,此時應該重新設計資料結構,從根本上解決物件管理的混亂局面。
  • 函式的return語句寫錯了,注意不要返回指向“棧記憶體”的“指標”或者“引用”,因為該記憶體在函式體結束時被自動銷燬。
  • 使用free或delete釋放了記憶體後,沒有將指標設定為null。導致產生“野指標”。

【規則1】用malloc或new申請記憶體之後,應該立即檢查指標值是否為null。防止使用指標值為null的記憶體。

【規則2】不要忘記為陣列和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。

【規則3】避免陣列或指標的下標越界,特別要當心發生“多1”或者“少1”操作。

【規則4】動態記憶體的申請與釋放必須配對,防止記憶體洩漏。

【規則5】用free或delete釋放了記憶體之後,立即將指標設定為null,防止產生“野指標”。

3、指標與陣列的對比

c++/c程式中,指標和陣列在不少地方可以相互替換著用,讓人產生一種錯覺,以為兩者是等價的。

陣列要麼在靜態儲存區被建立(如全域性陣列),要麼在棧上被建立。陣列名對應著(而不是指向)一塊記憶體,其地址與容量在生命期內保持不變,只有陣列的內容可以改變。

指標可以隨時指向任意型別的記憶體塊,它的特徵是“可變”,所以我們常用指標來操作動態記憶體。指標遠比陣列靈活,但也更危險。

下面以字串為例比較指標與陣列的特性。

3.1 修改內容

示例3-1中,字元陣列a的容量是6個字元,其內容為hello。a的內容可以改變,如a[0]= ‘x’。指標p指向常量字串“world”(位於靜態儲存區,內容為world),常量字串的內容是不可以被修改的。從語法上看,編譯器並不覺得語句p[0]= ‘x’有什麼不妥,但是該語句企圖修改常量字串的內容而導致執行錯誤。

  1. //示例3.1 修改陣列和指標的內容
  2. char a[] = “hello”;  
  3. a[0] = ‘x’;  
  4. cout << a << endl;  
  5. char *p = “world”; // 注意p指向常量字串
  6. p[0] = ‘x’; // 編譯器不能發現該錯誤
  7. cout << p << endl;  

3.2 內容複製與比較

不能對陣列名進行直接複製與比較。示例7-3-2中,若想把陣列a的內容複製給陣列b,不能用語句 b = a ,否則將產生編譯錯誤。應該用標準庫函式strcpy進行復制。同理,比較b和a的內容是否相同,不能用if(b==a) 來判斷,應該用標準庫函式strcmp進行比較。

語句p = a 並不能把a的內容複製指標p,而是把a的地址賦給了p。要想複製a的內容,可以先用庫函式malloc為p申請一塊容量為strlen(a)+1個字元的記憶體,再用strcpy進行字串複製。同理,語句if(p==a) 比較的不是內容而是地址,應該用庫函式strcmp來比較。

  1. //示例3.2 陣列和指標的內容複製與比較
  2. // 陣列…
  3. char a[] = "hello";  
  4. char b[10];  
  5. strcpy(b, a); // 不能用 b = a;
  6. if(strcmp(b, a) == 0) // 不能用 if (b == a)
  7. …  
  8. // 指標…
  9. int len = strlen(a);  
  10. char *p = (char *)malloc(sizeof(char)*(len+1));  
  11. strcpy(p,a); // 不要用 p = a;
  12. if(strcmp(p, a) == 0) // 不要用 if (p == a)
  13. …  

3.3 計算記憶體容量

用運算子sizeof可以計算出陣列的容量(位元組數)。示例7-3-3(a)中,sizeof(a)的值是12(注意別忘了’’)。指標p指向a,但是sizeof(p)的值卻是4。這是因為sizeof(p)得到的是一個指標變數的位元組數,相當於sizeof(char*),而不是p所指的記憶體容量。c++/c語言沒有辦法知道指標所指的記憶體容量,除非在申請記憶體時記住它。

注意當陣列作為函式的引數進行傳遞時,該陣列自動退化為同類型的指標。示例7-3-3(b)中,不論陣列a的容量是多少,sizeof(a)始終等於sizeof(char *)。

  1. char a[] = "hello world";  
  2. char *p = a;  
  3. cout<< sizeof(a) << endl; // 12位元組
  4. cout<< sizeof(p) << endl; // 4位元組 
  5. //示例3.3(a) 計算陣列和指標的記憶體容量
  6. void func(char a[100])  
  7. {  
  8.  cout<< sizeof(a) << endl; // 4位元組而不是100位元組
  9. }  
  10. //示例3.3(b) 陣列退化為指標

4、指標引數是如何傳遞記憶體的?

如果函式的引數是一個指標,不要指望用該指標去申請動態記憶體。示例7-4-1中,test函式的語句getmemory(str, 200)並沒有使str獲得期望的記憶體,str依舊是null,為什麼?

  1. 相關推薦

    經典分享 C++記憶體管理 部分

    C++記憶體管理。程式設計師們經常編寫記憶體管理程式,往往提心吊膽。如果不想觸雷,唯一的解決辦法就是發現所有潛伏的地雷並且排除它們,躲是躲不了的。本文的內容比一般教科書的要深入得多,讀者需細心閱讀,做到真正地通曉記憶體管理。 1、記憶體分配方式 記憶體分配

    程式設計師面試寶典隨筆記--記憶體管理

      記憶體管理是C++最令人切齒痛恨的問題,也是C++最有爭議的問題,C++高手從中獲得了更好的效能,更大的自由,C++菜鳥的收穫則是一遍一遍的檢查程式碼和對C++的痛恨,但記憶體管理在C++中無處不在,記憶體洩漏幾乎在每個C++程式中都會發生,因此要想成為C++高手,記

    spark調優-Apache Spark 記憶體管理

    Apache Spark 記憶體管理詳解 轉載於:https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-apache-spark-memory-management/index.html Spark 作為一個基於記憶體的分散式計算引擎,

    Spark2.1 記憶體管理

      本文中闡述的原理基於 Spark 2.1 版本,閱讀本文需要讀者有一定的 Spark 和 Java 基礎,瞭解 RDD、Shuffle、JVM 等相關概念。 在執行 Spark 的應用程式時,Spark 叢集會啟動 Driver 和 Executor 兩種 JVM 程序,前者為

    Apache Spark 記憶體管理

    Spark 作為一個基於記憶體的分散式計算引擎,其記憶體管理模組在整個系統中扮演著非常重要的角色。理解 Spark 記憶體管理的基本原理,有助於更好地開發 Spark 應用程式和進行效能調優。本文旨在梳理出 Spark 記憶體管理的脈絡,拋磚引玉,引出讀者對這個話題的深入探討。本文中闡述的原理基於 Sp

    動態記憶體管理

    C語言中開闢記憶體有很多種方式,目前我們最常用的也就是陣列,但陣列是在我們用到他之前就得設定好它的長度,有時很不方便。 我們知道,c語言規定,不允許設定一個未知長度的陣列。(但在Linux下可以設定,但也不支援這樣做)下面這段程式碼就會報錯喲!!! int x = 5; int arr[x]

    Apache Spark 記憶體管理轉載

    Spark 作為一個基於記憶體的分散式計算引擎,其記憶體管理模組在整個系統中扮演著非常重要的角色。理解 Spark 記憶體管理的基本原理,有助於更好地開發 Spark 應用程式和進行效能調優。本文旨在梳理出 Spark 記憶體管理的脈絡,拋磚引玉,引出讀者對這個話題的深入探討。本文中闡述的原理基於 Spark

    Spark 記憶體管理

    Spark 作為一個基於記憶體的分散式計算引擎,其記憶體管理模組在整個系統中扮演著非常重要的角色。理解 Spark 記憶體管理的基本原理,有助於更好地開發 Spark 應用程式和進行效能調優。本文旨在梳理出 Spark 記憶體管理的脈絡,拋磚引玉,引出讀者對這個話題的深

    Android NDK——必知必會之JNI的C++操作函式和小結

    引言 上一篇講解了一些關於JNI和NDK的必知必會的理論知識和機制,由於篇幅問題把關於JNI的重要的函式放到這篇,具體使用留到下一篇,此係列文章基連結: 一、JNI中的函式概述 在JNI層我們基本上都是通過env指標來呼叫jni.h標頭檔案裡定義的函式,JNI

    C++狀態模式--設計模式15

    State模式來源:   每個人、事物在不同的狀態下會有不同表現(動作),而一個狀態又會在不同的表現下轉移到下一個不同的狀態(State)。最簡單的一個生活中的例子就是:地鐵入口處,如果你放入正確的地鐵票,門就會開啟讓你通過。在出口處也是驗票,如果正確你就可以ok,否則就不讓

    】JNIJava Native Interface

    前言: 一提到JNI,多數程式設計者會下意識地感受到一種無法言喻的恐懼。它給人的第一感覺就是"難",因為它不是單純地在JVM環境內操作Java程式碼,而是跳出虛擬機器與其他程式語言進行互動。   你可能至今還沒聽說過這個技術,但是如果你是一個原始碼愛好者,或者有翻閱過JDK的一些原始碼,那你一定有接觸過nat

    《Python程式設計從入門到實踐》學習筆記-專案篇資料視覺化

    上一篇總結了《Python從入門到實踐》的第1章至第11章即基礎語法篇,這篇文章將介紹本書的專案篇之資料視覺化。 #專案一 資料視覺化 #繪製簡單的折線圖 import matplotlib.p

    《Python程式設計從入門到實踐》學習筆記-專案篇API的使用

    上幾篇介紹了《Python程式設計從入門到實踐》的語法篇及2個專案篇。這篇文章介紹Python專案篇之API的使用。 #Python網路程式設計--API的使用 #執行API呼叫並處理結果 imp

    次小生成樹及模板prim 以及 kruskal

    寫在前面我們大部分都對最小生成樹瞭解的多一些,一般求最小生成樹的演算法是prim、kurskal,那麼對於次小生成樹,我們也可以用上面兩種演算法來求解演算法解釋這兩種演算法的思路都是相同的,首先求出最小生成樹,我們列舉每條不在最小生成樹上的邊,並把這條邊放到最小生成樹上面,然

    微信和支付寶支付模式及實現.Net標準庫

         支付基本上是很多產品都必須的一個模組,大家最熟悉的應該就是微信和支付寶支付了,不過更多的可能還是停留在直接sdk的呼叫上,甚至和業務系統高度耦合,網上也存在各種解決方案,但大多形式各異,東拼西湊而成。所以這裡我介紹下OSS.PayCenter開源跨平臺支付元件 及

    資料結構與演算法之排序 一點課堂多岸學院

    通過前面的知識,我們已經知道,有序的資料在查詢時有極大的效能提升。很多查詢都基於有序資料,但並不是所有的結構都能像二叉排序樹一樣,

    Redis持久化RDB和AOF

    # 詳解Redis持久化(RDB和AOF) #### 什麼是Redis持久化? Redis讀寫速度快、效能優越是因為它將所有資料存在了記憶體中,然而,當Redis程序退出或重啟後,所有資料就會丟失。所以我們希望Redis能儲存資料到硬碟中,在Redis服務重啟之後,原來的資料能夠恢復,這個過程就叫持久化。

    分享】方言普通話語音識別語料庫語篇部分-600人

    方言普通話語音識別語料庫(語篇部分)-600人 儘管普通話語音識別系統的開發取得了很大進步,但在面對方言地區普通話時,遇到了挑戰。廣大方言地區、特別是南方方言區的普通話和標準普通話存在許多差別。因此,要提高方言語音識別率、進行方言語音識別,研究方言地區普通話的語音特點

    【轉】讓你不再害怕指針——C指針(經典,非常詳細)

    有一個 情況 value 第一個字符 接下來 意思 strcpy abcdefg 數值 前言:復雜類型說明 要了解指針,多多少少會出現一些比較復雜的類型,所以我先介紹一下如何完全理解一個復雜類型,要理解復雜類型其實很簡單,一個類型裏會出現很多運算符,他們也像普通的表

    讓你不再害怕指針——C指針(經典,非常詳細)

    [0 返回 hello spa 程序 string 和數 過程 建議 http://blog.csdn.net/soonfly/article/details/51131141 前言:復雜類型說明 要了解指針,多多少少會出現一些比較復雜的類型,所以我先介紹一下如何完