欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

c語言中形參與實參的關(guān)系解讀

 更新時間:2023年07月25日 15:25:36   作者:Royel transformed  
這篇文章主要介紹了c語言中形參與實參的關(guān)系,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

c語言中形參與實參的關(guān)系

一、形參出現(xiàn)在函數(shù)定義中,在整個函數(shù)體內(nèi)都可以使用,形參變量只有在被調(diào)用時才分配內(nèi)存單元,在調(diào)用結(jié)束時即刻釋放所分配的內(nèi)存單元,因此,形參只有在函數(shù)內(nèi)部有效。 函數(shù)調(diào)用結(jié)束返回主調(diào)函數(shù)后則不能再使用該形參變量。  離開該函數(shù)則不能使用,實參出現(xiàn)在主調(diào)函數(shù)中,進(jìn)入被調(diào)函數(shù)后,實參變量也不能使用。 

二、形參和實參的功能是作數(shù)據(jù)傳送。發(fā)生函數(shù)調(diào)用時, 主調(diào)函數(shù)把實參的值傳送給被調(diào)函數(shù)的形參從而實現(xiàn)主調(diào)函數(shù)向被調(diào)函數(shù)的數(shù)據(jù)傳送。

三、實參可以是常量、變量、表達(dá)式、函數(shù)等, 無論實參是何種類型的量,在進(jìn)行函數(shù)調(diào)用時,它們都必須具有確定的值, 以便把這些值傳送給形參。因此應(yīng)預(yù)先用賦值,輸入等辦法使實參獲得確定值。

四、實參和形參在數(shù)量上,類型上,順序上應(yīng)嚴(yán)格一致, 否則會發(fā)生“類型不匹配”的錯誤。 

五、實參和形參的數(shù)據(jù)傳遞方式有兩種,

1.一種是值傳遞,實參和形參都不是指針,這種情況下函數(shù)調(diào)用中發(fā)生的數(shù)據(jù)傳送是單向的。 即只能把實參的值傳送給形參,而不能把形參的值反向地傳送給實參。 在函數(shù)調(diào)用過程中,形參的值發(fā)生改變,而實參中的值不會變化。究其原因,是因為這兩個參數(shù)在內(nèi)存中位于不同的位置,值傳遞只是形參將實參的內(nèi)容復(fù)制,在內(nèi)存中被分配了新的內(nèi)存單元,在函數(shù)執(zhí)行完畢以后地址會立刻被釋放掉,因此形參的改變不會對實參有任何影響。

2.另一種是地址傳遞,就是實參與形參共用同一個內(nèi)存單元,在函數(shù)執(zhí)行的過程中,實際就是對實參的地址進(jìn)行操作,因此形參改變,實參同步變化,實際他們就是同一變量,因為在內(nèi)存中占據(jù)的就是一個內(nèi)存單元。

不過下面這個例子很狡猾,就是指針作為形參時,實參*s只是把地址傳遞給了形參*p,在函數(shù)體內(nèi),p指針指向了下一個內(nèi)存地址,因此原內(nèi)存地址中的內(nèi)容并未改變,p指針變量的如何變化與S無關(guān),除非p指針改變了實參所指向位置的內(nèi)存單元中的數(shù)值,那么訪問實參所得值也會隨之變化。

void func(char *p)
{
? ? p=p+1;
}
int main()
{
? ? char s[]={'1','2','3','4'};
? ? func(s);
? ? printf("%c",*s);
? ? return 0;
}

輸出結(jié)果為1

并不是2

形參與實參在系統(tǒng)堆棧的變化過程

在各種計算機(jī)編程語言中,都少不了函數(shù)調(diào)用來完成某些特定功能。我們要使用一個函數(shù)前,必定會先定義該函數(shù)(可能還會先聲明它)。在這個過程中,我們大多會給這個函數(shù)傳遞某些‘參數(shù)’,當(dāng)我們定義被調(diào)用函數(shù)時,函數(shù)名后的括號( )中即為我們傳入的“形參”,函數(shù)可以在函數(shù)體中描述如何操作這些“形參”;同時,我們調(diào)用函數(shù)時,例如 fun(param1,param2,...),括號里面的這些param,我們稱之為實參。

