訂閱
糾錯(cuò)
加入自媒體

一個(gè)printf(結(jié)構(gòu)體指針)引發(fā)的血案

一、前言

1. 為什么寫(xiě)這篇文章

在上周六,我在公眾號(hào)里發(fā)了一篇文章:C語(yǔ)言指針-從底層原理到花式技巧,用圖文和代碼幫你講解透徹,以直白的語(yǔ)言、一目了然的圖片來(lái)解釋指針的底層邏輯,有一位小伙伴對(duì)文中的代碼進(jìn)行測(cè)試,發(fā)現(xiàn)一個(gè)比較奇怪的問(wèn)題。我把發(fā)來(lái)的測(cè)試代碼進(jìn)行驗(yàn)證,思考好久也無(wú)法解釋為什么會(huì)出現(xiàn)那么奇怪的打印結(jié)果。

為了整理思路,我到陽(yáng)臺(tái)抽根煙。晚上的風(fēng)很大,一根煙我抽了一半,風(fēng)抽了一半,可能風(fēng)也有自己的煩惱。后來(lái)一想,煙是我買的,為什么讓風(fēng)來(lái)抽?于是我就開(kāi)始抽風(fēng)!不對(duì),開(kāi)始回房間繼續(xù)抽代碼,我就不信,這么簡(jiǎn)單的 printf 語(yǔ)句,怎么就搞不定?!

于是就有了這篇文章。

2. 你能得到什么收獲函數(shù)參數(shù)的傳遞機(jī)制;可變參數(shù)的實(shí)現(xiàn)原理(va_list);printf 函數(shù)的實(shí)現(xiàn)機(jī)制;面對(duì)問(wèn)題時(shí)的分析思路。

友情提醒:文章的前面大部分內(nèi)容都是在記錄思考問(wèn)題、解決問(wèn)題的思路,如果你對(duì)這個(gè)過(guò)程不感興趣,可以直接跳到最后面的第四部分,用圖片清晰的解釋了可變參數(shù)的實(shí)現(xiàn)原理,看過(guò)一次之后,保管你能深刻記住。

3. 我的測(cè)試環(huán)境3.1 操作系統(tǒng)

每個(gè)人的電腦環(huán)境都是不一樣的,包括操作系統(tǒng)、編譯器、編譯器的版本,也許任何一個(gè)小差別都會(huì)導(dǎo)致一些奇奇怪怪的的現(xiàn)象。不過(guò)大部分人都是使用 Windows 系統(tǒng)下的 VS 集成開(kāi)發(fā)環(huán)境,或者 Linux 下的 gcc 命令行窗口來(lái)測(cè)試。

我一般都是使用 Ubuntu16.04-64 系統(tǒng)來(lái)測(cè)試代碼,本文中的所有代碼都是在這個(gè)平臺(tái)上測(cè)試的。如果你用 VS 開(kāi)發(fā)環(huán)境中的 VC 編譯器,可能在某些細(xì)節(jié)上與我的測(cè)試結(jié)果又出入,但是問(wèn)題也不大,遇到問(wèn)題再分析,畢竟解決問(wèn)題也是提升自己能力的最快途徑。

3.2 編譯器

我使用的編譯器是 Ubuntu16.04-64 系統(tǒng)自帶的版本,顯示如下:

另外,我安裝的是 64 位系統(tǒng),為了編譯 32 位的可執(zhí)行程序,我在編譯指令中添加了 -m 選項(xiàng),編譯指令如下:

gcc -m32 main.c -o main

使用 file main 命令來(lái)查一下編譯得到的可執(zhí)行文件:

所以,在測(cè)試時(shí)如果輸出結(jié)果與預(yù)期有一些出入,先檢查一下編譯器。C 語(yǔ)言本質(zhì)上都是一些標(biāo)準(zhǔn),每家的編譯器都是標(biāo)準(zhǔn)的實(shí)現(xiàn)者,只要結(jié)果滿足標(biāo)準(zhǔn)即可,至于實(shí)現(xiàn)的過(guò)程、代碼執(zhí)行的效率就各顯神通了。

