基礎備忘:對象的傳值與返回

      網友投稿 717 2025-04-01

      說起函數,就不免要談談函數的參數和返回值。一般的,我們習慣把函數看作一個處理的封裝(比如黑箱),而參數和返回值一般對應著處理過程的輸入和輸出。這種情況下,參數和返回值都是值類型的,也就是說,函數和它的調用者的信息交流方式是用過數據的拷貝來完成,即我們習慣上稱呼的“值傳遞”。但是自從引入了“引用”的概念后,函數的傳統模型就不再那么“和諧”了。引用的傳遞可以允許函數和調用者共享數據對象,它們之間的信息交流不再使用信息拷貝的方式,而是使用更有效率的信息共享的方式,引用導致函數的參數并有輸入和輸出的雙重功能。然而,事物總有兩面性,信息共享帶來方便的同時也帶來了一定的不安全性。我們這里并不討論函數的使用和設計,我們關注與函數參數和返回值的傳遞方式。

      對于內置數據類型的參數和返回值,函數實際參數的傳遞一般是通過壓棧完成,函數執行時會從棧內取出參數的值進行計算。在32處理器上,push指令一次只能壓入4個字節的數據,那么對于long long就需要兩次壓棧指令了,而double類型參數就需要sub esp,8結合mov指令完成參數進棧的操作。函數帶有返回值時,若返回值不大于4字節,則會把返回值存儲在eax寄存器中,而long long類型返回值回保存在edx:eax寄存器中,double類型的數據會被協處理器棧保存。

      相對于內置類型的參數傳遞和返回值,對象的傳值和返回可能更復雜一點。當然,如果使用對象的引用或者指針作為參數傳遞和返回值的方式,這里和上述的內置類型并無多大區別,因為指針總是4個字節。如果不使用引用和指針,單純傳遞純粹的對象時,編譯器會如何處理呢?

      為此,我們定義一個簡單的類A,為了防止編譯器對我們的代碼優化處理(參考我的前一篇博文),我們自己定義構造函數、復制構造函數和賦值運算符重載函數。

      class A

      {

      int x;

      int y;

      int z;

      基礎備忘:對象的傳值與返回

      public:

      A(){}

      A(const A&a)

      {

      x=a.x;

      y=a.y;

      z=a.z;

      }

      const A&operator=(const A&a)

      {

      x=a.x;

      y=a.y;

      z=a.z;

      }

      };

      定義一個簡單的具有對象參數和返回值的函數,以及測試代碼。

      A fun(A x)

      {

      return x;

      }

      A a;

      a=fun(a);

      試想一下,如果A不是自定義類型,而是int類型的話,這段測試代碼會有怎樣的效果。

      mov eax,[a];//取出a的值

      push eax;//a值進棧

      call fun;//調用fun

      add esp,4;//恢復棧指針

      mov [a],eax;//返回值寫入a

      ;//而fun內部無非也是把參數x的值寫入eax,然后返回而已。

      mov eax,[a]

      ret

      事實是這樣的嗎?我們看一下VS2010的反匯編。

      和我們的預期完全一致!

      現在,我們回到對象的問題上來。由于對象是值傳遞方式,因此,對象傳遞之前需要進行一次對象拷貝(從原對象到實參)。函數調用結束后還需要將返回值對象進行一次拷貝。我們看看VS2010的處理方式。

      對象a定義是需要調用它的構造函數A::A(0A1112Ch)。

      對象A包含三個整形數據成員,因此它的大小是12(0x0C)字節。sub esp,0Ch正是開辟12個字節存儲從對象a拷貝出來的12字節數據。

      mov ecx,esp記錄了被拷貝的參數對象的地址(this指針),push eax壓入的是a的地址,也就是拷貝構造函數調用時參數對象的地址(引用)。拷貝構造函數(A::A(0A11131h))會把a地址記錄的對象數據拷貝到ecx記錄的this對應的參數對象內。調用結束后,使用ret 4指令將剛才壓入的a的地址彈出棧,這樣棧頂保存著完整參數對象(剛才開辟的12個字節)。這樣參數對象被完整的復制出來了。

      push ecx壓入了內存地址ebp-58h,這個地址既不是a的地址,也不是拷貝出參數對象的地址,而是要保存返回對象的地址!調用fun之前將該地址壓棧,就是為了保存fun處理結束后的返回值對象。fun調用結束后將esp指針恢復了16字節,正好是參數對象的大?。?2字節)加上返回值對象的地址(4字節)之和!要獲得fun的返回值,直接訪問eax即可,因為它保存著返回值對象的地址(ebp-58h)!

      最后一步是對象的賦值,這里需要調用對象的賦值運算符重載函數。而參數正是剛才fun調用結束后eax的值,因為它存儲了返回值對象的地址。ecx記錄this指針,正是被賦值對象的地址(a的地址)。賦值運算符重載函數調用結束后,完成返回值對象的賦值操作。

      按照編譯器產生的fun函數的語義,我們使用高級語言可以將它的意思描述如下。

      A a;//定義a

      a.A();//默認構造

      A x;//開辟x的12字節空間

      x.(a);//對象復制到實際參數

      A*pret=&ret;//取返回值對象地址(已經開辟過了)

      fun(pret,x);//傳遞返回值指針pret和參數對象x

      a=*pret;//把返回值對象賦值給對象a

      //這樣原本fun的函數形式就有所變化了。

      void fun(A*pret,A x)

      {

      pret->A(x);//將返回值拷貝到返回值對象內

      return;//啥也不返回了

      }

      我們看一下fun的匯編代碼。

      參數對象的地址被x記錄了下來,ebp+8記錄的正是函數第一個參數的內容,即返回值對象的地址!在拷貝構造函數調用之前,ecx保存的this指針正是返回值對象的,進棧的參數是x的地址,和我們預期的一樣!

      因此,我們可以針對對象的傳值和返回得出如下結論:

      1.?對象參數傳遞之前需要進行一次對象拷貝,將原對象的內容完整的拷貝到參數對象內部,函數執行時訪問的是參數對象,而不是原對象。

      2.?對象返回時,也需要將函數處理的結果進行一次對象拷貝,不過被拷貝的返回值對象內存已經在函數調用之前已經開辟出來了,函數只需要記錄它的地址即可,然后調用拷貝構造函數初始化它。

      3.?函數調用結束后,eax保存了返回值對象的地址,供調用者使用。

      通過本文的描述,相信讀者對對象作為函數參數和返回值時,編譯器的內部處理機制有個更清晰的了解。

      延伸閱讀:

      http://blog.csdn.net/zyearn/article/details/9709041

      http://tieba.baidu.com/p/1967981445

      面向對象編程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:excel數值怎么以千為單位
      下一篇:如何將excel導入ps(如何將Excel導入ps)
      相關文章
      亚洲国产成人久久综合| 久久亚洲精品人成综合网| 婷婷亚洲久悠悠色悠在线播放| 亚洲不卡AV影片在线播放| 亚洲人成网站18禁止| 久久精品国产亚洲AV忘忧草18| 青青草原精品国产亚洲av| 亚洲av伊人久久综合密臀性色| 国产亚洲美女精品久久久久狼| 国产亚洲一区区二区在线| 伊人久久精品亚洲午夜| 亚洲免费无码在线| 亚洲一级片免费看| 亚洲人成网站色在线入口| 亚洲日本中文字幕天堂网| 亚洲午夜精品久久久久久浪潮 | 亚洲视屏在线观看| 99久久亚洲综合精品成人网| 中文字幕亚洲色图| 亚洲国产成人精品电影| 亚洲宅男永久在线| 老司机亚洲精品影院| 亚洲最大在线观看| 亚洲av永久无码精品天堂久久| 国产.亚洲.欧洲在线| 亚洲中文字幕久久精品无码VA| 亚洲中文字幕AV每天更新| 亚洲精品欧美综合四区| 国产亚洲精品91| 久久精品国产亚洲一区二区三区 | 亚洲国产视频网站| 亚洲a∨无码男人的天堂| 亚洲欧洲免费无码| 亚洲Aⅴ无码一区二区二三区软件| 亚洲国产成人VA在线观看| 亚洲一区二区三区香蕉| 婷婷精品国产亚洲AV麻豆不片| 亚洲av无码不卡私人影院| 亚洲熟伦熟女新五十路熟妇| 国产亚洲A∨片在线观看| 99亚洲精品高清一二区|