之前有個老鐵問我,數組就是指針嗎?
我當時就愣了一下,平時都不記這些概念,一時半會還不知道怎么回答。
后面我針對性地去研究了一下,發現數組就是指針,一種誤解。
咱們看一段代碼:
char arr[10];char *p = arr;printf("sizeof(arr) = %d\\n", sizeof(arr)); // 輸出多少?printf("sizeof(p) = %d\\n", sizeof(p)); // 輸出多少?
你可能會信心滿滿地說:"都是10啊!"
但實際結果是:
sizeof(arr) = 10sizeof(p) = 4 // 32位系統
你看,這就是典型的"數組就是指針"**誤解帶來的坑。今天就用我真實的項目代碼,把這個概念給你講清楚。
數組是一塊連續的內存空間,用來存儲同類型的多個數據。
指針是一個變量,用來存儲地址。
它們是兩個完全不同的東西!
在我們項目的掉電保存參數里,就有典型的數組定義:
STU_DTC dERP[STU_DTC_NUM]; // 定義一個數組這段代碼在內存中開辟了 連續的STU_DTC_NUM 個 STU_DTC 大小的空間。
而指針呢?
STU_DTC *p;這只是定義了一個指針變量,本身只占4字節(32位系統)或8字節(64位系統),里面存的只是一個地址而已。
剛才那個例子,sizeof(arr) 和 sizeof(p) 為什么不一樣?
因為數組名在 sizeof 運算符中,不會被退化為指針!
看我們項目的實際代碼:
I2C_PageWrite(STU_DEVICEPARA_OFFSET, (unsigned char*)(&dERP), sizeof(dERP));這里的 sizeof(dERP) 獲取的是整個數組的大小(STU_DTC_NUM * sizeof(STU_DTC)),而不是指針的大小!
如果把數組名賦給指針呢?
STU_DTC *p = dERP; // 數組名在這里退化為指針printf("%d", sizeof(p)); // 輸出4或8,指針的大小
看到區別了嗎?
這是最容易讓人誤解的地方!
看我們項目里的函數聲明:
// 來源于 WG01項目 hal/hal_oled.c:844void hal_Oled_ShowPicture(unsigned char x, unsigned char y,unsignedcharsizex,unsignedchar sizey,constunsignedcharBMP[],unsignedchar mode)
注意參數 BMP[],雖然寫的是數組形式,但實際上它等價于 const unsigned char *BMP!
數組作為函數參數時,會自動退化為指向首元素的指針,并且丟失長度信息!
這就是為什么C語言的字符串函數都要帶一個長度參數,或者用 \\0 結尾——因為函數根本不知道數組有多大!
在大多數表達式中,數組名會被隱式轉換為指向首元素的指針:
int arr[10];int *p = arr; // arr 被轉換為 &arr[0]int *q = arr + 1; // arr 被轉換為 &arr[0],然后 +1
但是!這只是隱式轉換,不是數組"就是"指針!
看這段代碼:
int arr[10];int *p = arr;printf("%p\\n", arr); // &arr[0] 的值,類型是 int*printf("%p\\n", &arr); // 整個數組的地址,類型是 int(*)[10]printf("%p\\n", &arr[0]); // &arr[0] 的值,類型是 int*
arr 和 &arr 的值可能一樣,但類型完全不同!
arr 的類型是 int*(指向首元素的指針)&arr 的類型是 int(*)[10](指向整個數組的指針)在我們項目的 app.c 里就有這樣的用法:
// 來源于 WG01項目 app/app.c:107pStuSystemMode = &stu_Sysmode[sysMode];
這里取的是數組元素的地址,不是數組的地址。
前面已經說過了,這里再強調一次:
int arr[10];int *p = arr;sizeof(arr); // 10 * sizeof(int) = 40sizeof(p); // 4 或 8(指針大小)
在我們項目的 mt_wifi.c 里,清空串口緩沖區就是這樣做的:
// 來源于 WG02項目 MT/mt_wifi.c:121memset(Usart2RxBuff, 0, sizeof(Usart2RxBuff));
如果 Usart2RxBuff 是指針而不是數組,sizeof 就只會返回指針大小,清空就會出問題!
int arr[10];int arr2[10];arr = arr2; // 錯誤!數組名不能被賦值
因為數組名是一個常量(代表數組的起始地址),不是變量!
但是指針可以:
int *p1, *p2;p1 = p2; // OK
int arr[10];int *p = arr;int x = 100;p = &x; // OK,指針可以改變指向arr = &x; // 錯誤!數組名不能改變
在我們項目的菜單系統里,有大量指針運算的代碼:
// 來源于 WG01項目 app/app.c:1332-1346pMenu->pLase = pMenu + (pMenuIdx - 1);pMenu->pNext = pMenu + 1;(pMenu + i)->pLase = pMenu + (i - 1);(pMenu + i)->pNext = pMenu + (i + 1);
這里的 pMenu + 1 不是加1個字節,而是加1個元素的大小!
因為 pMenu 是結構體指針,pMenu + 1 會跳過整個結構體的大小。
這就是指針算術運算的規則:指針加N,跳過N個元素的大小。
int *p = malloc(10 * sizeof(int)); // OKint arr[10] = malloc(...); // 錯誤!數組不能動態分配
int arr[10];int i = 3;arr[i] 等價于 *(arr + i)
很多人覺得這證明數組就是指針——錯!
這只是C語言的語法糖,arr[i] 會被編譯器轉換成 *(arr + i),但 arr 在這里被隱式轉換為指針了。
void func(int arr[]) { ... }int a[10];func(a);
參數 arr[] 看起來像數組,但編譯器會把它當作 int *arr 處理。
這就是為什么你可以在函數里寫 arr++,但如果 arr 真是數組,這是不允許的!
int arr[5] = {1, 2, 3, 4, 5};printf("%d\\n", sizeof(arr)); // 20printf("%d\\n", sizeof(&arr)); // 4 或 8printf("%d\\n", sizeof(arr[0])); // 4printf("%d\\n", sizeof(&arr[0])); // 4 或 8printf("%d\\n", sizeof(*arr)); // 4printf("%d\\n", sizeof(*&arr)); // 20
來,一個一個解釋:
sizeof(arr) = 20 → 整個數組的大小,5個intsizeof(&arr) = 4或8 → 數組的地址,是個指針sizeof(arr[0]) = 4 → 第一個元素的大小sizeof(&arr[0]) = 4或8 → 第一個元素的地址,是個指針sizeof(*arr) = 4 → arr 被轉換為 &arr[0],解引用得到元素sizeof(*&arr) = 20 → &arr 取數組地址,* 解引用得到整個數組第6個是最難理解的:&arr 是"指向數組的指針",類型是 int(*)[5],解引用后得到的是數組本身,所以 sizeof 返回整個數組的大小!
| 本質 | ||
| sizeof | ||
| 取地址 | &arr | &p |
| 賦值 | ||
| 函數參數 | ||
| 運算 |
記住一句話:數組在某些表達式中會被轉換為指針,但數組不是指針!
// 好的做法extern int arr[10]; // 聲明為數組void func(int *p, int len); // 參數用指針 + 長度// 避免void func(int arr[]); // 看起來像數組,實際是指針
// 來源于 WG02項目 MT/mt_wifi.c:121-123#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))memset(Usart2RxBuff, 0, sizeof(Usart2RxBuff));memset(Usart2TxDMAMapBuff, 0, sizeof(Usart2TxDMAMapBuff));memset(WifiSsid, 0, sizeof(WifiSsid));
// 來源于 WG01項目 hal/hal_oled.c:844void hal_Oled_ShowPicture(..., const unsigned char BMP[], ...){// BMP只是指針,無法知道長度!// 需要額外參數或約定結束標志}
面試的時候,如果你的回答是"數組就是指針",基本就掛了。
記住:數組和指針是兩個不同的概念,只是在某些場景下可以互相轉換使用。
理解了這一點,在看一些復雜代碼是才能游刃有余!
end

下面是更多無際原創的個人成長經歷、行業經驗、技術干貨。
1.電子工程師是怎樣的成長之路?10年5000字總結
2.如何快速看懂別人的代碼和思維
3.單片機開發項目全局變量太多怎么管理?
4.C語言開發單片機為什么大多數都采用全局變量的形式?
5.單片機怎么實現模塊化編程?實用程度讓人發指!
6.c語言回調函數的使用及實際作用詳解
7.手把手教你c語言隊列實現代碼,通俗易懂超詳細!
8.c語言指針用法詳解,通俗易懂超詳細!