1. 程式人生 > >C語言 記憶體分配 地址 指標 陣列 引數 例項解析

C語言 記憶體分配 地址 指標 陣列 引數 例項解析

指標簡介 : 指標式儲存變數地址的變數;

-- 增加閱讀難度 : 指標 和 goto 語句會增加程式的理解難度, 容易出現錯誤;

-- ANSI C : American National Standards Institute 美國國家標準學會, 即標準C;

-- 通用指標型別 : ANSI C中使用 void* 作為通用指標型別, 即指向void的指標, void 是空型別, void* 是空型別指標, 可以指向任意型別的地址;

1. void 與 void* 

(1) void 簡介

void 作用 : 

-- 限定引數 : 函式沒有返回值, 需要使用 void 宣告, 否則預設返回 int 型別

;

-- 限定返回值 : 函式不接收引數, 使用 void 作為引數, 如果傳入引數, 編譯器就會報錯;

使用void注意點 : 

-- void不能表示變數 : void a, 這樣定義是錯誤的;

-- 預設返回值 : C 中, 如果沒有標明返回值型別, 預設的返回值不是 void, 是 int 型別;

-- void引數 : C 語言中引數是void, 傳入引數不會出錯, C++中傳入引數會出錯, 因此這裡我們統一規定, 如果函式沒有引數, 就定義為void;

.

(2) void*簡介

void * 作用 : 

-- 通用資料型別

 : void * 指標可以存放任意型別資料的地址, 任何資料型別的指標都可以賦值給 void * 通用型別指標;
-- 任意型別 : 如果 函式 的 引數 和 返回值 可以是任意型別, 就可以使用 void * 作為函式的 引數 或者 返回值;

使用void* 注意點 : 

-- void * 與 其它型別互相賦值 : int * 變數可以賦值給 void * 變數, 但是void * 變數如果賦值給 int * 變數需要強轉為 int * 型別;

-- void * 不允許進行 算數操作 : 標準C 中規定 void * 型別不允許進行 加減乘除 算數運算, 因為我們不知道這個型別的大小, GUN 中void * 等價於 char * ;

2. C 語言 程式記憶體分配

(1) 記憶體分割槽狀況

棧區 (stack) : 

-- 分配, 釋放方式 : 由編譯器自動分配 和 釋放;

-- 存放內容 : 區域性變數, 引數;

-- 特點 : 具有 後進先出 特性, 適合用於 儲存 回覆 現場;

堆區 (heap) : 

-- 分配, 釋放方式 : 由程式設計師手動 分配(malloc) 和 釋放(free), 如果程式設計師沒有釋放, 那麼程式退出的時候, 會自動釋放;

-- 存放內容 : 存放程式執行中 動態分配 記憶體的資料;

-- 特點 : 大小不固定, 可能會動態的 放大 或 縮小;

堆區記憶體申請 

-- 申請過程 : OS中有一個記錄空閒記憶體地址的連結串列, 如果程式設計師申請記憶體, 就會找到空間大於申請記憶體大小的節點, 將該節點從空間記憶體連結串列中刪除, 並分配該節點; 

-- 剩餘記憶體處理 : 系統會將多餘的部分重新放回 空閒記憶體連結串列中;

-- 首地址記錄大小 : 分配記憶體的首地址存放該堆的大小, 這樣釋放記憶體的時候才能正確執行; 

全域性區/靜態區 (資料段 data segment / bss segment) : 

-- 分配, 釋放方式 : 編譯器分配記憶體, 程式退出時系統自動釋放記憶體;

-- 存放內容 : 全域性變數, 靜態變數;

-- 特點 : 全域性變數 和 靜態變數儲存在一個區域, 初始化的兩種變數 和 未初始化的 儲存在不同區域, 但是兩個區域是相鄰的;

常量區 

-- 分配, 釋放方式 : 退出程式由系統自動釋放;

-- 存放內容 : 常量;


程式碼區 (text segment) : 

-- 分配, 釋放方式 : 編譯器分配記憶體, 程式退出時系統自動釋放記憶體;

-- 存放內容 : 存放 程式的二進位制程式碼, 和一些特殊常量;

