Appearance
指標的應用
雖然說了不少指標的基礎,但是很多時候,真正困難的是各種指標的使用。
動態記憶體管理 (Dynamic Memory Allocation)
在 C 語言中,手動管理記憶體是通往高階開發者的必經之路。如果說之前的指標是「找到現有的房子」,那麼動態記憶體管理就是「根據需求臨時蓋一棟房子」。
進階: 記憶體的位置差異
動態請求記憶體的位置是在 heap,使用 first fit 找合適的位置,類似於拼拼圖時,找到合適的一塊就直接放下去,記憶體是找到一塊長度足夠的就劃分。
而一般的變數宣告記憶體是存在 stack,大致上就是看成是一行一行執行,到哪裡就在當前的 stack 頂端多疊一塊。
優缺點
手動管理記憶體雖然增加了心智負擔,但帶來了巨大的好處:
- 更大的空間: 想比一般陣列宣告
的極限,動態宣告的極限是 - 合適的大小: 如果使用傳統做法,對於從 10 個到 1e5 個的內容,只能選擇宣告一個大的陣列(1e5),時常浪費空間;動態做法則能夠根據需求劃分剛好的空間
權力越大,責任越大。不當的指標操作是資安漏洞的主要來源:
- 懸空指標 (dangling pointer): 指標指向一個已經被釋放的變數的位址,可以理解為住戶搬走了,但登記的各種資料還在
- 記憶體洩漏 (memory leak): 指標被更改了,但是指向的變數沒有被釋放,可以理解為買了一個包裹,但忘了領,堆在物流中心
- 請求失敗: 要了一塊記憶體,但失敗了,卻直接開始使用
- 可讀性: 過多的 malloc/free 夾雜在邏輯中,會讓程式碼變得像迷宮一樣難以追蹤。
核心函數工具箱 (<stdlib.h>)
| 函數 | 功能 | 備註 |
|---|---|---|
malloc | 請求一塊連續空間 | 不保證內容,裡面是殘留的垃圾值 |
calloc | 請求一塊空間,並自動歸零 | 格式為 (個數, 大小) |
realloc | 調整已申請空間的大小 | 可能會把資料搬到全新的地址 |
free | 釋放記憶體歸還給系統 | 釋放後應立即將指標設為 NULL(也就是 0) |
記憶體請求有可能失敗,因此在使用前應使用
if檢查。
實戰示範
c
// sizeof: int 的 byte 數
int *p1 = malloc(4*sizeof(int)); // 4 int 的陣列
free(p1);
p1 = NULL; // 防止程式誤用已釋放的記憶體
int *p2 = calloc(3, sizeof(int));// {0, 0, 0}
if (p2) p2[1] = 1;
int *tmp = realloc(p2, 4*sizeof(int)); // 擴充成四個
if (tmp) { // 分配成功
p2 = tmp; // 更新地址
// 此時內容為 {0, 1, 0, 未定義}
}
else { // 分配失敗
// tmp = NULL
// p2 沒有釋放
free(p2);
p2 = NULL;
}指標的指標示範
3x3 array example
c
int **ptr = NULL;
ptr = malloc(3 * sizeof(int*));
if (ptr)
for (int i = 0; i < 3; i++)
ptr[i] = malloc(3 * sizeof(int));多層指標需要多層
free,否則會有懸空指標
指標與函數
前面在 陣列 部分有說過,因為陣列名是指標,因此作為參數傳遞會讓 function 可以更改其中的數值,解決函數回傳只能有一個數值的痛點與傳遞參數過大的問題,降低性能消耗與記憶體使用(尤其在遞迴情境)。
遞迴範例
我寫過比較難的功課: 算 fibonacci 數
題幹:
給 k: [1, 93],求第 k 個費波那契數
formula:
fib(2k) = fib(k) * [2 * fib(k + 1) - fib(k)]
fib(2k + 1) = fib(k) * fib(k) + fib(k + 1) * fib(k + 1)測試程式:
c
#include <stdio.h>
typedef unsigned long long uint64_t;
void fib_fast_doubling(unsigned k, uint64_t *f2k, uint64_t *f2k1);
int main()
{
unsigned k;
scanf("%u", &k);
uint64_t f2k, f2k1;
fib_fast_doubling(k / 2, &f2k, &f2k1);
printf("%llu", (k & 0x1) ? f2k1 : f2k);
return 0;
}實作 fib_fast_doubling 函數,並在 1s 與 5000KB 內完成。
code
c
void fib_fast_doubling(unsigned k, uint64_t *f2k, uint64_t *f2k1)
{
if(k == 0)
{
*f2k1 = 1;
*f2k = 1;
return;
}
else if(k == 1)
{
*f2k1 = 2;
*f2k = 1;
return;
}
uint64_t ori,ori1;
if(k%2)
{
fib_fast_doubling(k/2,f2k,f2k1);
ori1 = *f2k1;
fib_fast_doubling(k/2+1,f2k,f2k1);
ori = *f2k;
*f2k = ori1 * (2 * ori - ori1);
*f2k1 = ori1 * ori1 + ori * ori;
}
else
{
fib_fast_doubling(k/2,f2k,f2k1);
ori1 = *f2k * *f2k + *f2k1 * *f2k1;
ori = *f2k * (2 * *f2k1 - *f2k);
*f2k = ori;
*f2k1 = ori1;
}
}