C语言,把指针按地上摩擦,爽
不要陷在指針里面,最好的方法是跳出指針,我們從最終結(jié)果來(lái)思考問(wèn)題。于是我的解題思路總是很偏,但是直指本質(zhì)。
我們寫(xiě)一段代碼:
編譯,反編譯,反編譯這里我們用objdump -d?hello >1.txt,如果你是用IDA,會(huì)發(fā)現(xiàn)出來(lái)的匯編不一樣,因?yàn)楦鞣N格式的匯編,有不同的寫(xiě)法,這里主要就是Intel和AT&T,GNU遵循的是AT&T的寫(xiě)法。
在里面找到我們的add 和main
這里我不會(huì)展開(kāi)去講AT&T,這個(gè)玩意就是查表,我這里主要講幾個(gè)內(nèi)容,這個(gè)非常重要。匯編語(yǔ)言中自己要關(guān)注堆棧平衡,再一個(gè)就是寄存器的保存與恢復(fù),第三個(gè)就是調(diào)用參數(shù)約定。
舉例來(lái)說(shuō),add %edx,%eax ,這個(gè)的結(jié)果在哪里?這個(gè)都是指令直接就決定的,也就是我們的CPU設(shè)計(jì)時(shí)候,它的這條指令執(zhí)行完,數(shù)據(jù)會(huì)放在哪里。
我們看到的main方法中的
mov?$0x6,%esi
mov?$0x5,%edi
這兩個(gè)就是我們add方法執(zhí)行的兩個(gè)參數(shù),它賦值到這兩個(gè)寄存器,那么在用這兩個(gè)寄存器,是不是要把寄存器當(dāng)前的值保存下來(lái)呢?所以你能看到緊挨著上面的就是保存動(dòng)作。
然后callq 調(diào)用add方法,這里我們看緊跟著的?mov?%eax?,-0x4(%rbp) ,我們剛才說(shuō)了add方法執(zhí)行后,eax里面是結(jié)果。
這里將%eax的值放到了 %rbp寄存器-0x4的地方,這個(gè)地方是什么?是棧,具體到代碼中,就是sum的位置。
int sum =add(5,6);的執(zhí)行過(guò)程就是這樣的。我這里分享一個(gè)圖,主要說(shuō)的是傳參的約定,我們知道調(diào)用函數(shù)時(shí)候是有參數(shù)的約定,其實(shí)二進(jìn)制這里也是有的,這個(gè)叫做System V ABI 。
我一般是怎么掌握這些規(guī)則,去寫(xiě)匯編,一般就是用C寫(xiě)一些,編譯,反匯編來(lái)看,這個(gè)大家可以參考一本書(shū),
我們編譯完的程序,是沒(méi)有sum這個(gè)變量,在執(zhí)行的代碼中,都是變成了具體的位置,這里簡(jiǎn)單說(shuō)下就是堆還是棧,局部變量是在棧上面,局部靜態(tài)變量是在堆上面。
全局?jǐn)?shù)據(jù)分兩類,一類是初始化的全局變量,一類是未初始化的,未初始化的運(yùn)行時(shí)候系統(tǒng)會(huì)默認(rèn)給初始化為0(但是不要以為它就必須是0,這個(gè)就是跟運(yùn)行機(jī)制有關(guān),我們寫(xiě)代碼一定記住,不要去嘗試依賴外部不確定的因素)
全局?jǐn)?shù)據(jù)區(qū)分為 data rodata 和 bss ,rodata這個(gè)就是read only,只讀區(qū)域這個(gè)是由加載器加載程序進(jìn)入進(jìn)程時(shí)候,會(huì)對(duì)這個(gè)數(shù)據(jù)區(qū)域的page,做設(shè)定,設(shè)定只讀,如果后續(xù)在這里寫(xiě)入數(shù)據(jù),就會(huì)報(bào)錯(cuò)。
data就是我們常規(guī)的數(shù)據(jù),舉例就是 int a=100;這類全局變量會(huì)放在data區(qū)域。而我們?nèi)绻莍nt a;這個(gè)全局變量,就會(huì)放置到bss,這個(gè)區(qū)域叫做全局未初始化區(qū)域,這個(gè)跟data的區(qū)別在于,這個(gè)bss在程序中不占用大小,只是在加載時(shí)候會(huì)在內(nèi)存中占用大小。
text區(qū)域就是代碼段。
說(shuō)到這里,我這里再說(shuō)一個(gè)內(nèi)容,我們?cè)诳吹酱a時(shí)候,發(fā)現(xiàn)printf這個(gè)函數(shù),后面有個(gè)plt。
我們來(lái)說(shuō)下這個(gè)plt。plt的意思是,這個(gè)方法不在這個(gè)程序里面,是在外面的,而對(duì)應(yīng)的位置,這里就是4003f0,這個(gè)位置是什么?我們知道printf是在glibc.so ,這里用的動(dòng)態(tài)庫(kù)。
我們程序要跑起來(lái),是要補(bǔ)全這里的printf的執(zhí)行塊的,系統(tǒng)的做法就是,先放置一個(gè)占位位置,然后程序加載的時(shí)候,加載器知道這里需要一個(gè)printf的真實(shí)地址,這些需要放置地址的區(qū)域,統(tǒng)一在plt這個(gè)區(qū)域里面,在這個(gè)位置放入真正的printf的入口點(diǎn)。
聽(tīng)起來(lái)很繞,我們用一個(gè)例子,你就能明白了。但是這里的例子,估計(jì)又牽扯進(jìn)來(lái)新的概念,大家先理解下吧。
typedef int (*operate)(int a,int b);這個(gè)定義了一個(gè)類型,類型是一個(gè)有兩個(gè)參數(shù),一個(gè)返回值的函數(shù)類型。我們平時(shí)的類型就是int,這里是一個(gè)函數(shù)的類型。
然后聲明一個(gè)列表,把a(bǔ)dd,和sub放進(jìn)來(lái),我們直接調(diào)用即可。這里就想給大家說(shuō),這個(gè)是可以放置一個(gè)函數(shù)名的,等下我們繼續(xù)操作,就能夠更深入的理解這個(gè)函數(shù)名。
那么看到這里,我們開(kāi)始真正進(jìn)入指針的世界,我們來(lái)理解指針。我的操作就是,編譯,反匯編,我們先看下代碼:
這里我們引入了指針p,儲(chǔ)存了變量a的地址,然后*p代表拿出p地址里面的內(nèi)容,我們看下反編譯匯編,分析下這個(gè)過(guò)程。
這里mov $0x5,-0xc(%rbp)將5放入rbp-0xc的位置。lea -0xc(%rbp),%rax ? mov %rax,-0x18(%rbp) 這兩句話的意思是,拿到?-0xc(%rbp)的地址,放入?-0x18(%rbp)位置,也就是指針p的位置。
這里的mov?(%rax),%eax 指的意思是,將rax里面的值讀出來(lái),找到這個(gè)值對(duì)應(yīng)的地址的內(nèi)容,存儲(chǔ)到%eax里面,這里可以用c語(yǔ)言寫(xiě)就是,int c=*p;
從這里面我想說(shuō)的就是,我們的指針,這些,在匯編形態(tài)下,不過(guò)是兩種類型,一個(gè)是讀取寄存器的值,一個(gè)是讀取把寄存器中的值當(dāng)做地址對(duì)應(yīng)位置的值。
我們只要這樣子去理解,基本上就能清晰的了解指針,指針?biāo)鎯?chǔ)的值,我們一般都是用它所指向的地址內(nèi)容,它本身的地址只是途徑,類似于我們?cè)趫D書(shū)館查出來(lái)書(shū)的序號(hào)A-1-303,我們真正要的是這個(gè)位置的那本書(shū)。
當(dāng)我們理解了這個(gè),這里的add函數(shù)就是個(gè)地址,我們這么來(lái)看下。
我們直接用void *p=add;然后把這個(gè)p讓編譯器按照add對(duì)應(yīng)的參數(shù),返回類型去調(diào)用,這樣子就可以用到add函數(shù)。
int 這類我們就能理解了,那么我們?cè)賮?lái)說(shuō)下int[];這個(gè)看完匯編語(yǔ)句,一下子就明白了。我們說(shuō)過(guò)一點(diǎn),就是在真實(shí)的計(jì)算機(jī)上面,執(zhí)行的是指令,指令理解就兩類,一個(gè)是值,一個(gè)是地址,也可以理解成直接引用,間接引用。
這里想說(shuō)的是,array在編譯器里面,就是理解成一個(gè)指針,指向了一個(gè)int數(shù)組。我們把a(bǔ)rray賦值到p指針,發(fā)現(xiàn)p[1]跟array[1]是一樣的。我們看反匯編代碼:打印語(yǔ)句改成printf("p[1]=%d,array[1]=%d\n",p[1],array[1]);,來(lái)比對(duì)下。
這里數(shù)組的取值,直接被優(yōu)化了,直接用的mov -0x1c(%rbp),%edx 從上面的存儲(chǔ)可以看到,這個(gè)位置直接就是array[1],具體指令是movl $0x2,-0x1c(%rbp),我們指針的獲取,這里很明確,拿到數(shù)組起始地址,用add %0x4,%rax,進(jìn)行了偏移,找到了p[1]位置。
這里分享下,地址的+1,指的是地址所指向的內(nèi)容的大小,進(jìn)行偏移。理解了這個(gè),再去理解數(shù)組,就很好理解了。
我們把代碼改成
long *p=array;
printf("p[1]=%d,array[1]=%d\n",(int)(p[1]),array[1]);
打印出來(lái)就不一樣了,原因就是p+1,是加的sizeof(long) 的大小,也就是它所指向的內(nèi)容所代表的大小。所以我們?cè)賮?lái)說(shuō)下,
int array[3][5]={1,2,3,4,5,
?6,7,8,9,10,
11,12,13,14,15};
然后 int (*p)[5] =array; 那么p[1][0]是多少呢?我們前面說(shuō)了,p+1是依據(jù)它指向的大小,這里就是 int [5]? 的大小,所以就是輸出的6。這里也就是array實(shí)際就是一個(gè)指向一行五個(gè)int的一個(gè)數(shù)組指針。
核心一句話,指針的+1是根據(jù)指向的數(shù)據(jù)大小決定,同時(shí)在指令級(jí)別去看,只有兩種解析,就是值和地址。
這一節(jié)有可能講的太晦澀,后面我們?cè)賮?lái)講一通。建議學(xué)習(xí)下x86匯編的尋址,再一個(gè)就是計(jì)算機(jī)組成原理,或許后面我們?cè)僦v一次指針,再花費(fèi)一些力氣把這塊講下。下一節(jié)我來(lái)說(shuō)下,靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)怎么使用,兩者的本質(zhì)區(qū)別,以及設(shè)計(jì)的邏輯是什么。
推薦閱讀:
專輯|Linux文章匯總
專輯|程序人生
專輯|C語(yǔ)言
我的知識(shí)小密圈
關(guān)注公眾號(hào),后臺(tái)回復(fù)「1024」獲取學(xué)習(xí)資料網(wǎng)盤(pán)鏈接。
歡迎點(diǎn)贊,關(guān)注,轉(zhuǎn)發(fā),在看,您的每一次鼓勵(lì),我都將銘記于心~
總結(jié)
以上是生活随笔為你收集整理的C语言,把指针按地上摩擦,爽的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: xsd文件规则和语法
- 下一篇: PostgreSQL与mysql语法不同