記憶體存放順序 (由上到下) : 棧區 -> 堆區 -> 全域性區 -> 常量區 -> 程式碼區;

(2) 記憶體分配方式

全域性記憶體分配 : 

-- 生命週期 : 編譯時分配記憶體, 程式退出後釋放記憶體, 與 程式 的生命週期相同;

-- 儲存內容 : 全域性變數, 靜態變數;

棧記憶體分配 :

-- 生命週期 : 函式執行時分配記憶體, 執行結束後釋放記憶體;

-- 特點 : 該分配運算由處理器處理, 效率高, 但是棧記憶體控制元件有限;

堆記憶體分配 : 

-- 生命週期 : 呼叫 malloc()開始分配, 呼叫 free()釋放記憶體, 完全由程式設計師控制;

-- 謹慎使用 : 如果分配了 沒有釋放, 會造成記憶體洩露, 如果頻繁 分配 釋放 會出現記憶體碎片; 

(3) register變數

使用場景 : 如果 一個變數使用頻率特別高, 可以將這個變數放在 CPU 的暫存器中;

-- 修飾限制 : 只有 區域性變數 和 引數 可以被宣告為 register變數, 全域性 和 靜態的不可以;

-- 數量限制 : CPU 暫存器 很寶貴, 不能定義太多register變數;

(4) extern 變數

extern變數概念 : 宣告外部變數, 外部變數就是在函式的外部定義的變數, 在本函式中使用;

-- 作用域 : 從外部變數定義的位置開始, 知道本原始碼結束都可以使用, 但是隻能在定義extern後面使用, 前面的程式碼不能使用;

-- 存放位置 : 外部變數 存放在 全域性區;

extern變數作用 : 使用extern修飾外部變數, ① 擴充套件外部變數在本檔案中的作用域, ② 將外部變數作用域從一個檔案中擴充套件到工程中的其它檔案;

extern宣告外部變數的情況 : 

-- 單個檔案內宣告 : 如果不定義在檔案開頭, 其作用範圍只能是 定義位置開始, 檔案結束位置結束;

-- 多個檔案中宣告 : 兩個檔案中用到一個外部變數, 只能定義一次, 編譯 和 連線的時候, 如果沒有這個外部變數, 系統會知道這個外部變數在別處定義, 將另一個檔案中的外部變數擴充套件到本檔案中;

extern編譯原則 

-- 本檔案中能找到 : 編譯器遇到 extern 的時候, 現在本檔案中找外部變數的定義的位置, 如果找到, 就將作用域擴充套件到 定義的位置 知道檔案結束;

-- 本檔案中找不到 : 如果本檔案中找不到, 連線其它檔案找外部變數定義, 如果找到, 將外部變數作用域擴充套件到本檔案中;

-- 外部檔案找不到 : 報錯;

使用效果 : extern 使用的時候, 可以不帶資料型別;

-- 本檔案 : int A = 0; 在第10行, extern A 在第一行, 那麼A的作用域就擴充套件為從第一行到檔案末尾;

-- 多檔案 : 在任意檔案中定義了 int A = 0; 在本檔案中宣告 extern A, 那麼從當前位置到檔案末尾都可以使用該變數;

(5) static變數 與 全域性變數區別

static 變數 與 全域性變數 相同點 : 全域性變數是靜態儲存的, 儲存的方式 和 位置基本相同;

static 變數 與 全域性變數不用點 : 全域性變數的作用域是 整個專案工程 橫跨過個檔案, 靜態變數的作用域是 當前檔案, 其它檔案中使用是無效的;

變數儲存位置 : 全域性變數 和 靜態變數 存放在 全域性區/靜態去, 區域性變數存放在 棧區(普通變數, 指標變數內容) 和 堆區(指標變數指向的內容);

變數靜態化 

-- 區域性變數 : 區域性變數 加上 static , 相當於將區域性變數的生命週期擴大到了整個檔案, 作用域不改變;

-- 全域性變數 : 全域性變數 加上 static , 相當於將全域性變數的作用域縮小到了單個檔案, 生命週期是整個程式的週期;

關於函式標頭檔案的引申 : 

-- 內部函式 : 單個檔案中使用的內部函式, 僅在那個特定檔案中定義函式即可;

