高階C語言2

sleeeeeping發表於2024-05-05
計算機的記憶體長什麼樣子?

1、計算機中的記憶體就像一疊非常厚的“便籤”,一張便籤就相當於一個位元組的記憶體,一個位元組有8個二進位制位

2、每一張“便籤”都有自然排序的一個編號,計算機是根據便籤的編號來訪問、使用"便籤"

3、CPU會有若干個金手指,每根金手指能感知高低電平,高電平轉換成1,低電平轉換成0,我們常說的32位CPU指的是CPU有32個金手指用於感知電平,並計算出“便籤”的編號

便籤的最小編號:
00000000 00000000 00000000 00000000 = 0
便籤的最大編號:
11111111 11111111 11111111 11111111 = ‭4294967295‬
所以32位CPU最多能使用4Gb的記憶體

4、便籤的編號就是記憶體的地址,是一種無符號的整數型別

什麼是指標:

1、指標(pointer)是一種特殊的資料型別,使用它可以用於定義指標變數,簡稱指標

2、指標變數中儲存的是記憶體的地址,是一種無符號的整數型別,

3、透過指標變數中記錄的記憶體地址,我們可以讀取對應的記憶體中所儲存的資料、也可以向該記憶體寫入資料

4、可以透過 %p 顯示指標變數中儲存的地址編號

如何使用指標:

定義指標變數

型別* 指標變數名;

int num;
char n;
double d;
int* nump;	//	訪問4位元組
char* p; 	//	訪問1位元組
double* doublep;	//	訪問8位元組
long* lp;	//	訪問4/8位元組

1、一個指標變數衝只記錄記憶體中某一個位元組的地址,我們把它當做一塊記憶體的首地址,當使用指標變數去訪問記憶體時具體連續訪問多少個位元組,指標變數的型別來決定。

2、普通變數與指標變數的用法上有很大區別,為了避免混用,所以指標變數一般以p結尾,以示區分

3、指標變數不能連續定義,一個*只能定義一個指標變數

int n1,n2,n3;	//	n1 n2 n3都是int
int* p1,p2,p3;	//	int *p1,p2,p3  p1是int*  p2 p3是int
int* p1,*p2,*p3; //	p1 p2 p3都是int*

4、指標變數與普通一樣,預設值是隨機的(野指標),為了安全儘量給指標變數初始化,如果不知道該初始化為多少,可以先初始化為NULL(空指標)

int* p;	//	野指標
int* p = NULL;	//	空指標
給指標變數賦值:

指標變數 = 記憶體地址

所謂的給指標變數賦值,其實就是往指標變數中儲存一個記憶體地址,如果該記憶體地址是非法的,當使用該指標變數去訪問記憶體時會出現 段錯誤

//	儲存堆記憶體地址
int* p = malloc(4);

//	儲存指向num所在記憶體地址(stack\data\bss)
int num;	//	stack
int* p = #
注意:num變數的型別必須與p型別相同
指標變數解引用:

*指標變數名;

給指標變數賦值就是讓指標指向某一個記憶體,對指標變數解引用就是根據指標變數中儲存的記憶體編號,去訪問該記憶體,具體連續訪問多少個位元組由指標變數定義時的型別決定

 int num = 100;

    //  定義指標變數
    int* p = NULL;

    //  給指標變數賦值
    p = #

    //  檢視指標變數的值
    printf("%p\n",p);                                        
    
    //  對指標變數解引用
    printf("%d\n",*p + 10);
    *p = 88; 
    printf("%d\n",num);

如果指標變數中儲存的是非法的記憶體地址,當程式執行到該指標變數解引用時,會出現段錯誤

    //  定義指標變數
    int* p = NULL;
	
	*p = 100;	//	非法訪問記憶體 會段錯誤
驗證指標變數中儲存的就是一個整數
#include <stdio.h>                                           
void func(unsigned long addr) {
    *(int*)addr = 88; 
}
int main() {
    int num = 10; 

    func(&num);

    printf("num=%d\n",num);
}

為什麼要使用指標:
1、函式之間需要共享變數

​ 函式之間的名稱空間是相互獨立,並且是以賦值的方式進行單向值傳遞,所以無法透過普通型別形參傳參來解決共享變數的問題

​ 全域性變數雖然可以在函式之間共享,但是過多地使用全域性變數容易造成命名衝突和記憶體浪費

​ 使用陣列是可以共享,但是需要額外傳遞長度

​ 因此,雖然函式之間的名稱空間是相互獨立的,但是所使用的是同一條記憶體,也就是說記憶體空間是同一個,所以使用指標可以解決函式之間共享變數的問題