簡單來說,調(diào)用函數(shù)時可以把實參傳給定義的函數(shù)作為形參以供函數(shù)來使用它們。

形參和實參的定義確實很好理解,但如果僅僅是理解它們是什么的話,這往往是不夠的。

接下來,我會在系統(tǒng)堆棧層面說明一下函數(shù)調(diào)用過程中,形參與實參以及函數(shù)局部變量是如何在堆棧里變化的。

先說一個C語言中的例子,如果我們想要通過一個子函數(shù)交換在主函數(shù)main()里面定義的兩個局部變量x,y的值的話,一些初學(xué)者可能會像這樣寫code:

 

寫完之后,在主函數(shù)打印了一下x,y發(fā)現(xiàn)x與y的值根本沒有變化??!

在這里,我們在主函數(shù)中定義了兩個int類型的局部變量x和y,它們的值分別為3和5,然后我們調(diào)用了在主函數(shù)前面定義的交換函數(shù)Exchange,并將變量x與y分別作為了兩個實參傳遞給它;再看Exchange函數(shù)中,我們的得到了兩個傳過來的形參a、b,然后我們通過同一個中間變量使它們(注意,兩個形參)交換。得到的結(jié)果時,主函數(shù)的x,y的值并沒有發(fā)生交換。

這個代碼正確的寫法應(yīng)該是:

這次我們的實參是&x,&y,即傳遞過去的是x,y的地址,相當(dāng)傳遞過程中存在int *a=&x與int *b=&y操作,其結(jié)果是讓a指向x,b指向y。然后同樣通過tmp完成值交換。

如果我們單單從指針方面去考慮為什么這次能夠交換成功,可能還不太容易弄懂交換的本質(zhì),接下來讓我談一談這次交換在系統(tǒng)堆棧中是如何做到的。

眾所周知,計算機(jī)存儲體系分為4層,它們的讀寫速度從慢到快依次是外存<內(nèi)存<高速緩存<CPU寄存器。

其中,寄存器又可分類為通用寄存器,段寄存器,輔助寄存器,兩個特殊寄存器:IP寄存器和Flag標(biāo)志位寄存器

所有的C源程序都要進(jìn)行編譯,從而生成最終的機(jī)器指令代碼和文件,那么,上述的內(nèi)容最終是由編譯軟件負(fù)責(zé)完成的;也就是說,編譯軟件將我們的ASCII碼形式的源程序代碼,根據(jù)自己的原則,使用上述的寄存器完成源代碼的任務(wù)。反過來說,如果在編程中需要特殊處理,那么,就需要給編譯器一定的“指導(dǎo)”,方可達(dá)到我們的目的!

輔助寄存器中有兩個寄存器bp(基址寄存器)和sp (棧寄存器),通常bp表示系統(tǒng)堆棧的棧底地址,sp表示系統(tǒng)堆棧的棧頂?shù)刂贰?/p>

特殊寄存器IP(指令寄存器),可以用來存儲“下一步”要執(zhí)行的某個進(jìn)程(線程)的代碼的地址的;

函數(shù)調(diào)用的過程中就發(fā)生了如下的變化:

首先,我們將代碼的執(zhí)行過程顯示成以.asm為后綴的匯編文件

 

可以看見,當(dāng)我們調(diào)用Exchange函數(shù)時,我們將x與y的地址作為實參變量傳遞了過去,在匯編層面,發(fā)生的事情是先通過lea指令將ebp[x]和ebp[y]地址的偏移量,把它們分別傳送到通用寄存器里的dx和cx寄存器里面。

