前言:文章裡的指針指一個內存地址,指針變量指的是存儲一個內存地址的變量。
有的地方可能會把兩者混為一談,但本文不採用那種講法。
我們知道,&i表示獲取i的內存地址,這就叫一個指針。我們可以聲明一個變量專門存儲它,這叫指針變量。
int i;
int* p = &i;
printf("%p", p);
這說的是聲明一個p,類型是int*。還可以有char*等類型。
(這個可以寫成int*p、int *p、int* p、int * p都行)
然後,我們可以用*符號解引用這個指針變量,獲取這個指針指的內容。比如
int i = 666;
int* p = &i;
printf("p的內容:%p,這是一個內存地址,對應的這塊內存存儲的內容是%d",p, *p);
運行結果
p的內容:000000000062FE14,這是一個內存地址,對應的這塊內存存儲的內容是666
注意不要寫錯了,*p是對p解引用,&p是獲取p的內存地址!
我們就可以用這個,去讀取i的值、修改i的值。這就是指針的用處。
那麼,一個指針,實際上就是一個長度為32位或者64位(4byte或者8byte)的地址,那麼不是隻要隨便整一個能放得下它的變量就可以存放地址了嗎?
為什麼指針變量還要有int*、char*等不同類型呢?
比如說,我們就搞一個指針類型,表示所有指針不好嗎?
實際上,int*、char*等,內容都是一樣的,都是一個地址(都是4byte或8byte)。
只是,對指針的各種操作,編譯器都需要知道這個指針指向的元素是多少字節一個元素:
在操作*p的時候,我們得知道所指向的這個元素,屬於它的內存有多大,這樣才能知道該從這個地址開始往後在多大的地方進行操作(讀取/寫入多少個字節);
指針的+1、++操作跳轉到下一個元素(比如說數組跳到下一個元素就是下一個數組項)功能,也需要知道下一個的話要往後跳多少。
所以,就有了各種int*、char*等類型,用來表示指向的是int、char,然後對應的指針做++就分別是向後4字節、向後1字節。
但是實際上它們都是一樣的,比如
int a = 666;
int *p = &a;
char *q = &a;
printf("p:%p q: %p", p, q);
可以看到p和q的內容是一樣的
它們並不會存儲說自己是int*還是`char*`等。c的變量都是很單純的,內存裡只存儲值,類型是由編譯器管理的。
所以關鍵就在於,c裡面引用一個變量,使用的是它起始位置的內存地址,但是並沒有記錄它的結束位置。這就是一切指針混亂的源頭。
這就導致需要用int*等類型作為“標籤”,告訴編譯器如何操作,提供一點點保護,但是說到底還是由程序員自己來管理指針指向的數據有多大。
實際上,在c的前身b語言中,指針甚至沒有類型,導致各種危險的野指針到處跑。
由於當時設備資源有限,再加上當時編程任務的要求更為底層,c並沒有徹底改革指針的結構,僅僅只是加上int*、char*等“類型”,從現在的角度,有點像是發現這個問題後,一種水多加面、面多加水的和稀泥行為,一種打補丁的無奈之舉。
在這之後的更現代的語言已經通過各種數據結構,去更好地管理內存,像是Java、Python等更是直接用對象去包裝數據,杜絕了指針的使用。
這些int*等的保護機制很脆弱,我們可以把指針強制轉換為其它類型,然後用在別處,可能可以用在一些更加靈活的地方。(當然,這樣很容易造成野指針等問題)
比如
char a;
int *p = (int*)&a;
當然把char*轉為int*本質上也就只是告訴編譯器,之後是4個字節地往後跳罷了,值是不會變的。
(講這些並不是說要這樣用,別用!別搞野指針!只是瞭解了這些,能更全面、更完整地理解指針)