-- 全域性函式 : 如果要在整個工程中使用一個全域性函式, 需要將這個函式定義在一個頭檔案中;

static變數與普通變數區別 : 

-- static全域性變數 與 全域性變數區別 : static 全域性變數 只初始化一次, 防止在其它檔案中使用;

-- static區域性變數 與 區域性變數區別 : static 區域性變數 只初始化一次, 下一次依據上一次結果;

static函式與普通函式區別 : static 函式在記憶體中只保留一份, 普通函式 每呼叫一次, 就建立一個副本;

.

(6) 堆 和 棧比較

堆(heap)和棧(stack)區別 

-- 申請方式 : stack 由系統自動分配, heap 由程式設計師進行分配;

-- 申請響應 : 如果 stack 沒有足夠的剩餘空間, 就會溢位; 堆記憶體從連結串列中找空閒記憶體;

-- 記憶體限制 : stack 記憶體是連續的, 從高位向低位擴充套件, 而且很小, 只有幾M, 是事先定好的, 在檔案中配置; heap 是不連續的, 從低位向高位擴充套件, 系統是由連結串列控制空閒程式, 連結串列從低地址到高地址, 堆大小受虛擬記憶體限制, 一般32位機器有4G heap;

-- 申請效率 : stack 由系統分配, 效率高; heap 由程式設計師分配, 速度慢, 容易產生碎片;

(7) 各區分佈情況

.

按照下圖分佈 : 由上到下順序 : 棧區(stack) -> 堆區(heap) -> 全域性區 -> 字元常量區 -> 程式碼區;


驗證分割槽狀況 : 

-- 示例程式 : 

  1. /************************************************************************* 
  2.     > File Name: memory.c 
  3.     > Author: octopus 
  4.     > Mail: octopus_work.163.com  
  5.     > Created Time: Mon 10 Mar 2014 08:34:12 PM CST 
  6.  ************************************************************************/
  7. #include<stdio.h>
  8. #include<stdlib.h>
  9. int global1 = 0, global2 = 0, global3 = 0;  
  10. void function(void)  
  11. {  
  12.         int local4 = 0, local5 = 0, local6 = 0;  
  13.         staticint static4 = 0, static5 = 0, static6 = 0;  
  14.         int *p2 = (int*)malloc(sizeof(int));  
  15.         printf("子函式 區域性變數 : \n");  
  16.         printf("local4 : %p \n", &local4);  
  17.         printf("local5 : %p \n", &local5);  
  18.         printf("local6 : %p \n", &local6);  
  19.         printf("子函式 指標變數 : \n");  
  20.         printf("p2 : %p \n", p2);  
  21.         printf("全域性變數 : \n");  
  22.         printf("global1 : %p \n", &global1);  
  23.         printf("global2 : %p \n", &global2);  
  24.         printf("global3 : %p \n", &global3);  
  25.         printf("子函式 靜態變數 : \n");  
  26.         printf("static4 : %p \n", &static4);  
  27.         printf("static5 : %p \n", &static5);  
  28.         printf("static6 : %p \n", &static6);  
  29.         printf("子函式地址 : \n");  
  30.         printf("function : %p \n", function);  
  31. }  
  32. int main(int argc, char **argv)  
  33. {  
  34.         int local1 = 0, local2 = 0, local3 = 0;  
  35.         staticint static1 = 0, static2 = 0, static3 = 0;  
  36.         int *p1 = (int*)malloc(sizeof(int));  
  37.         constint const1 = 0;  
  38.         char *char_p = "char";  
  39.         printf("主函式 區域性變數 : \n");  
  40.         printf("local1 : %p \n", &local1);  
  41.         printf("local2 : %p \n", &local2);  
  42.         printf("local3 : %p \n", &local3);  
  43.         printf("const1 : %p \n", &const1);  
  44.         printf("主函式 指標變數 : \n");  
  45.         printf("p1 : %p \n", p1);  
  46.         printf("全域性變數 : \n");  
  47.         printf("global1 : %p \n", &global1);  
  48.         printf("global2 : %p \n", &global2);  
  49.         printf("global3 : %p \n", &global3);  
  50.         printf("主函式