2010年7月23日 星期五

call by value、address、reference

分別說明C++中 call by value , call by address , call by reference 與C的關係
參考來源 :
ofstream的參數傳虒 為什麼要加 & 運算子? / C++ / 程式設計俱樂部
裡面sflam(Raymond)大大的回覆

-----------------------------------------------------------------------------------------------------------
2007/11/2 下午 10:48:47
我個人覺得 "call by X" 這個說法容易造成誤解, 我比較喜歡用 "pass by X". 因為這裡講的是參數如何傳遞, 而不是函式如何呼叫.

嚴格說起來, C 沒有 "pass by address" 這個東西. 所有的參數都是 "pass by value". 即使傳入的是個位址也是如此. 如果傳入的參數是個位址, 那接收的函式就必須是個指標, 指標所得到的是位址的值, 都是 value.

比方說:

  int main()
  {
    int i;
    func(&i);
    ...
  }

  void func(int *pi)
  {
    ...
  }


在 main() 裡:

       int i
      +=====+
〔i的位址〕|     |
   |  +=====+
   |
   |
   |   在 func() 裡, pi 得到一份 「i 位址的拷貝」...
   |
   |     int *pi     
   |    +=========+
   +----+->〔i的位址〕 |
        +=========+

結果就是, func() 裡的 pi 指向 main() 裡的 i:

   i
  +=====+
  |     |<--+
  +=====+   |
            | main()
~~~~~~~~~~~~|~~~~~~
            | func()
   pi       |
  +=====+   |
  | *---+---+
  +=====+

在 func() 裡面更改 pi 所指向的記憶體, 也就更改了 main() 裡面的 i. 如果函式要修改上一層的變數, 在 C 語言裡面唯一的方法就是傳入變數的位址, pass an address. 所傳的方式是這個位址的值, the address is pass by value.

所以 "pass by address" 可以看成是 "pass the address by value".


C++ 除了 C 的 pass by value 外, 也支援 "pass by reference" 的概念. 在概念上, reference 可以看成是一個變數的別名. 更改這個別名的內容也就更改了這個別名所代表的變數內容.

比較看看三種做法在語法上有什麼不同:

〔1: pass by value〕

  int main()
  {
    int i;
    func(i);
  }

  void func(int i2) { ... }


〔2: pass an address by value〕
  int main()
  {
    int i;
    func(&i);
  }

  void func(int *p) { ... }


〔3: pass by reference〕
  int main()
  {
    int i;
    func(i);
  }

  void func(int &i2) { ... }


在 〔1〕 裡, 不管 func() 裡面如何更改 i2, 都不會影響到 main() 裡的 i.
在 〔2〕 裡, func() 是透過 *p 去改變 main() 裡 i 的值.
在 〔3〕 裡, i2 是 i 的別名. func() 更改 i2 的值, main() 裡的 i 也會跟著改變.

在 C 語言裡, 只有 〔1〕 跟 〔2〕 兩種寫法. 要把 〔1〕 改成 〔2〕, 呼叫的地方跟函式的內容都要更改.

在 C++ 語言裡, 三種寫法都可以用. 單單看 main(), 〔1〕 跟 〔3〕是沒有分別的. 要把 〔1〕 改成 〔3〕, 只需把函式的參數加個 '&'.

〔1〕 跟 〔3〕 的最大分別是: 物件的拷貝. 在 〔1〕 裡, 物件會被拷貝. 〔3〕 則不會, i2 跟 i 是同樣的物件.

樓主的問題就是『物件有無拷貝』的問題. 有些物件是不能或不適合拷貝的, 比方說 ostream.

ostream 是個 C++ 物件, 它有一個對應的檔案物件. C++ 物件是個抽象的物件, 在記憶體裡. 它對應的檔案是個實在的物件, 存在磁碟裡. 這兩個物件的 states 必須要一致, 要同步才能 work. 如果這個物件被拷貝了, 那同一個檔案物件就對上了一個以上的 ostream 物件. 只要其中一個 ostream 物件更改了它的 state (比方說在函式裡輸出一些值到檔案裡, 或關閉檔案), 那其他的 ostream 的 state 就跟檔案的 state 不一致, 不同步了. 當然就會造成很大的問題.

所以像這類不適合拷貝的物件, 只能用 〔2〕 或 〔3〕 的方式來做. 用 reference 是比較方便, 因為 pointer 還要用 dereference 的語法.

其它『不能拷貝的物件』的例子還滿多的, 像 MFC 的 CWnd 及所有 CWnd 衍生的物件都是基於同樣的原因, 不能拷貝的物件.

2010年7月13日 星期二

VS2005 inline function 反組譯追蹤

開發平台: VC++ , Windows

使用行內涵式時,修飾詞inline會告知編譯器,
每當程式碼呼叫此函式時,就會產生一函式副本來取代該函式呼叫,
以便節省呼叫函式的時間。
也就是說,
程式控制權一直在main()裡面(假設在main中呼叫inline函式),
而非每次呼叫函式時便轉移控制權到該函式。

如何在vs2005平台偵錯模式中觀察這個差異?
在專案->屬性



在屬性視窗中,預設debug mode 的內嵌函式展開屬性為"預設"



要觀察行內涵式的反組譯須把偵錯模式改為release,內嵌行式展開設為"僅_inline"


避免編譯時因最佳化關係自動把函式inline起來



~觀察反組譯~


inline function 程式碼已內嵌

一般函式叫用,可以看到第4行 :0040106B call countcubeVolume1 (401140h)
程式執行至此,會call countcubeVolume1 。