一個printf(結構體指針)引發的血案
一、前言
1. 為什么寫這篇文章
在上周六,我在公眾號里發了一篇文章:C語言指針-從底層原理到花式技巧,用圖文和代碼幫你講解透徹,以直白的語言、一目了然的圖片來解釋指針的底層邏輯,有一位小伙伴對文中的代碼進行測試,發現一個比較奇怪的問題。我把發來的測試代碼進行驗證,思考好久也無法解釋為什么會出現那么奇怪的打印結果。
為了整理思路,我到陽臺抽根煙。晚上的風很大,一根煙我抽了一半,風抽了一半,可能風也有自己的煩惱。后來一想,煙是我買的,為什么讓風來抽?于是我就開始抽風!不對,開始回房間繼續抽代碼,我就不信,這么簡單的 printf 語句,怎么就搞不定?!
于是就有了這篇文章。
2. 你能得到什么收獲函數參數的傳遞機制;可變參數的實現原理(va_list);printf 函數的實現機制;面對問題時的分析思路。
友情提醒:文章的前面大部分內容都是在記錄思考問題、解決問題的思路,如果你對這個過程不感興趣,可以直接跳到最后面的第四部分,用圖片清晰的解釋了可變參數的實現原理,看過一次之后,保管你能深刻記住。
3. 我的測試環境3.1 操作系統
每個人的電腦環境都是不一樣的,包括操作系統、編譯器、編譯器的版本,也許任何一個小差別都會導致一些奇奇怪怪的的現象。不過大部分人都是使用 Windows 系統下的 VS 集成開發環境,或者 Linux 下的 gcc 命令行窗口來測試。
我一般都是使用 Ubuntu16.04-64 系統來測試代碼,本文中的所有代碼都是在這個平臺上測試的。如果你用 VS 開發環境中的 VC 編譯器,可能在某些細節上與我的測試結果又出入,但是問題也不大,遇到問題再分析,畢竟解決問題也是提升自己能力的最快途徑。
3.2 編譯器
我使用的編譯器是 Ubuntu16.04-64 系統自帶的版本,顯示如下:

另外,我安裝的是 64 位系統,為了編譯 32 位的可執行程序,我在編譯指令中添加了 -m 選項,編譯指令如下:
gcc -m32 main.c -o main
使用 file main 命令來查一下編譯得到的可執行文件:

所以,在測試時如果輸出結果與預期有一些出入,先檢查一下編譯器。C 語言本質上都是一些標準,每家的編譯器都是標準的實現者,只要結果滿足標準即可,至于實現的過程、代碼執行的效率就各顯神通了。
二、問題導入
1. 網友測試代碼#include <unistd.h>#include <stdio.h>#include <stdlib.h>
typedef struct { int age; char name[8];} Student;
int main(){ Student s[3] = {{1, "a"}, {2, "b"}, {3, "c"}}; Student *p = &(s[0]); printf("%d, %d ", *s, *p);}
2. 期望結果
根據上篇文章的討論,我們知道:
s 是一個包含 3 個元素數組,每個元素的類型是結構體 Student;p 是一個指針,它指向變量s,也就是說指針 p 中保存的是變量 s 的地址,因為數組名就表示該數組的首地址。
既然 s 也是一個地址,它也代表了這個數組中第一個元素的首地址。第一個元素類型是結構體,結構體中第一個變量是 int 型,因此 s 所代表的那個位置是一個 int 型數據,對應到示例代碼中就是數字 1。因此 printf 語句中希望直接把這個地址處的數據當做一個 int 型數據打印出來,期望的打印結果是:1, 1。
這樣的分析過程好像是沒有什么問題的。
3. 實際打印結果
我們來編譯程序,輸出警告信息:

警告信息說:printf 語句需要 int 型數據,但是傳遞了一個 Student 結構體類型,我們先不用理會這個警告,因為我們就是想通過指針來訪問這個地址里的數據。
執行程序,看到實際打印結果是:1, 97,很遺憾,與我們的期望不一致!
三、分析問題的思路
1. 打印內存模型
可以從打印結果看,第一個輸出的數字是 1,與預期符合;第二個輸出 97,很明顯是字符 'a' 的 ASCII 碼值,但是 p 怎么會指到 name 變量的地址里呢?
首先確認 3 個事情:
結構體 Student 占據的內存大小是多少?數組 s 里的內存么模型是怎樣的?s 與 指針變量 p 的值是否正確?
把代碼改為如下:
Student s[3] = {{1, "a"}, {2, "b"}, {3, "c"}};Student *p = s;
printf("sizeof Student = %d ", sizeof(Student));
printf("print each byte in s: ");char *pTmp = p;for (int i = 0; i < 3 * sizeof(Student); i++){ if (0 == i % sizeof(Student)) printf(""); printf("%x ", *(pTmp + i));}printf("");
printf("print value of s and p ");printf("s = 0x%x, p = 0x%x ", s, p);
printf("%d, %d ", *s, *p);
我們先畫一下數組 s 預期的內存模型,如下:

編譯、測試,打印結果如下:

從打印結果看:
結構體 Student 占據 12 個字節,符合預期。數組 s 的內存模型也是符合預期的,一共占據 36 個字節。s 與 p 都代表一個地址,打印結果它倆相同,也是符合預期的。
那就見鬼了:既然 s 與 p 代表同一個內存地址,但是為什么用 *p 讀取 int 型數據時,得到的卻是字符 'a' 的值呢?
2. 分開打印信息
既然第一個 *s 打印結果是正確的,那么就把這個兩個數據分開來打印,測試代碼如下:
Student s[3] = {{1, "a"}, {2, "b"}, {3, "c"}};Student *p = s;
printf("%d ", *s);printf("%d ", *p);
請輸入評論內容...
請輸入評論/評論長度6~500個字
最新活動更多
- 1 AI狂歡遇上油價破百,全球股市還能漲多久? | 產聯看全球
- 2 OpenAI深夜王炸!ChatGPT Images 2.0實測:中文穩、細節炸,設計師慌了
- 3 6000億美元估值錨定:字節跳動的“去單一化”突圍與估值重構
- 4 Tesla AI5芯片最新進展總結
- 5 連夜測了一波DeepSeek-V4,我發現它可能只剩“審美”這個短板了
- 6 熱點丨AI“瑜亮之爭”:既生OpenClaw,何生Hermes?
- 7 AI界的殺豬盤:9秒刪庫跑路,全員被封號,還繼續扣錢!
- 8 2026,人形機器人只贏了面子
- 9 DeepSeek降價90%:價格屠夫不是身份,是戰略
- 10 AI Infra產業鏈卡在哪里了?


分享













