缘起

起因是我在一个函数里面开了一个 socket,大概像。。这样~

1
2
3
4
5
6
7
8
9
10
11
12
int fun(){
int s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
/* ... */
if (/* ...*/){
close(s);
return -1;
}
if(/* ... */){
close(s);
return xx;
}
}

显而易见的一个问题就是必须在每个 return 之前都要手写一个 close(s),不然调用多次之后就会产生句柄泄露(当我发现这个问题的时候,lsof -p my_pid 已经有一千多条啦),这样写其实是比较麻烦的,于是 leader 建议可以利用 shared_ptr 的特性——离开作用域的时候释放,来化简代码。处理之后就变成这样~

1
2
3
4
5
6
7
8
9
10
11
12
13
int fun(){
int s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
std::shared_ptr<int> p(&s,[](int *s){
close(*s);
});
/* ... */
if (/* ...*/){
return -1;
}
if(/* ... */){
return xx;
}
}

下面是这个技巧的简单模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <memory>

int test(){
int hh =10;
{
std::shared_ptr<int> p(&hh,[](int *hh){
*hh = 15;
std::cout<<*hh<<std::endl;
});
}
std::cout<<"jiujiu"<<hh<<std::endl;
return hh;
}
int main(){
std::cout<<"miaomiao"<<test()<<std::endl;
std::cout<<"wangwang"<<std::endl;
}

按我的理解输出应该是这样:

1
2
3
4
15
jiujiu15
miaomiao15
wangwang

但实际上输出是这样~

1
2
3
4
miaomiao15
jiujiu15
15
wangwang

刺激,让我们来看看汇编代码叭~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
Dump of assembler code for function main():
0x00401531 <+0>: lea 0x4(%esp),%ecx
0x00401535 <+4>: and $0xfffffff0,%esp
0x00401538 <+7>: pushl -0x4(%ecx)
0x0040153b <+10>: push %ebp
0x0040153c <+11>: mov %esp,%ebp
0x0040153e <+13>: push %ebx
0x0040153f <+14>: push %ecx
0x00401540 <+15>: sub $0x10,%esp
0x00401543 <+18>: call 0x402180 <__main>
=> 0x00401548 <+23>: movl $0x406071,0x4(%esp)
0x00401550 <+31>: movl $0x6ff03a20,(%esp)
0x00401557 <+38>: call 0x401c24 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)>
0x0040155c <+43>: mov %eax,%ebx
0x0040155e <+45>: call 0x4014c7 <test()>
0x00401563 <+50>: mov %eax,(%esp)
0x00401566 <+53>: mov %ebx,%ecx
0x00401568 <+55>: call 0x401c4c <std::ostream::operator<<(int)>
0x0040156d <+60>: sub $0x4,%esp
0x00401570 <+63>: movl $0x401c2c,(%esp)
0x00401577 <+70>: mov %eax,%ecx
0x00401579 <+72>: call 0x401c54 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))>
0x0040157e <+77>: sub $0x4,%esp
0x00401581 <+80>: movl $0x40607a,0x4(%esp)
0x00401589 <+88>: movl $0x6ff03a20,(%esp)
0x00401590 <+95>: call 0x401c24 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)>
0x00401595 <+100>: movl $0x401c2c,(%esp)
0x0040159c <+107>: mov %eax,%ecx
0x0040159e <+109>: call 0x401c54 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))>
0x004015a3 <+114>: sub $0x4,%esp
0x004015a6 <+117>: mov $0x0,%eax
0x004015ab <+122>: lea -0x8(%ebp),%esp
0x004015ae <+125>: pop %ecx
0x004015af <+126>: pop %ebx
0x004015b0 <+127>: pop %ebp
0x004015b1 <+128>: lea -0x4(%ecx),%esp
0x004015b4 <+131>: ret
End of assembler dump.

Dump of assembler code for function test():
0x004014c7 <+0>: push %ebp
0x004014c8 <+1>: mov %esp,%ebp
0x004014ca <+3>: sub $0x28,%esp
0x004014cd <+6>: movl $0xa,-0x10(%ebp)
0x004014d4 <+13>: lea -0x18(%ebp),%eax
0x004014d7 <+16>: mov %dl,0x4(%esp)
0x004014db <+20>: lea -0x10(%ebp),%edx
0x004014de <+23>: mov %edx,(%esp)
0x004014e1 <+26>: mov %eax,%ecx
0x004014e3 <+28>: call 0x4015b6 <std::shared_ptr<int>::shared_ptr<int, test()::<lambda(int*)> >(int *, <lambda(int*)>)>
0x004014e8 <+33>: sub $0x8,%esp
0x004014eb <+36>: lea -0x18(%ebp),%eax
0x004014ee <+39>: mov %eax,%ecx
0x004014f0 <+41>: call 0x4043c8 <std::shared_ptr<int>::~shared_ptr()>
0x004014f5 <+46>: movl $0x40606a,0x4(%esp)
0x004014fd <+54>: movl $0x6ff03a20,(%esp)
0x00401504 <+61>: call 0x401c24 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)>
0x00401509 <+66>: mov %eax,%edx
0x0040150b <+68>: mov -0x10(%ebp),%eax
0x0040150e <+71>: mov %eax,(%esp)
0x00401511 <+74>: mov %edx,%ecx
0x00401513 <+76>: call 0x401c4c <std::ostream::operator<<(int)>
0x00401518 <+81>: sub $0x4,%esp
0x0040151b <+84>: movl $0x401c2c,(%esp)
0x00401522 <+91>: mov %eax,%ecx
0x00401524 <+93>: call 0x401c54 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))>
0x00401529 <+98>: sub $0x4,%esp
0x0040152c <+101>: mov -0x10(%ebp),%eax
0x0040152f <+104>: leave
0x00401530 <+105>: ret
End of assembler dump.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
sourece code:
std::cout<<"miaomiao"<<test()<<std::endl;
asm:
0x00401531 <+0>: lea 0x4(%esp),%ecx
0x00401535 <+4>: and $0xfffffff0,%esp
0x00401538 <+7>: pushl -0x4(%ecx)
0x0040153b <+10>: push %ebp
0x0040153c <+11>: mov %esp,%ebp
0x0040153e <+13>: push %ebx
0x0040153f <+14>: push %ecx
0x00401540 <+15>: sub $0x10,%esp
0x00401543 <+18>: call 0x402180 <__main>
=> 0x00401548 <+23>: movl $0x406071,0x4(%esp)