#include <stdio.h>
void func(int* p) {
    printf("func p=%p,*p=%d\n",p,*p);
    *p = 88; 
    printf("func p=%p,*p=%d\n",p,*p);
}
int main() {
    int num = 66; 
    func(&num);
    printf("main &num=%p ",&num);                            
    printf("main:%d\n",num);
}

當函式需要返回兩個以上的資料時,光靠返回值滿足不了,可以透過指標共享一個變數,藉助該輸出型引數,返回多個資料

//  put_p輸出型引數
int func(int* put_p) {
    *put_p = 20; 
    return 10; 
}

int main() {
    int num = 0;
    int ret1 = func(&num);
    printf("ret1 = %d ret2=%d\n", ret1, num);                  
}
2、使用指標可以提高函式之間的傳參效率

​ 一個指標變數佔記憶體 4 | 8 位元組

​ 函式之間傳參是以記憶體複製的方式進行,當引數的記憶體位元組數比較大(大於4位元組時)的時候,傳參的效率就會比較低下,此時使用指標傳參可以提高傳參效率

#include <stdio.h>                                           
void func(long double* f) {
}
int main() {
    long double f = 3.14;
    for (int i = 0; i < 1000000000; ++i) {   
        func(&f);
    }   
}

3、使用堆記憶體時,必須與指標變數配合

​ 堆記憶體無法像棧、資料段、bss段那樣給記憶體取名字,透過標準庫、作業系統提供的管理堆記憶體的介面函式,來操作堆記憶體時,是直接返回堆記憶體的地址給呼叫者,因此必須使用指標變數配合才能訪問堆記憶體

malloc 
realloc
calloc

學習建議:指標就是一種工具,目的是完成任務,而使用指標是有危險性,所以除了以上三種情況需要使用指標以外,不要輕易使用指標

使用指標需要注意的問題:

空指標:

​ 指標變數中儲存的NULL,那麼它就是空指標

​ 作業系統規定程式不能訪問NULL指向的記憶體,只要訪問必定段錯誤

​ 當函式的返回值是指標型別時,函式執行出錯時一般返回NULL,作為函式的錯誤標誌

​ NULL也可以作為初始值給指標變數初始化

#include <stdio.h>
int* func(void) {
    return NULL;    	//表示執行出錯                                  
}
int main() {
    int* p = NULL;
    int num= 10;
    p = &num;
    printf("%d\n,",*p);	//	必定段錯誤
}

如何避免空指標產生的段錯誤?

對來歷不明的指標進行解引用前先判斷是否是空指標

1、當自己寫的函式的引數中有指標型別時,在使用該引數時,需要先判斷是否是空指標再使用

2、當使用別人提供的函式時,它的返回值型別是指標型別時,獲取返回值後,也需要先判斷是否是空指標再使用

int* p = malloc(4);
if(NULL == p) {
    printf("記憶體申請失敗\n");
} else {
    *p = 100;
}
if(NULL == p) // 正確寫法
if(p == NULL)	// 容易漏寫= 變成賦值 錯誤寫法
if(!p) {	//	絕大多數系統中 NULL 是0,少數系統中是1
    //	通用性不夠強
}
注意:必須匯入 stdio.h 後 NULL才可以使用
野指標:

​ 指標變數中儲存的地址,無法確定是哪個地址、是否是合法地址,此時該指標就稱為野指標

對野指標解引用的後果:

​ 1、一切正常,剛好指標變數中儲存的是空閒且合法的地址

​ 2、段錯誤,剛好指標變數中儲存的是非法的地址

​ 3、髒資料,儲存的是其它變數的地址

野指標比空指標的危害性更大

​ 1、空指標可以透過if(NULL==p)判斷出來,但是野指標一旦產生,無法透過程式碼判斷,只能透過經驗人為判斷

​ 2、野指標就算暫時不暴露問題,不代表沒有問題,後期可能隨時暴露

如何避免產生野指標:

​ 所有的野指標都是人為造成的,因此想要避免野指標的危害,只能透過不人為製造野指標

​ 1、定義指標變數時一定初始化

​ 2、函式不要返回區域性變數、塊變數的地址,因為當函式執行結束後,該地址指向的記憶體就會被自動銷燬回收,如果非要接收,就接受到了一個野指標

​ 3、與堆記憶體配合的指標,當堆記憶體手動釋放後,該指標要及時置空

int* p = malloc(4);
*p = 100;
free(p);
p = NULL;
if(NULL==p)

相關文章