為了更好地理解本篇文章的知識內容,讀者可以將以下文章作為補充知識進行閱讀?:
C語言————原碼 補碼 反碼 (超絕詳細解釋)-CSDN博客
C語言————二、八、十、十六進制的相互轉換-CSDN博客
C語言————斐波那契數列的理解和運用-CSDN博客
目錄
1. 內存和指針(地址)
1.1 內存的介紹
1.1.1內存的劃分
1.2 指針和地址
2. 指針變量和地址
2.1 取地址操作符&
2.2 指針變量
2.2.1 指針變量的定義
2.2.2 指針變量的類型
2.2.3 指針變量的大小?
2.3 解引用操作符*
?2.4 指針變量類型的意義
3. 指針計算
3.1 指針+-整數
3.2 指針-指針
3.3 指針的關系運算
4.野指針
4.1 野指針的成因和解決方法
4.1.1 指針未初始化
4.1.2 指針越界訪問
4.1.3 指針指向的空間釋放
5. void指針和assert斷言
5.1 void指針
5.2 assert斷言
1. 內存和指針(地址)
1.1 內存的介紹
在計算機中,有各種各樣的數據,他們的存儲需要在內存中劃分空間,計算機中的內存空間大小是有限的。如果把數據比作水,內存就是用以承載水的容器,而我們知道在生活中容器的大小都是有限的。因此我們可以 更好地理解內存之于數據的意義。
1.1.1內存的劃分
一個整型變量a= 10存儲在程序中需要占據4個字節的內存空間大小,而數據的單位是多種多樣的,那我們在內存中應該按照何種單位進行空間劃分呢?
為了內存空間的高效管理,內存被劃分為一個個的內存單元,而每個內存單元的大小為1字節。
?
其中,一個bit位可以存儲一個二進制的0或1,一個字節可以存儲8個bit位,即一個內存單元能存儲8個bit位。?
在內存中存儲的數據需要通過CPU的處理,那么CPU又是如何讀取這些數據的呢?
1.2 指針和地址
我們打個比方,當我們在入住一個酒店時,服務員會給我們對應的 房號和房卡,這樣我們就能快速找到對應的房間。CPU和內存中的數據傳輸也是同樣的道理,他們之間通過很多的地址總線進行連接,每根線只有兩態,表示0或1(聯想到二進制),那么通過地址總線不同的脈沖組合形成的這樣一個二進制數字,就是對應數據的地址編碼,即地址。
?在C語言中,我們將這樣的地址起名為指針。
所以我們可以理解為:
內存單元的編號 == 地址 == 指針
2. 指針變量和地址
2.1 取地址操作符&
我們在學習scanf函數時知道,scanf函數除格式化字符串以外的參數代表的都是地址。
當我們在創建變量的時候,他會向內存申請空間,我們想知道他具體的地址編號時就需要用到操作符&,示例如下:
?
?如圖創建的整型變量a,通過查看內存,我們知道他的地址即指針為0x00000099588FFB14-0x00000099588FFB17(x64環境下),共四個字節;但如果我們對a的地址進行打印的話(x86環境下,更加便于查看),結果又是怎樣的呢?
?
我們會發現,他只打印了一個地址編號,這是因為一個數據進行存儲時,他的內存空間都是連續的,打印的往往是最低的那個地址編號,進而根據數據的內存大小,從低往高訪問對應數據。
?
經過多次嘗試我們會發現,每一次變量的地址都是在發生變化的,這是因為在每次運行程序時,操作系統的內存分配情況存在差異,所以分配給變量的具體內存地址是不同的。
2.2 指針變量
2.2.1 指針變量的定義
那么通過取地址操作符&得到的地址我們又該將他存儲在哪呢?為了方便提取這些指針的數據,C語言中用指針變量作為他的容器。如:
#include <stdio.h>
int main()
{int a = 10;int * pa = &a;//取出a的地址并存儲到指針變量pa中return 0;
此時的pa就是一個指針變量,而他的類型為int *;?
指針變量也是?種變量,這種變量就是?來存放地址的,存放在指針變量中的值都會理解為地址。
?在C語言中,地址就是指針,指針就是地址。
2.2.2 指針變量的類型
由上我們知道的一種指針變量類型為int *,我們應該怎么去理解他呢?
我們單獨看int * pa = &a這段語句可以知道,a為整型變量,pa存儲的是a的地址。由此知道:
int 代表pa存儲的指針所指向的數據a的類型(整型),* 表明pa為指針變量。
a和pa分別都在內存中劃分了屬于他們自己的空間。
?
那么字符類型的變量a,他的地址又該放在上面類型的指針變量中呢?
我們可以進一步推導如下:
int main()
{char a = '2';char* pc = &a;//字符指針pc,類型為char *return 0;
}
2.2.3 指針變量的大小?
在介紹內存中,我們知道地址的編號是由地址總線輸出的信號轉換得到的,32位機器假設有32根地址總線,他們產生的二進制序列作為一個地址,那么一個地址就是32個bit位,需要4個字節的存儲空間,指針變量的大小就是 4個字節。
同理64位機器,假設有64根地址線,一個地址就是64個二進制位組成的二進制序列,存儲起來就需要 8個字節的空間,指針變量的大小就是8個字節。
我們可以輸出如下的代碼進行測試:
#include <stdio.h>
//指針變量的??取決于地址的??
//32位平臺下地址是32個bit位(即4個字節)
//64位平臺下地址是64個bit位(即8個字節)
int main()
{printf("%zd\n", sizeof(char *));printf("%zd\n", sizeof(short *));printf("%zd\n", sizeof(int *));printf("%zd\n", sizeof(double *));return 0;
}
在x86環境下,即32位操作系統
?
在x64環境下,即64位操作系統?
?
結論:
1.?32位平臺下地址是32個bit位,指針變量大小是4個字節
2. 64位平臺下地址是64個bit位,指針變量大小是8個字節
注:指針變量的大小和類型是無關的,同樣指針類型的變量,在相同平臺下,大小都是相同的
2.3 解引用操作符*
那么對于指針變量,他們應該如何使用呢?這里我們將介紹一個關鍵的操作符——解引用操作符*
?他相當于是一把鑰匙,指針變量是對應的地址,指針變量指向的數據相當于被存儲在對應的地址,但我們無法直接操作他,因此需要通過鑰匙打開這道壁壘,這樣我們在不直接使用數據變量時,也能對數據進行相應的操作。示例如下:
#include <stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0;//找到變量a,并通過*打開操作他的權限return 0;
}
?我們會發現,通過解引用,*pa就相當于變量a,我們能夠對他進行重新賦值
?2.4 指針變量類型的意義
由2.1中我們知道,指針變量會存儲數據空間中最小的地址編號,整型指針變量解引用時,他會向上訪問四個字節的內存空間,我們思考一下,如果我們使用字符指針變量對&a進行訪問,能得到正確的數據么?
在int *整型指針下,我們打印讀取的整型變量數據是正確的(十六進制11223344轉為十進制為287454020?)
?當我們使用char *字符指針對整型變量n進行讀取時,我們發現,他僅讀取了n內存空間中的一個內存單元,數據為十六進制的44,轉為十進制為68。
在這里我們可以發現,不同的指針變量所訪問的內存空間大小也是不一樣的,因此學習指針變量的類型也是十分關鍵的。
注:在進行地址存儲時,指針變量的類型應該和地址的類型相對應,在代碼中我們可以看到(char*)&n,由于&n的類型為int *,我們用char *的指針變量接收他,兩者類型不同,為了使指針變量pc能夠順利存儲n的地址,我們需要對&n進行強制轉換,如果不進行強制轉換,編譯器會發出警告。(編譯器會進行隱式轉換類型)
結論:指針的類型決定了對指針解引?的時候有多大的權限(?次能操作幾個字節)。
如: char* 的指針解引用就只能訪問?個字節,而?int* 的指針的解引用就能訪問四個字節。
3. 指針計算
3.1 指針+-整數
我們觀察如下代碼的運行結果:
我們可以發現,整型指針變量+1,他的地址跳過了4個字節;字符指針變量+1,他的地址跳過了1個字節。
指針+1,就是跳過1個指針指向的元素。指針可以+1,那也可以-1。
跳過一個指針的空間大小就取決于指針的類型
3.2 指針-指針
此運算的前提條件:
- 參與減法運算的兩個指針必須指向同一數組中的元素(或數組最后一個元素的下一個位置)
- 兩個指針必須指向相同類型的數據(即指針變量類型一致)
運算結果:
結果是一個ptrdiff_t
類型(在<stddef.h>
中定義的有符號整數類型),表示兩個指針所指向元素之間的元素個數差,而不是字節數差。
?我們觀察如下代碼:
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int* p1 = &arr[9];printf("%d\n", (int)(p1 - p));//p1 - p為ptrdiff_t類型,%d無法讀取,需要強制轉換return 0;
}
?指針求差他們的結果就是兩個指針之間內存的元素個數,如下圖,兩個箭頭之間的元素有1,2,3,4,5,6,7,8,9。共9個整型元素,故輸出9。
3.3 指針的關系運算
我們知道內存的地址編碼是從低到高依次排布的,因為指針是可以用來比較大小的。
常見的關系運算符包括:== 、!= 、< 、> 、<= 、>= 。
這些關系符構建的指針關系運算可以作為語句的判斷條件;
指針的關系運算常用于數組遍歷或內存區間判斷。
int arr[5] = {1,2,3,4,5};
int *p;// 遍歷數組:當p未超過數組最后一個元素時繼續循環
for (p = &arr[0]; p < &arr[5]; p++) {printf("%d ", *p);
}
4.野指針
- 可能立即觸發程序崩潰(如段錯誤)
- 可能暫時正常運行,但在后續操作中引發錯誤
- 可能修改無關內存區域,導致數據損壞或程序邏輯錯誤
- 可能觸發安全漏洞,被用于緩沖區溢出等攻擊
4.1 野指針的成因和解決方法
4.1.1 指針未初始化
#include <stdio.h>
int main()
{ int *p;//局部變量指針未初始化,默認為隨機值*p = 20;return 0;
}
局部變量p未進行初始化,此時的p就是野指針。?
解決方法:
4.1.2 指針越界訪問
#include <stdio.h>
int main()
{int arr[10] = {0};int *p = &arr[0];int i = 0;for(i=0; i<=11; i++){//當指針指向的范圍超出數組arr的范圍時,p就是野指針*(p++) = i;}return 0;
}
當指針指向的范圍超出數組arr的范圍時,p就是野指針?。
4.1.3 指針指向的空間釋放
#include <stdio.h>
int* test()
{int n = 100;return &n;
}
int main()
{int*p = test();
printf("%d\n", *p);return 0;
}
當程序運行函數test()結束后,由于n為局部變量,他在內存中所占的空間就會被銷毀,導致指針p無法指向具體的變量,成為了野指針。
解決方法:
1. 指針變量不再使?時,及時置NULL,指針使?之前檢查有效性
2. 如造成野指針的第3個例?,不要返回局部變量的地址。
———————————————————————————————————————————
持續更新中
5. void指針和assert斷言
5.1 void指針
5.2 assert斷言
打怪升級中.........................................................................................................................................