gdb:s
source code:
int hh =10;
asm:
0x004014c7 <+0>: push %ebp
0x004014c8 <+1>: mov %esp,%ebp
0x004014ca <+3>: sub $0x28,%esp
=> 0x004014cd <+6>: movl $0xa,-0x10(%ebp)

可以看到 int hh = 10; 正好对应的就是 movl $0xa,-0x10(%ebp)

此时看一下栈的状态:(大地址是栈底,小地址是栈顶哈)
0x29fee0: 0x70 0x7d 0x79 0x66 0x00 0xe0 0xfd 0x7f
0x29fee8: 0x08 0xff 0x29 0x00 0xa9 0x49 0xef 0x6f
0x29fef0: 0x20 0x3a 0xf0 0x6f 0x71 0x60 0x40 0x00
0x29fef8: 0x08 0x00 0x00 0x00 0xcc 0xff 0x29 0x00
0x29ff00: 0xd0 0xd1 0xf0 0x76 0x44 0x0a 0xa5 0x10
可以看到很熟悉的两个地址 0x20 0x3a 0xf0 0x6f(对应 0x00401550 指令),0x71 0x60 0x40 0x00(对应 0x00401548 指令),顺便一提,0x406071里是猫猫哦~
(gdb) x/16cb 0x406071
0x406071 <std::ignore+8>: 109 'm' 105 'i' 97 'a' 111 'o' 109 'm' 105 'i' 97 'a' 111 'o'
0x406079 <std::ignore+16>: 0 '\000' 119 'w' 97 'a' 110 'n' 103 'g' 119 'w' 97 'a' 110 'n'

后面 shared_ptr 就不咋能看懂了,直接跳到
source code:
std::cout<<"jiujiu"<<hh<<std::endl;
asm:
=> 0x004014f5 <+46>: movl $0x40606a,0x4(%esp)
0x004014fd <+54>: movl $0x6ff03a20,(%esp)

跟上面一样,0x40606a 里面是 jiujiu,顺便怀疑一下,0x6ff03a20是栈的边界保护符!

然后比较诡异的事情就出现了,此时,控制台输出了 miaomiao15。。。

然后到
source code:
return hh;
sam:
=> 0x0040152c <+101>: mov -0x10(%ebp),%eax
这个时候控制台输出了 jiujiu15,可以理解哈

继续:
source code:
}
sam:
=> 0x0040152f <+104>: leave
继续:
source code:
std::cout<<"wangwang"<<std::endl;
asm:
=> 0x00401581 <+80>: movl $0x40607a,0x4(%esp)
这个时候控制台输出了:15

ok,总结一下目前发现的不合理的点:
1、*hh = 15; std::cout<<*hh<<std::endl;这两句没有一起执行,而且在离开 shared_ptr 作用域的时候,第一句是肯定执行了的。
2、test 没有 返回之前,miaomiao15 就已经输出了。

这一切诡异的背后,都指向了一个关键词”缓冲区”,于是我把所有的 cout 换成了 printf,再次运行,快乐的事情发生了

1
2
3
4
15
jiujiu15
miaomiao15
wangwang

或者像这个样子,结果也是一样哈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <stdio.h>
#include <memory>

int test(){
int hh =10;
{
std::shared_ptr<int> p(&hh,[](int *hh){
*hh = 15;
std::cout<<*hh<<std::endl;
//printf("%d\n",*hh);
});
}
std::cout<<"jiujiu"<<hh<<std::endl;
//printf("jiujiu%d\n",hh);
return hh;
}
int main(){
int xixi = test();
std::cout<<"miaomiao"<<xixi<<std::endl;
//printf("miaomiao%d\n",test());
std::cout<<"wangwang"<<std::endl;
}

画图

If I can,I wish to see the stream of every bit. 所以透明什么的最讨厌了。。

恰完饭再画图叭~!(或许是明年了。。)