然后通過call指令調(diào)用函數(shù)Exchange.也就是說,調(diào)用函數(shù)時,我們將距離ebp(棧底地址)為4個和8個字節(jié)的x和y變量地址偏移量,傳送給ecx和edx寄存器;有人可能想問,上圖中為什么_x$=-8,_y$=-12。

我們都知道,堆棧是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),局部變量定義后,會使堆棧內(nèi)存減小,地址往低處走,局部變量相對于棧底指針bp在其上面,也就是堆棧的低處,所以偏移量用負(fù)值表示。

同時,主函數(shù)其實也是一個相對于操作系統(tǒng)的子函數(shù),操作系統(tǒng)調(diào)用主函數(shù),然后主函數(shù)再調(diào)用子函數(shù)。

也就是說,建立主函數(shù)時,我們在主函數(shù)中定義的相對于主函數(shù)的局部變量也同樣被壓入了保存變量空間的系統(tǒng)堆棧。接下來我將通過主函數(shù)調(diào)用子函數(shù)的例子說明:

子函數(shù)是被call指令執(zhí)行的,call指令能夠停止主函數(shù)的運(yùn)行,并進(jìn)入子函數(shù),運(yùn)行其函數(shù)體內(nèi)容。當(dāng)然,還要保護(hù)主函數(shù)的運(yùn)行狀態(tài)信息,以使子函數(shù)調(diào)用完成返回后能夠接著執(zhí)行主函數(shù)后面的操作!進(jìn)入子函數(shù)后,先將ebp入棧,這樣做是為了保護(hù)主函數(shù)的ebp信息。第二步則是將esp移到ebp,棧頂指針和棧底指針重合,形成了一個空棧。而這個空棧就是為Exchange函數(shù)中局部變量所準(zhǔn)備的空間。Exchange函數(shù)不僅擁有傳進(jìn)來的形參變量a,b,還有自己定義的局部變量tmp。

第三步的push ecx,是為局部變量tmp準(zhǔn)備的,這個操作后,esp向地址低處移動4B,相當(dāng)于偏移量為-4;

與此同時,堆棧里的情況如下圖所示:

                                                         

實參變量是調(diào)用函數(shù)Exchange時入棧的,而且是根據(jù)括號里的順序($x,$y)從右往左依次入棧。

而賦值給形參變量是從左往右賦值的,也就是上面所說的int *a=&x與int *b=&y。往上,我們還要push兩個名為ebp和eip的值入棧,ebp入棧是因為我們要保存main原ebp的值,而為什么這里還有4B的eip值也入棧了呢?

這是因為,eip值是用來存放“下一步"要執(zhí)行的某個進(jìn)程(線程)的代碼的地址的,主函數(shù)調(diào)用子函數(shù),主函數(shù)被”中斷“,當(dāng)子函數(shù)返回到主函數(shù)時,同時也應(yīng)該準(zhǔn)確地返回到主函數(shù)被中斷的地方,繼續(xù)往下執(zhí)行。

所以eip是保護(hù)”現(xiàn)場信息“的關(guān)鍵。于是,這也就解釋了為什么形參變量a和b與ebp的偏移量分別為8B和12B了。

如果子函數(shù)在其內(nèi)部繼續(xù)定義局部變量,那么空間就繼續(xù)往上走,esp指針也跟著往上走就可以了。

函數(shù)調(diào)用的入棧順序就很明了了:實參表達(dá)式(形參)->main()eip->main()ebp->被調(diào)函數(shù)局部變量。如果子函數(shù)中再調(diào)用其它函數(shù),就如printf(),入棧細(xì)節(jié)也是如法炮制的。

但是,這好像依舊沒搞清楚最開始的問題,即它們是如何成功交換值的。要搞清楚這個,我們就必須明白主函數(shù)局部變量,實參和形參三者的所占用空間關(guān)系。

其重點(diǎn)就是,局部變量與實參并不是占用同一空間,然而實參表達(dá)式算出來的值,在系統(tǒng)堆棧占用的空間,就是與之對應(yīng)的形參變量的空間。