二、問(wèn)題導(dǎo)入

 1. 網(wǎng)友測(cè)試代碼#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. 期望結(jié)果

根據(jù)上篇文章的討論,我們知道:

s 是一個(gè)包含 3 個(gè)元素?cái)?shù)組,每個(gè)元素的類型是結(jié)構(gòu)體 Student;p 是一個(gè)指針,它指向變量s,也就是說(shuō)指針 p 中保存的是變量 s 的地址,因?yàn)閿?shù)組名就表示該數(shù)組的首地址。

既然 s 也是一個(gè)地址,它也代表了這個(gè)數(shù)組中第一個(gè)元素的首地址。第一個(gè)元素類型是結(jié)構(gòu)體,結(jié)構(gòu)體中第一個(gè)變量是 int 型,因此 s 所代表的那個(gè)位置是一個(gè) int 型數(shù)據(jù),對(duì)應(yīng)到示例代碼中就是數(shù)字 1。因此 printf 語(yǔ)句中希望直接把這個(gè)地址處的數(shù)據(jù)當(dāng)做一個(gè) int 型數(shù)據(jù)打印出來(lái),期望的打印結(jié)果是:1, 1。

這樣的分析過(guò)程好像是沒(méi)有什么問(wèn)題的。

3. 實(shí)際打印結(jié)果

我們來(lái)編譯程序,輸出警告信息:

警告信息說(shuō):printf 語(yǔ)句需要 int 型數(shù)據(jù),但是傳遞了一個(gè) Student 結(jié)構(gòu)體類型,我們先不用理會(huì)這個(gè)警告,因?yàn)槲覀兙褪窍胪ㄟ^(guò)指針來(lái)訪問(wèn)這個(gè)地址里的數(shù)據(jù)。

執(zhí)行程序,看到實(shí)際打印結(jié)果是:1, 97,很遺憾,與我們的期望不一致!

三、分析問(wèn)題的思路 

1. 打印內(nèi)存模型

可以從打印結(jié)果看,第一個(gè)輸出的數(shù)字是 1,與預(yù)期符合;第二個(gè)輸出 97,很明顯是字符 'a' 的 ASCII 碼值,但是 p 怎么會(huì)指到 name 變量的地址里呢?

首先確認(rèn) 3 個(gè)事情:

結(jié)構(gòu)體 Student 占據(jù)的內(nèi)存大小是多少?數(shù)組 s 里的內(nèi)存么模型是怎樣的?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);

我們先畫(huà)一下數(shù)組 s 預(yù)期的內(nèi)存模型,如下:

編譯、測(cè)試,打印結(jié)果如下:

從打印結(jié)果看:

結(jié)構(gòu)體 Student 占據(jù) 12 個(gè)字節(jié),符合預(yù)期。數(shù)組 s 的內(nèi)存模型也是符合預(yù)期的,一共占據(jù) 36 個(gè)字節(jié)。s 與 p 都代表一個(gè)地址,打印結(jié)果它倆相同,也是符合預(yù)期的。

那就見(jiàn)鬼了:既然 s 與 p 代表同一個(gè)內(nèi)存地址,但是為什么用 *p 讀取 int 型數(shù)據(jù)時(shí),得到的卻是字符 'a' 的值呢?

2. 分開(kāi)打印信息

既然第一個(gè) *s 打印結(jié)果是正確的,那么就把這個(gè)兩個(gè)數(shù)據(jù)分開(kāi)來(lái)打印,測(cè)試代碼如下:

Student s[3] = {{1, "a"}, {2, "b"}, {3, "c"}};Student *p = s;
printf("%d ", *s);printf("%d ", *p);

1  2  3  下一頁(yè)>  
聲明: 本文由入駐維科號(hào)的作者撰寫(xiě),觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

    掃碼關(guān)注公眾號(hào)
    OFweek人工智能網(wǎng)
    獲取更多精彩內(nèi)容
    文章糾錯(cuò)
    x
    *文字標(biāo)題:
    *糾錯(cuò)內(nèi)容:
    聯(lián)系郵箱:
    *驗(yàn) 證 碼:

    粵公網(wǎng)安備 44030502002758號(hào)