一、bool值的范围
根据C++语言中的说明,一个bool值可能的值只有两个,要么是0,要么是1。但是对于一个未初始化的bool值变量,它的取值范围却要丰富多彩的多,它的取值范围应该是可以为任意的一个单字节整数。测试的程序非常简单,大家随便打印一下一个未初始化的bool变量的值就应该可以看到各种各样的初始化值。
二、测试代码
[tsecer@Harry boolval]$ cat boolval.cpp
int level = 0;
#include <stdio.h>
int recursive(bool b_var)
{
if (level++ > 1024*20 )
return 0 ;
bool b_localvar;这个变量是木有初始化的。
printf("%d ",b_var);
recursive(b_localvar);
}
int main()
{
recursive(1);
}
[tsecer@Harry boolval]$ g++ boolval.cpp -o
a.out boolval.cpp boolval.cpp.exe
[tsecer@Harry boolval]$ g++ boolval.cpp -o boolval.cpp.exe
[tsecer@Harry boolval]$ ./boolval.cpp.exe | more
1 8 8 8 8 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 8 8 8
这个命令一直输出,输出到最后这个输出结果一直是这个非常吉利的8这个数字。按照正常来说,操作系统给用户第一次映射的物理页面是执行过清零动作的,所以所以也就是说输出的所有结果应该是另一组为0的。我以为这里是乱码,但是在打印了5个物理页面之后,打印的内容依然还是这个8,这就有些诡异的。
三、这里为什么不是0也不是1
[tsecer@Harry boolval]$ gdb boolval.cpp.exe
GNU gdb (GDB) Fedora (7.0-3.fc12)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/tsecer/CodeTest/perltest/boolval/boolval.cpp.exe...(no debugging symbols found)...done.
(gdb) disas recursive
Dump of assembler code for function _Z9recursiveb:
0x08048474 <_Z9recursiveb+0>: push %ebp
0x08048475 <_Z9recursiveb+1>: mov %esp,%ebp
0x08048477 <_Z9recursiveb+3>: sub $0x38,%esp
0x0804847a <_Z9recursiveb+6>: mov 0x8(%ebp),%eax
0x0804847d <_Z9recursiveb+9>: mov %al,-0x1c(%ebp) 传递给recursive的bool值放入一个byte结构中。
0x08048480 <_Z9recursiveb+12>: mov 0x80497e4,%eax
0x08048485 <_Z9recursiveb+17>: cmp $0x5000,%eax
0x0804848a <_Z9recursiveb+22>: setg %dl
0x0804848d <_Z9recursiveb+25>: add $0x1,%eax
0x08048490 <_Z9recursiveb+28>: mov %eax,0x80497e4
0x08048495 <_Z9recursiveb+33>: test %dl,%dl
0x08048497 <_Z9recursiveb+35>: je 0x80484a4 <_Z9recursiveb+48>
0x08048499 <_Z9recursiveb+37>: mov $0x0,%eax
0x0804849e <_Z9recursiveb+42>: mov %eax,%edx
0x080484a0 <_Z9recursiveb+44>: mov %edx,%eax
0x080484a2 <_Z9recursiveb+46>: jmp 0x80484c4 <_Z9recursiveb+80>
0x080484a4 <_Z9recursiveb+48>: movzbl -0x1c(%ebp),%eax
0x080484a8 <_Z9recursiveb+52>: mov %eax,0x4(%esp)
0x080484ac <_Z9recursiveb+56>: movl $0x80485b4,(%esp)
0x080484b3 <_Z9recursiveb+63>: call 0x80483a0 <printf@plt>
0x080484b8 <_Z9recursiveb+68>: movzbl -0x9(%ebp),%eax
0x080484bc <_Z9recursiveb+72>: mov %eax,(%esp)
---Type <return> to continue, or q <return> to quit---
0x080484bf <_Z9recursiveb+75>: call 0x8048474 <_Z9recursiveb>
0x080484c4 <_Z9recursiveb+80>: leave
0x080484c5 <_Z9recursiveb+81>: ret
End of assembler dump.
(gdb)
从汇编代码中可以看到,其中bool变量是存在一个字节为单位来存储的,汇编代码首先把一个字节放入-0x1c(%ebp)中的,高3字节单位全部清零。所以参数中的字节内容的取值范围是0到255字节的范围都是有可能的。
四、为什么一直是8而不是0
我当场拿出纸笔比划了一下,情况可能是这样的:
秘密在于递归执行recursive函数之前,函数还调用了printf函数,该函数将会提前污染堆栈空间中的内容,而整个8是一个0x80地址的最高的一个字节,大家知道,在通常的桌面系统中,用户态的堆栈空间是从2G开始的,也就是0x80开始的堆栈地址。为了便于分析,我这里使用了静态链接的版本分析:
0x0804828c <_Z9recursiveb+48>: movzbl -0x1c(%ebp),%eax
0x08048290 <_Z9recursiveb+52>: mov %eax,0x4(%esp)
0x08048294 <_Z9recursiveb+56>: movl $0x80b006c,(%esp)
0x0804829b <_Z9recursiveb+63>: call 0x8054ad0 <printf> 此处call返回值占用一个int。
0x080482a0 <_Z9recursiveb+68>: movzbl -0x9(%ebp),%eax 这个是发送给子函数的bool值的参数。我们现在分析一下之前调用的printf是如何确定的修改这个值的。
0x080482a4 <_Z9recursiveb+72>: mov %eax,(%esp)
---Type <return> to continue, or q <return> to quit---
0x080482a7 <_Z9recursiveb+75>: call 0x804825c <_Z9recursiveb>
0x080482ac <_Z9recursiveb+80>: leave
0x080482ad <_Z9recursiveb+81>: ret
printf的代码
(gdb) x/30i 0x8054ad0
0x8054ad0 <printf>: push %ebp push占用一个int。
0x8054ad1 <printf+1>: mov %esp,%ebp
0x8054ad3 <printf+3>: sub $0xc,%esp最后一个esp指向的int就是将要使用的bool值。
0x8054ad6 <printf+6>: lea 0xc(%ebp),%eax
0x8054ad9 <printf+9>: mov %eax,0x8(%esp)
0x8054add <printf+13>: mov 0x8(%ebp),%eax
0x8054ae0 <printf+16>: mov %eax,0x4(%esp)
0x8054ae4 <printf+20>: mov 0x80d3d80,%eax
0x8054ae9 <printf+25>: mov %eax,(%esp)
0x8054aec <printf+28>: call 0x8063960 <vfprintf> call在堆栈中占用一个int。我们看到的这个地址就是这里call调用的时候被CPU直接压入堆栈中的vfprintf函数的地址,而它的最高字节为0x08,所以显示的8就是此处的8。
0x8054af1 <printf+33>: leave
0x8054af2 <printf+34>: ret
最后的描述可能比较模糊,因为清楚的描述可能需要画图,那样会非常直观,只是我觉得没有这个必要,有兴趣的同学自己验证一下这个过程。