也就是說,如果我們按開頭第一種代碼寫交換功能的話,我們交換的只是,形參變量空間中的值而已,因為它只相當(dāng)于復(fù)制了一份主函數(shù)中x,y的值給形參而已,并沒有產(chǎn)生能夠改變真正在主函數(shù)中x,y的值。

但是如果我們傳遞給Exchagne的是x,y的地址的話,就相當(dāng)于構(gòu)建了Exchange中形參變量a和b與主函數(shù)x,y的一種地址指向”聯(lián)系“。

一旦我們建立了這種聯(lián)系,我們就可以將a所指向空間的值(局部變量x的值)與b所指向空間的值(局部變量y的值)同過tmp進(jìn)行交換。從而達(dá)成了交換的功能。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • C++實現(xiàn)LeetCode(134.加油站問題)

    C++實現(xiàn)LeetCode(134.加油站問題)

    這篇文章主要介紹了C++實現(xiàn)LeetCode(134.加油站問題),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • 詳解C++之C++11的牛逼特性

    詳解C++之C++11的牛逼特性

    這篇文章主要介紹了C++之C++11的牛逼特性,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-09-09
  • 將正小數(shù)轉(zhuǎn)化為2-9進(jìn)制小數(shù)的實現(xiàn)方法

    將正小數(shù)轉(zhuǎn)化為2-9進(jìn)制小數(shù)的實現(xiàn)方法

    本篇文章對正小數(shù)轉(zhuǎn)化為2-9進(jìn)制小數(shù)的實現(xiàn)方法進(jìn)行了介紹,需要的朋友參考下
    2013-05-05
  • c/c++實現(xiàn)獲取域名的IP地址

    c/c++實現(xiàn)獲取域名的IP地址

    本文給大家匯總介紹了使用c/c++實現(xiàn)獲取域名的IP地址的幾種方法以及這些方法的核心函數(shù)gethostbyname的詳細(xì)用法,非常的實用,有需要的小伙伴可以參考下。
    2015-11-11
  • C語言結(jié)構(gòu)體占用內(nèi)存深入講解

    C語言結(jié)構(gòu)體占用內(nèi)存深入講解

    這篇文章主要給大家介紹了關(guān)于C語言結(jié)構(gòu)體占用內(nèi)存的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • 基于C語言實現(xiàn)迷宮游戲的示例代碼

    基于C語言實現(xiàn)迷宮游戲的示例代碼

    這篇文章主要介紹了基于C語言如何實現(xiàn)簡單的迷宮游戲,對于學(xué)習(xí)游戲開發(fā)的朋友相信有一定的借鑒價值,需要的朋友可以參考下
    2022-05-05
  • 詳解VS2019 dumpbin查看DLL的導(dǎo)出函數(shù)

    詳解VS2019 dumpbin查看DLL的導(dǎo)出函數(shù)

    這篇文章主要介紹了詳解VS2019 dumpbin查看DLL的導(dǎo)出函數(shù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • EasyC++?右值引用

    EasyC++?右值引用

    這篇文章主要介紹了C++?右值引用,右值引用指的是以引用傳遞(而非值傳遞)的方式使用?C++?右值,下面文章將對此詳細(xì)介紹,需要的朋友可以參考一下,希望對你有所幫助
    2021-12-12
  • 基于C語言實現(xiàn)的迷宮游戲代碼

    基于C語言實現(xiàn)的迷宮游戲代碼

    這篇文章主要介紹了基于C語言實現(xiàn)的迷宮游戲代碼,對于學(xué)習(xí)游戲開發(fā)的朋友相信有一定的借鑒價值,需要的朋友可以參考下
    2014-08-08
  • C++實現(xiàn)從輸入中讀取字符串

    C++實現(xiàn)從輸入中讀取字符串

    這篇文章主要介紹了C++實現(xiàn)從輸入中讀取字符串的實現(xiàn)思路和具體代碼,非常的簡單實用,有需要的小伙伴可以參考下
    2016-05-05

最新評論