研究C++已经有一段时间了,我那时扔掉了手头的中文版c++ primer,开始阅读英文版,用时一年半多才勉强开完一遍,增加了一千一百多个注释,如果确认没有版权问题,我会把它共享。再给gcc提bug时,通过邮件交流得知C++标准化工作组的网站,http://open-std.org/JTC1/SC22/WG21/,后面有陆续接触了C++创始人写的书,以及他在自己home page http://www.stroustrup.com/发表的文章。也浏览Nicolai M. Josuttis关于标准库和template的书,并为其找到几处错误,还一直期待他的C++11 template相关的书籍。目前在研究GCC中C++标准库的实现,也在看lippman的 inside the c++ object model。
今天看到class作为函数参数传递的问题,解决了困扰自己很久的问题,回想起来,其实好多类似的问题,但我没有总结出来,尽管我水平不高,这些问题别人也可能碰到,总结出来总会帮助到别人的,后面我会慢慢回忆那些让我豁然开朗的情形,写出来。接下来进入正题。
在C++中,object有一种叫做memberwise的copy方式,函数参数传递就是用的这样的机制。对于类似int,char,或者c中的structure,这都不是问题,因为对于它们,memberwise copy实现的拷贝构造函数是合法的。大家都清楚,memberwise copy方式实现的拷贝构造并不总是合法的。那么如果memberwise copy不合法时,如何实现函数参数传递呢? 编译会采用下面的策略:
1、直接把作为参数的object构造在被调用的函数栈里
2、在calling function stack中构造函数参数,然后将called function parameter改写成引用形式。
GCC默认采用了第二种方式,下面是我用GDB验证的结果。
1 #include <iostream>
2
3 class test
4 {
5 private:
6 int i;
7 public:
8 test(int v):i(v){}
9 test(const test &v):i(v.i + 1){}
10 virtual void show()
11 {
12 std::cout << i << std::endl;
13 return;
14 }
15 ~test() = default;
16 };
17
18 int foo(test v)
19 {
20 int ii = 0;
21 v.show();
22 std::cout << ii << std::endl;
23 return 0;
24 }
25
26 int main()
27 {
28 test v(1);
29 foo(v);
30 return 0;
31 }
可执行程序gdb调式的结果
Breakpoint 1, foo (v=...) at how_class_argument_passed.cpp:20
20 int ii = 0;
(gdb) info r
eax 0xbffff588 -1073744504
ecx 0xbffff5b0 -1073744464
edx 0x2 2
ebx 0x48996000 1218011136
esp 0xbffff550 0xbffff550
ebp 0xbffff568 0xbffff568
esi 0x0 0
edi 0x0 0
eip 0x80486f2 0x80486f2 <foo(test)+6>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) print &v
$1 = (test *) 0xbffff588
(gdb) f 1
#1 0x08048773 in main () at how_class_argument_passed.cpp:29
29 foo(v);
(gdb) info r
eax 0xbffff588 -1073744504
ecx 0xbffff5b0 -1073744464
edx 0x2 2
ebx 0x48996000 1218011136
esp 0xbffff570 0xbffff570
ebp 0xbffff598 0xbffff598
esi 0x0 0
edi 0x0 0
eip 0x8048773 0x8048773 <main()+65>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) print &v
$2 = (test *) 0xbffff580
可以看到函数foo的stack是0xbffff550 - 0xbffff568,但是值传递的参数地址是0xbffff588,明显不在foo stack中,在main stack中。
main stack 0xbffff570 - 0xbffff598,以及调用foo的argument的地址0xbffff580。
这说明调用foo时,参数是复制了,但是复制的object依然在主调函数的栈中。也就是上面说的第二种情况。