摘要: random()函数和rand()函数都可以产生随机数,但是,两者的实现过程是不一样的,在使用这两个函数时总是会遇到一些疑问。该文结合实例分析了rand()函数产生随机数的过程,对不同随机函数的使用有一定的指导意义。
关键词:C++语言; 随机函数;随机数
中图分类号:TP312 文献标识码:A 文章编号:1009-3044(2015)34-0104-02
Abstract: random () function and rand () function can generate random numbers, but both the implementation process is not the same when using these two functions will always encounter some doubts. Examples of this paper analyzes the rand () function generates a random number process, the use of different random function has a certain significance.
Key words: C ++ language; random function; random numbers
在C语言中的随机数函数是randomize()和random(),前者是产生随机数的种子,而后者是根据前者生成的种子来产生随机数。random()函数产生的随机数是一种伪随机数。在C++语言的开发工具中(例如DEV C++)已经无法使用randomize()函数参数随机数了,取而代之的是rand()函数[1-3],接下来就分析一下rand()函数产生随机数的实现原理。
1 产生随机数的实例
C++语言编写的产生随机数的程序,如下代码所示。
#include "stdlib.h"
#include
using namespace std;
int main()
{for(int i=1;i<=10;i++)
cout< return 0; } 程序运行后输出10个整数,分别是: 41 18467 6334 26500 19169 15724 11478 29358 26962 24464 为了更加清晰的了解rand( )函数,可以通过工具逆向分析一下这个程序,逆向生成的主要代码如下。 CALL DWORD PTRDS:[<&MSVCR90D.rand>] ;调用rand函数 MOV DWORD PTR SS:[EBP-8],EAX ;rand的返回值赋值保存给[EBP-8] MOV EAX,DWORD PTR SS:[EBP-8] PUSH EAX PUSH OFFSET Randomiz.??_C@_0BF@NKDDNMAK@>; ASCII " Random number = %d" CALL DWORD PTR DS:[<&MSVCR90D.printf>] 首先程序先调用rand函数,然后把返回值赋值给寄存器EAX,再把EAX返回的随机数赋值给地址[EBP-8]保存,最后把EAX的值当做参数压栈后在调用printf()函数[4]。这就是输出模块的底层实现了,由于我们探寻的是rand()的函数的实现原理,所以,接下来探讨一下rand函数的内部实现。 2 rand函数的内部实现 rand函数的内部代码如下: 54E33085 PUSH ECX 54E33086 CALL MSVCR90D._getptd ;调用_getptd函数 54E3308B MOV DWORD PTR SS:[EBP-4],EAX 54E3308E MOV EAX,DWORD PTR SS:[EBP-4] 54E33091 MOV ECX,DWORD PTR DS:[EAX+14] 54E33094 IMUL ECX,ECX,343FD ;带符号数乘法指令 54E3309A ADD ECX,269EC3 ;加法 54E330A0 MOV EDX,DWORD PTR SS:[EBP-4] 54E330A3 MOV DWORD PTR DS:[EDX+14],ECX 54E330A6 MOV EAX,DWORD PTR SS:[EBP-4] 54E330A9 MOV EAX,DWORD PTR DS:[EAX+14] 54E330AC SHR EAX,10 ;逻辑右移 54E330AF AND EAX,7FFF ;和 54E330B4 MOV ESP,EBP 54E330B6 POP EBP 54E330B7 RETN 在rand( )函数的内部调用了一个未公开的函数_getptd(),无法得知_getptd( )函数的内部细节,不知道这个函数做了什么。但是,可以分析整个函数的代码,并结合rand函数的外部代码,可以看出_getptd( )函数的返回值并没有被使用。同时,变量[EAX+14]这个值引起了高度关注,可以深入分析变量[EAX+14]得出关于_getptd( )函数的信息[5]。可以借助OllyDbg动态反汇编工具看一看[EAX+14]的初始值,反汇编代码如下所示。
5E0C308B 8945 FC MOV DWORD PTR SS:[EBP-4], EAX
5E0C308E 8B45 FC MOV EAX, DWORD PTR SS:[EBP-4], EAX
5E0C3091 8B4814 MOV ECX, DWORD PTR DS:[EAX+14]
5E0C3094 69C9 FD430300 IMUL ECX, ECX, 343FD
5E0C309A 81C1 C29E2600 ADD ECX, 269EC3
5E0C30A0 8B55 FC MOV EDX, DWORD PTR SS:[EBP-4]
5E0C30A3 894A 14 MOV DWORD PTR DS:[EDX+14], ECX
5E0C30A6 8B45 FC MOV EAX, DWORD PTR SS:[EBP-4]
5E0C30A9 8B40 14 MOV EAX, DWORD PTR DS:[EAX+14]
通过反编译后的结果,可以看出原来[EAX+14]指向的地址是005A0814,而它的值是0x00000001,也就相当于十进制数1。知道了这个关键点,现在可以把rand( )函数的内部代码简化一下,如下代码所示。
MOV ECX, DWORD PTR DS:[EAX+14] ;ECX=000000001
IMUL ECX, ECX,343FD ;带符号数乘法指令
ADD ECX, 269EC3 ;加法
MOV DWORD PTR DS:[EDX+14],ECX
MOV EAX, DWORD PTR DS:[EAX+14]
SHR EAX, 10 ;逻辑右移
AND EAX, 7FFF ;和
MOV ESP, EBP
POP EBP
RETN
rand()通过把[EAX+14]地址的值赋值给ECX,然后再使用带符号数乘法指令和加法指令进行运算,最后把计算的结果重新赋值给[EAX+14],这样做是覆盖掉[EAX+14]原先的值00000001,然后在重新计算的[EAX+14]的值赋值给EAX,EAX在进行逻辑右移和和操作[6],通过这些复杂的运算,rand最终生成了一个随机数。
3 自定义随机函数
经过分析,比较清楚的了解了rand( )函数的实现原理,此时,就可以自己编写一个产生随机数的函数了,具体实现代码如下所示。
#include
#include
int _tmain(int argc, _TCHAR* argv[])
{
int tnum = 0x00000001;
int Rnum = 0;
while(1){
_asm
{
pushad
mov ecx, tnum
imul ecx,ecx,343fdH
add ecx,369ec3H
mov tnum,ecx
mov eax,ecx
shr eax,10H
and eax,7fffH
mov Rnum,eax
popad
}
printf(" %d\t",Rnum);
system(“pause”);
}
return 0;
}
编译运行此程序,输出的结果与rand函数运行的结果是一样的。
4 结束语
random()、rand()函数都可以产生随机数,但是在不同的编程语言及开发工具中的使用是有差别的。random函数不是ANSI C标准,不能在gcc,vc等编译器下编译通过。本文分析了rand函数的内部实现原理,分析了产生随机数的过程,拓展了初学习C语言同学的知识视野,也激发他们探索C语言奥妙的兴趣。
参考文献:
[1] 周建儒.C语言中逻辑关系与逻辑运算的分析[J].电子测试,2012(22):38-39.
[2] 周志德,候正昌.C++程序设计[M].北京:电子工业出版社,2005.
[3] 埃克尔,刘宗田.C++编程思想[M].北京:机械工业出版社,2012.
[4] 赵凤芝.C语言程序设计能力教程[M].北京:中国铁道出版社,2006.
[5] 刘金魁.C语言程序设计基础[M].北京:中国铁道出版社,2014.
[6] 田美艳.计算机中C语言的应用特点分析[J].电子制作,2015(4):87.