精华内容
下载资源
问答
  • 详解游戏辅助编程 目录 什么是windows API windows进程 windows 的内存的运行原理 windows 中句柄的概念 windows的变量类型 辅助实现的原理 编程实现游戏辅助 怎样查找内存地址 总结 准备软件 软件环境:VC,...

    详解游戏辅助编程 目录 什么是windows API windows进程 windows 的内存的运行原理 windows 中句柄的概念 windows的变量类型 辅助实现的原理 编程实现游戏辅助 怎样查找内存地址 总结 准备软件     软件环境:VC,CheatEngineer     学习这部分内容,你必须要掌握C语言的基础知识,非常基础的语法就行了。这篇文章的内容适合刚开始接触编程的人,高手请飘过。 1.什么是windows API     windows API 中文翻译过来就是windows应用程序接口(Application Programming Interface)     我们知道,我们在使用C/C++的时候,会包含很多的头文件,例如最常用的stdio.h,里面有很多函数的声明,例如使用scanf函数能输入数据,使用printf函数能打印文本到屏幕上显示出来。又例如math.h里面有很多数学计算函数,如sqrt求平方根的函数。我们只要包含这些头文件,就能调用这些函数。只要给函数传递相应的参数,就能实现我们想要的效果,这些函数就是C语言给我们提供的“接口”。     所谓windows应用程序接口,其实就是windows提供给程序员的一组函数。     Windows api 包含几千个可调用的函数,这些函数能让我们程序员操作系统的方方面面。     我们就可以调用者几千个函数实现各种功能。如调用ExitWindowsEx来关闭计算机,CopyFile来复制文件,MessageBox来弹出系统提示框,用BitBlt来绘制图像等等。而调用这几千函数的条件,就是在代码的开头加上#include 就行了,因为绝大部分的函数声明都在windows.h里面,我们只要包含了这个头文件,就能够使用这些函数。     所以可以这么理解     Windows API =windows提供的几千个函数 2.windows进程     关于进程,我想大家都应该熟悉这个名词。在应用程序无响应无法关闭的时候,我们常常使用CTRL + ALT +DELETE来叫出任务管理器     我们可以看到一个进程列表,其中列出了所有正在运行的进程          进程的概念:进程就是一个正在执行的程序。     假如我们要运行一个程序,首先找到文件(如qq.exe)或者快捷方式,双击打开。操作系统就会把这个程序的文件从硬盘上加载到内存中,等加载完毕后,CPU开始执行包含在程序中的代码,然后系统进程列表多了一个qq.exe,这就是进程——正在运行的应用程序。等到这个程序qq.exe运行结束了,系统就会释放这个程序在内存中占用的空间,并且从进程列表里移除这个程序。     进程的特点:每个进程都是相互独立的,互不干扰的。通常情况下,一个进程只能操作自己的代码和数据,无法对其他进程造成影响。这也保证了windows系统运行的稳定性。     进程=正在内存中运行的程序 3.windows内存的运行原理     这个是外挂编程的最重要部分,我们分几个方面讨论: (1) 什么是内存     内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁,其存取速度比硬盘快很多倍。内存对应是电脑的内存条,能存储数据,但是与硬盘不同,在内存中的数据一断电就会消失,并且存取数据的速度是硬盘的几十倍,所以将程序加载到内存中运行将大大提高程序的运行速度。现在一般的电脑内存2GB,4GB,8GB或者更高的16GB。     计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大,并且CPU只能执行和处理在内存中代码和资源。     程序的存在形式实际上是这样的:我们的程序文件,首先是以二进制存在于硬盘上的,有的游戏特别大,几个GB,当我们打开资源管理器时,查看的,其实就是硬盘上的文件。当我们运行程序时,程序会适当的从硬盘加载自己需要的数据,然后开始运行。     由于内存比较少,当程序不需要这些数据的时候,就会从内存中释放一些不需要的资源,以保证内存的充足。     总的看来当执行一个程序的时候,系统会将exe文件中的代码和资源从硬盘加载到内存中,加载完成后,CPU开始执行程序入口点的代码,一个程序开始运行。     程序结束时,将释放所有这个进程占用的资源,以避免内存的浪费。     概括来说:内存,其实存储运行中的程序代码和数据的地方(CPU只能处理内存中的代码和数据),当进程结束时,该进程所有占用的内存空间将被系统释放。 (2)进程的边界--虚拟内存空间     我们设想一下,假如一个系统中有很多的程序在运行,我们只有一个内存空间,这样的话,一个程序在读写数据的过程中,由于程序自己本身的缺陷,错误的读写了其他程序的数据,这样就很容易影响其他程序,造成其他程序的崩溃。这当然是我们不愿意见到的。     为了解决程序之间使用内存互相干扰的问题,于是便有了虚拟内存。     Windows的虚拟内存机制为每个进程分配了4GB的虚拟内存空间。这里我们不免产生疑问,我们的实际物理内存空间只有1GB或者2GB的,等等,怎么能给每个进程分配4GB内存空间呢?     实际上,这4GB空间最下面的64KB是空指针赋值区域,系统保留。内存最上面的2G被系统占用。我们能够访问和使用的,也只有中间这一部分内存而已。这部分内存将近2GB,我想对所有程序来说,存储代码和数据都是够用的。     但是每个进程都有这么大内存可以用,这些内存空间从何而来呢。Windows在执行一个程序的时候,为了节约内存空间,将程序内存中暂时用不到的数据,以二进制存储到硬盘上(这些二进制文件其实就是页面文件)。等到需要的时候,再从硬盘上加载到内存中,这样就以牺牲少量CPU运行时间为代价,将硬盘当做内存使用。使得多程序同时执行成为了可能。这4GB中的数据,有的是存在于硬盘上的,有的是存在于内存中,所以说,每个进程的4GB内存空间,是“虚拟”内存。     虚拟内存的地址一般用十六进制表示,以字节为单位,地址范围是0x00000000~0xFFFFFFFF(0x表示十六进制)。     由于这样的机制,我们不难发现,进程之间就不能相互影响,保证了各进程的稳定性。例如有两个进程A和B,进程A可以在它自己内存空间0x12345678地址存储数据,进程B也可以在0x12345678地址处存储数据。当进程A访问0x12345678处的内存时,访问的是进程A的内存空间;当进程B访问0x12345678处的内存时,访问的是进程B的内存空间。进程A无法访问位于进程B的内存空间中的数据,反之亦然。     这就是进程的边界--每个进程分配了4GB的虚拟内存空间,它们在自己的内存空间内运行,不会相互干扰,保护了系统和各进程的稳定性。     为什么要以16进制表示地址     编程中,我们一般用十进制表示一个数,因为C/C++是高级语言。     例如x=78;     不过,由于数据在计算机中的表示,最终以二进制的形式存在,所以有时候使用二进制,可以更直观地解决 问题。但二进制数太长了。比如int 类型占用4个字节,32位。比如100,用int类型的二进制数表达将是:       0000 0000 0000 0000 0110 0100        面对这么长的数进行思考或操作,没有人会喜欢。因此,C,C++ 没有提供在代码直接写二进制数的方法。用16进制可以解决这个问题。因为,进制越大,数的表达长度也就越短。     例如同样一个地址。用十进制和十六效果对比下: 十进制 十六进制 1258965451 4B0A49CB 5600 000015E0 85693 00014EBD 8965231 0088CC6F     这样可以看到用十六进制表现更直观一些。并且,在很多软件中,数值的表示都是用十六进制的,例如WinHex,CE,OllyDBG,几乎所有与内存有关的数据都是用十六进制表示。用十六进制最大的优点就是缩小了表达长度。 (3) 打开进程的边界      当然所有的事情不是绝对的,我们有很多时候也需要访问其他进程中的数据。例如,杀毒软件要监视其他进程的行为,防止其他进程做有害系统的事,杀毒软件往往就会在其他进程里注入自己的代码,以此来监视其他进程的行为。这就是访问其他进程数据的最好例子。     当然,想要访问其他进程的内存空间,需要强大的windows api。我们知道,windows api是系统提供给程序员的一组强大的函数,几乎能实现任何关于系统的任何操作。     我们要打开进程的边界,修改其他进程的数据,当然需要调用相应的函数。     我们的目的很明确,就是要修改其他进程(如游戏进程)的数据。     下面的一些函数是访问其他进程数据和外挂编程必备的函数,在后面介绍外挂编程的时候,我会详细的介绍这几个函数,现在先熟悉一下。 1. 查找窗口函数 FindWindow 2. 通过窗口获取进程ID函数 GetWindowThreadProcessId 3. 打开进程函数 OpenProcess 4. 写其他进程数据的函数 WriteProcessMemory 5. 读其他进程数据的函数 ReadProcessMemory 6. 关闭句柄的函数 CloseHandle 这六个函数,就是写一个简单外挂的所有函数。在后面我会详细说明打开进程边界的步骤,以及如何使用这些函数。 4.windows 中句柄的概念     句柄,是整个windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个四字节长的数值,来标志系统中的不同对象。     打个比喻,一个学校有很多很多的学生,为了识别这些学生,我们会给每个学生分配一个学号。当我们要找一个学生的时候,我们只需要知道这个学生的学号,然后查阅相关信息就能找到这个学生。     同样,在windows系统中有很多很多的对象,如一个窗体,一个进程,一个图标,一张图片,甚至一块内存空间等。Windows 给它们都分配了不同的一个unsigned int型数(即无符号整型数)来标识和区分它们。这个标识号,就叫做句柄。     我们可以通过系统分配的这个标识号,也就是句柄,来访问这些对象。     例如,我们可以通过一个窗口的句柄,找到这个窗口,然后可以修改这个窗口的大小,标题名字等等。     我们可以通过一个进程的句柄,来结束这个进程。     我们可以通过一张图片的句柄,来将这张图片画在屏幕上。     等等等等。     句柄,就是系统中每个对象的标识号,我们可以通过这些标识号,来访问相应的系统对象,如窗体,进程等等。 5.windows变量类型     在windows编程中,我们往往会看到很多变量类型,如HANDLE,HWND,BYTE,DWORD等等。     这些变量类型是什么呢,和我们熟悉的char,short,int等等的变量类型有什么区别呢。     而经常要用到的  句柄  HANDLE类型,实质上是无类型指针void,HANDLE定义为: typedef PVOID HANDLE;     HANDLE实际上就是一个PVOID,那PVOID又是什么呢? typedef void *PVOID;     PVOID就是指向void的指针(void *)。     所以HANDLE = void*     那为什么要多此一举呢,直接用void*代替就行了,为什么要用HNADLE呢?其实,这是为了让程序员更能读懂程序。只要看到变量类型就能知道这个变量是用来干什么的。     例如:我要用a,b来表示长方形的宽和高,如果都用int型我们要花费一番功夫才能理解这些意思。     但是我们分别定义两种变量类型Width,Height就能一目了然了。 typedef int Width; typedef int Height; Width a; //定义width型变量a,一眼就能看出a变量是来表示宽度的 Height b; //定义wheight型变量b,一眼就能看出b变量是来表示高度的     声明特定的变量类型其好处就不言而喻了,就是为了让程序员更好的理解任何变量的作用,使人一目了然。     下面我们需要知道的变量类型以及这些变量类型的作用;     HANDLE =void* HANDLE型变量是一个对象句柄     HWND   =void* HANDLE WINDOW 窗口句柄     DWORD  = unsigned int   无符号整型,windows通常用来表示一个对象的序号ID     BYTE   =unsigned char   无符号字符型,0-255     我们做外挂,只需要了解上面3种windows数据类型就行。     另外我需要声明一点:void *型的变量,一般都是用来表明内存地址的,     如 void * ptr=0x12345678     这个ptr指针,表示的是0x12345678这个内存地址     指针我现在不想多说了,这部分在后面的编程中会看到 6.辅助的的实现原理     我们现在做的只是游戏外挂,有一句古话说的好:知己知彼,方能百战不殆。我们要做游戏的辅助,就要先知道游戏是怎样运行的。     我们知道,一个游戏进程的有很多数据,例如,一个角色的HP,一个角色的经验,他的金钱,等级,以及装备都是通过变量来存储的。我们只要找到这些变量在内存中的地址,然后通过某种方法去修改这些数的数值,就能达到修改游戏的目的。     我们先来说说这些游戏中的数据,怎么判断是什么类型,怎么得到在内存中占的空间大小。     例如,一个人的经验一般用int型变量(4字节)来存储,为什么呢?     因为int型变量(4字节)的取值范围是2147483648~2147483647,而short型变量(2字节)的范围是,由于short的范围太小,而很多游戏的经验值一般都超过这个范围,例如在地下城与勇士的游戏中,我的经验值是108866674523,一千多万,所以,在这个游戏中,一个人物的经验值是必须是int型的,用short型变量会超出范围导致程序运行出错。     我们为什么要知道这些变量占多大内存空间呢?那是因为我们在修改其他进程的数据的时候,我们必须首先要有三个参数:1-在哪个地址 2-要修改成多少 3-有多大的内存数据要被修改。例如:我们要修改的地址是0x00EFFAE0,要修改成1000000,由于这个变量是int型的,所以,有4字节的内存数据要被修改。这很容易让我们想到游戏程序的源代码里面有这条代码           int exp;     //人物经验     然后&exp就等于0x00EFFAE0     好吧,开始进入正题了。游戏辅助一般分为两大部分:     一就是找出我们想要的内存地址。     二就是写程序去修改这个内存地址的数到一个相应的数值。     我们先不谈第一个部分,因为第一个部分变化性大,因为很多游戏做了不同程度的保护,使我们找内存地址会相对麻烦点,例如CS。不过也有游戏没做任何保护,例如,植物大战僵尸。但是,我们写程序修改其他进程内存中的数据,这个方法是不变的。所以我先来说说如何修改其他进程中的内存数据。     第一步:查找游戏窗口句柄     第二步:通过窗口句柄,获取目标进程的ID     第三步:通过目标进程的ID,打开目标进程,获得句柄     第四步:通过目标进程的句柄,修改目标进程的内存数据     第五步:关闭目标进程     我想有必要说一下,第一步,第二步是为第三步服务的,我们要修改某一进程的内存数据,必须获得这个进程在系统的身份证,即进程的句柄。获取一个进程的句柄有很多方法,我在这里说的第一步,第二步,第三步是最常用的获取目标进程句柄的方法。     但是,为什么如此曲折的才能获得目标进程的句柄呢,我想,主要和我们要使用的windows api 有关。     接下来我们就要具体说说这些强大的windows api函数了。我们之前说的所有知识,很多windows运行原理,都是为了理解下一节这些函数的调用。     并且下一节用到的函数较多,我们只有经常使用,我们才能掌握它们。 7.编程实现辅助     上一节我们说了:要修改一个进程的内存数据必须先获得这个进程的句柄,就像我们要找一个人一样,我们可以通过这个人的身份证,知道这个人住在哪里,才能找到这个人。在windows系统里面也一样,我们只有知道这个进程的身份证--句柄,系统才能找到这个进程,并相应的按照我们的需求修改数据。     在开始说辅助编程之前,我先要说说一个很有用的函数,这个函数就是MessageBox,先来看看这个函数有什么效果。                                           是不是有种熟悉的感觉,没错这就是我经常看见的windows提示,现在我们程序员可以自由操作windows提示。现在来具体说说MessageBox函数     首先来看看函数原型 int WINAPI MessageBox( HWND hWnd,//消息窗口父窗口句柄 LPCTSTR lpText,//显示的消息内容 LPCTSTR lpCaption, //消息框的标题 UINT uType);//消息框风格     第一个参数:消息框的父窗口句柄,为了方便,我们可以设为NULL,不影响辅助的使用。     第二个参数:显示的消息内容,上面图片示例中游戏已经运行,游戏没有运行都属于消息内容     第三个参数:消息窗口标题,上面图片示例中都是“提示”。     第四个参数:消息框的风格,现在我只介绍三个常量     MB_OK:表示有确定按钮     MB_ICONINFORMATON:表示有信息图标,上左图     MB_ICONERROR:表示有错误图标上右图     用 ” | ”符号同时使用多种风格,例如使用MB_OK|MB_ICONINFORMATION,确定按钮和信息图标,上左图所示。MB_OK|MB_ICONERROR,确定按钮和错误图标,上右图所示。     下面,进入正题我们来说说修改其他进程内存数据的第一,二,三步——获取目标进程的句柄,并且学习相应的函数。 第一步:获取目标游戏窗口的句柄     在windows中,一个窗口的句柄数据类型是HWND,就是handle window的简写,我们可以这样,前面我们已经说过windows变量类型的概念,HWND其实就是void*类型的,这里我就不多说了。重点我们知道怎么用。     window API里面有这么一个函数,函数原型如下 HWND FindWindow( LPCSTR lpClassName, LPCSTR lpWindowName);     我们可以知道,返回值HWND类型的变量是存储一个窗口的句柄的。那么LPCSTR是什么变量类型呢?     LPCSTR=char*,这个变量类型其实就是char*类型,是一个字符串的指针,以后我们只要看到LPCSTR类型的变量,我们就可以知道这个变量是存储一个字符床地址的指针的。这就是声明很多变量类型的好处,看到这个变量是什么类型的,就知道这个变量是用来干什么的。     它有两个参数,两个参数都是字符串的指针     第一个参数lpClassName,这是要查找窗口的类名,关于窗口的类名,这里没有说明,主要是因为我们可以将此参数设为NULL,也基本不影响我们查找游戏窗口的句柄。有兴趣的可以看看《windws核心编程第5版》,上面说的很详细。     第二个参数lpWindowName,这个就是主要参数了,目标窗口的标题,例如,植物大战僵尸的窗口标题就是“植物大战僵尸中文版”,红色警戒窗口的标题就是“Red Alert 2”.但是有很多游戏是全屏幕显示的,不是窗体形式的,没有标题,那我们怎么知道它的标题呢?其实有一种很简单的方法,游戏全屏运行后,就是按开始菜单键将游戏最小化。然后将鼠标移动到任务栏的游戏图标上,系统就会提示该窗体的标题。     知道了这两个参数,我们可以这么写代码调用这个函数。 HWND gameWindow=FindWindow(NULL,”植物大战僵尸中文版”);     当这行代码执行完毕后我们gameWindow这个变量就保存游戏窗口的句柄。但是我们需要注意,当目标进程没有运行,也就是不存在窗体标题为”植物大战僵尸中文版”的窗体时,游戏系统找不到这个窗口,FindWindow调用失败,返回值为0,即gameWindow为0。所以这个函数,可以判断游戏有没有运行。加上下面代码就有这个效果 if(gameWindow==NULL){ //提示游戏没有运行 }else{ //提示游戏已运行 } 第二步:通过窗口句柄,获得进程ID     windows api提供了这样一个函数,函数定义如下 DWORD GetWindowThreadProcessId( HWND hWnd, LPDWORD lpdwProcessId );     我们可以看到,这个函数只有两个参数,第一个参数,就是目标窗口的句柄(上例中的gameWindow),我们只要填上我们获取到的窗口句柄就OK     第二个参数LPDWORD,LP代表指针,DWORD代表unsigned int,所以这个参数就代表unsigned int *,是一个无符号整数型的指针。那么这个参数是什么呢?     每个进程不仅有自己的句柄,还有自己的序号,在系统中叫ID,这个ID是DWORD型整数,在windows中就叫ProcessID          上面这张图就是我用tasklist命令列举出系统中正在运行的进程,上面的PID(ProcessID)就是该进程的标识号,可以看到,wininit.exe进程的ID是516 ,csrss.exe进程的ID是528,等等。我们可以通过目标窗口的句柄,知道这个窗体是属于哪个进程的,然后通过这个函数我们就可以知道,这个窗体所在进程的ID,我们可以这样使用。 DWORD pid; GetWindowThreadProcessID(gameWindow,&pid);     第二个参数传入一个DWORD变量的地址就行了。     当这行代码运行完毕后。pid就自动填充了目标窗体所在进程的ID。 第三步通过目标进程的ID,打开目标进程,获得句柄     windows api提供了这样一个函数 HANDLE OpenProcess( DWORD dwDesiredAccess, //渴望得到的访问权限(标志) BOOL bInheritHandle, // 是否继承句柄 DWORD dwProcessId// 进程标示符即,进程ID );     首先它的返回值是HANDLE类型的,返回值就是目标进程的句柄。再来看看这三个参数。     第一个参数,我们想要得到的访问权限。这里我们使用常量PROCESS_ALL_ACCESS,这个是在windows.h里面定义的常量,注意要全部大写。用了这个常量,就等于我们对系统说:“我要获取对该进程操作的所有权限”,例如读写内存空间等等。等这个函数调用成功后,我们就可以获取对该进程进行任何操作了。     第二个参数,我们不考虑,设为NULL。     第三个参数,就是我们获得的进程ID。     我们可以这么调用:     HANDLE hProcess=OpenPrcess(PROCESS_ALL_ACCESS,NULL,pid);     这样这行代码运行后,hProcess被填充了目标进程的句柄。当然,如果这个函数因为某种原因调用失败的话hProcess就为NULL,所以我们可以加上下面的错误处理代码 If(hProcess==NULL){ //提示打开进程失败 }     好现在总结一下这个步骤。     我们先通过FindWindow找出目标窗体的句柄     再通过GetWindowThreadProcessId获得目标进程的ID     最后通过OpenPocess打开进程,获得句柄     最后需要特别注意一点,在windows xp上这么写代码会运行正常,但是在windows7或者windows8上,程序必须要管理员权限。FindWindow会因为你没有管理员权限而调用失败,最后导致你的程序无法正确的获得目标进程句柄。 第四步修改进程的内存数据     这是最关键的一步了,修改内存数据。前面我们已经说过,外挂主要分为两个重要步骤,第一个是找内存地址,哪些数据是我们要修改的,如一个人的金钱,经验,属性,等级。我们要找出这些数据在进程中的内存地址。第二步就是写程序去修改这些数据。写程序修改数据的方法是一成不变的,但是找内存地址却有很大的技巧性。这里限于篇幅就不多说了,后面我会简单的介绍一下找代码的原理,然后我会推荐一些专门的文章给大家看的。     Window提供了下面一个函数来修改进程的内存数据,函数定义如下 BOOL WriteProcessMemory( HANDLE hProcess,//目标进程句柄 LPVOID lpBaseAddress,//目标进程写入地址 LPVOID lpBuffer,//自己进程中缓冲区地址 DWORD nSize,//缓冲区大小 LPDWORD lpNumberOfBytesWritten//实际数据长度,设为NULL );     返回值是BOOL型,返回TRUE调用成功,FALSE调用失败     第一个参数:目标进程的句柄,我们可以通过前三步获得目标进程的句柄     第二个参数:目标写入的起始地址,即要将数据写到目标进程哪个位置。这个就是我们找到的内存地址,例如一个人的血量内存地址是0x40000000     第三个参数:写入的缓冲区地址     第四个参数;写入的缓冲区大小     这第三个,第四个参数是什么意思呢?其实WriteProcessMemory工作原理是这样的        我们将自己进程中的数据,拷贝到其他进程中。自己这个缓冲区的地址就是第三个参数lpBuffer,缓冲区的大小事第四个参数nSize。这个函数将我们进程中lpBuffer地址起始处nSize大小的数据,原封不动的拷贝到目标进程的 lpBaseAddress(第二个参数)处。     假如,我们找到游戏进程人物金钱的地址是0x40000000,,我们就可以这样写代码 int Money=10000000;//定义一个变量后自己进程已经分配一块区域存这个变量 WriteProcessMemory(hProcess,(LPVOID)0x40000000,(LPVOID)&Money,,(DWORD)4,NULL);     第一个参数我们填入目标进程的句柄     第二个参数是目标进程的写入地址,编译器认识“0x”,知道0x40000000是一个16进制的数,我们将这个数强制转换成LPVOID型,也就是void*型,无值型指针     第三个参数我们填入了我们进程Money变量的地址,并且将这个变量地址强制转换成LPVOID型。     第四个参数由于在c++中int型变量默认占四个字节内存空间,所以我们填入4,并将这个数转换成LPVOID型     第五个参数 填NULL     这就是WriteProcessMemory的用法,功能强大,能写其他进程的数据。     这样,我们就完成了第四步,写游戏内存数据,我们再次回到游戏会发现,游戏中相应的数据已经变成我们想要的数值了,看下图,植物大战僵尸修改后的结果      第五步关闭进程句柄     我们需要调用CloseHandle函数来关闭我们打开的进程,这个函数的使用方法很简单。     CloseHandle(hProcess);     它只有一个参数------要关闭的句柄这里我们填入我们打开的进程句柄即可。     至此我们已经看到了一个完整的程序代码     我们来回顾一下          我想没有什么能比真实的游戏辅助源代码更好的了解外挂了,下面,我将给出Vc++6.0环境下,植物大战僵尸辅助的真实源代码。我把修改代码放到了ChangeGame函数里面,我们只要在main函数或者WinMain中调用这个函数就ok void ChangeGame() { //通过标题获取窗口 HWND gameWindow=FindWindow(NULL,"植物大战僵尸中文版"); if(!gamewindow) MessageBox(NULL,”游戏未运行”,”错误”,MB_OK|MB_ICONERROR); //获取进程标示符pid DWORD pid; GetWindowThreadProcessId(gameWindow,&pid); //打开进程 HANDLE hprocess=OpenProcess(PROCESS_ALL_ACCESS,0,pid); //修改数据 int sun=56789; WriteProcessMemory(hprocess,(void *)0x1429E2B0,&sun,4,0); //关闭进程 CloseHandle(hprocess); } 8.怎样查找内存地址        CE是一个强大的工具,它的搜索速度非常快,一般的游戏程序用它来搜索内存能在短短数秒内搜索几百万甚至几千万个内存地址。为我们编写外挂提供了很大的帮助。     好,废话不多说,边上图边解说,先来学学简单的内存修改,熟悉熟悉CE的操作。     第一步:选择目标进程                               先点击左上角的第一个按钮,会出现右图进程列表。该列表中列举出了系统中所有正在运行的程序。我们选择植物大战僵尸程序(PlantVsZombies.exe)。这样就完成了选择目标进程。     第二步:搜索阳光的内存地址     先运行植物大战僵尸,开始一局游戏,我们的目的是要修改阳光。于是先看看阳光现在是多少,150,很好(下左图)。我们在CE值里面的填入150,点击首次扫描(下右图)                           看CE左边的地址栏,我们可以看到,我们用CE搜索到了145个地址(下左图)。这说明在这个游戏中,有这么多个地址的值是150.。但是我们所需要的阳光的变量,只是这么多其中的一个,所以我们种一颗植物,现在阳光变成了50,然后我们输入50,点击再次扫描(下右图)                             再次扫描其实是在上次扫描的结果中,再次搜索这些结果的值。当我们通过游戏操作修改我们所需要的变量的值的时候,通过再次搜索就很容易把我们想要的变量找出来。之后会出现下面的结果                                    我们可以看到,结果地址里面只有一个了,没错,这个搜索到的地址0x1429E2B0就是我们要找到阳光的地址。我们双击这个地址,这个地址会出现在下面。的编辑列表里,我们再双击编辑列表里这个地址的值,输入56789,点确定。这个时候我们回游戏就会发现已经变成我们想要的值了。     好了,这是找内存的最简单的方法。但是还有一些问题。     第一个问题:     事实上,有很多游戏都做了一些保护措施。例如你的植物大战僵尸里面金币显示的是1120,但是实际上在内存中这个值是112。如果你一开始搜1120,并且按照这个方法搜下去的话会一个地址也搜不到。相同的例子还有很多。例如侠盗飞车人物的血量上面显示的是87,在内存中这个数值是17023+87,流星蝴蝶剑中你看到你的血是230,但是在内存中你的血的值是2300等等。     第二个问题:     如果你重启植物大战僵尸后会发现,阳光变量的地址发生了改变,变成0x143780B4,这是为什么呢。因为植物大战僵尸中,存储阳光的变量地址是动态分配的,每次分配的地址都是不一样呢。     这就是编程中的局部变量,这个局部变量所属的函数执行完了,这个局部变量就会从内存中释放。等下次再次执行这个函数时,这个局部变量又会从内存中重新分配空间。     这样每次运行植物大战僵尸,由于动态分配,每次阳光变量的的地址都是不一样的。     所以说,搜索也是有一定复杂性的。第一个问题可以通过模糊搜索,多次搜索解决。第二个问题,可以通过查找变量的偏移地址,计算出准确的阳光地址(这个需要ReadProcessMemory函数)或者通过反汇编。     虽然听起来很复杂,但是这些搜索技巧其实都是很简单的,这里就不一一介绍了。因为要说搜索内存的方法的话,说清楚没有几千字是不行的。这篇文章主要是说外挂编程的原理,关于内存搜索的方法,我找到几篇很好的文章,大家看一看就足够了:     CE找内存偏移地址教程请网上自行搜索。     关于找内存地址就说到这里。 9.总结     游戏辅助,其实就是用来修改游戏,方便玩家玩游戏的的一个软件工具。     这篇文章所说的游戏修改都是初级的游戏修改,只涉及到修改内存变量。事实上,像高级的游戏修改,例如消除冷却时间,修改游戏界面,改变游戏运行逻辑等等,这些知识还远远不够。这些都需要最低级而又最强大的汇编语言来解决。通过反汇编逆向分析游戏代码,修改游戏代码而达到令人咋舌效果。     例如LOL盒子,至少运用了DLL远线程注入,函数挡截,反汇编代码修改等等技术,所以能够给游戏添上一些辅助功能。     学习编程的道路很漫长,有很多的非常有用知识不是课堂上能学到的,等着你自己去发现,去挖掘。等你深入到某一领域的时候,你会发现,这里还有非常广阔的天空,你要用一生的时间去探索它,掌握它。 --------------------- 本文来自 softdongs 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/qq401476683/article/details/10427531?utm_source=copy

    展开全文
  • 原著:Rick McMullin前言Linux的发行版中包含了很多软件开发工具. 它们中的很多是用于 C 和 C++应用程序开发的. 本文介绍了在 Linux 下能用于 C 应用程序开发和调试的工具. 本文的主旨是介绍如何在 Linux 下使用 C ...
    原著:Rick McMullin

    前言
    Linux的发行版中包含了很多软件开发工具. 它们中的很多是用于 C 和 C++应用程序开发的. 本文介绍了在 Linux 下能用于 C 应用程序开发和调试的工具. 本文的主旨是介绍如何在 Linux 下使用 C 编译器和其他 C 编程工具, 而非 C 语言编程的教程. 在本文中你将学到以下知识:
     

    什么是 C
    GNU C 编译器
    用 gdb 来调试GCC应用程序
      你也能看到随 Linux 发行的其他有用的 C 编程工具. 这些工具包括源程序美化程序(pretty print programs), 附加的调试工具, 函数原型自动生成工具(automatic function prototypers).

    --------------------------------------------------------------------------------
    注意: 源程序美化程序(pretty print programs)自动帮你格式化源代码产生始终如一的缩进格式. 
    --------------------------------------------------------------------------------

    什么是 C?
      C 是一种在 UNIX 操作系统的早期就被广泛使用的通用编程语言. 它最早是由贝尔实验室的 Dennis Ritchie 为了 UNIX 的辅助开发而写的, 开始时 UNIX 是用汇编语言和一种叫 B 的语言编写的. 从那时候起, C 就成为世界上使用最广泛计算机语言.
     
      C 能在编程领域里得到如此广泛支持的原因有以下一些:
    它是一种非常通用的语言. 几乎你所能想到的任何一种计算机上都有至少一种能用的 C 编译器. 并且它的语法和函数库在不同的平台上都是统一的, 这个特性对开发者来说很有吸引力.
    用 C 写的程序执行速度很快.
    C 是所有版本的UNIX上的系统语言.
      C 在过去的二十年中有了很大的发展. 在80年代末期美国国家标准协会(American National Standards Institute)发布了一个被称为 ANSI C 的 C 语言标准.这更加保证了将来在不同平台上的 C 的一致性. 在80年代还出现了一种 C 的面向对象的扩展称为 C++. C++ 将在另一篇文章 "C++ 编程"中描述.
      Linux 上可用的 C 编译器是 GNU C 编译器, 它建立在自由软件基金会的编程许可证的基础上, 因此可以自由发布. 你能在 Linux 的发行光盘上找到它.
     
     
     

    GNU C 编译器
      随 Slackware Linux 发行的 GNU C 编译器(GCC)是一个全功能的 ANSI C 兼容编译器. 如果你熟悉其他操作系统或硬件平台上的一种 C 编译器, 你将能很快地掌握 GCC. 本节将介绍如何使用 GCC 和一些 GCC 编译器最常用的选项.
     
    使用 GCC
      通常后跟一些选项和文件名来使用 GCC 编译器. gcc 命令的基本用法如下:
    gcc [options] [filenames]
      命令行选项指定的操作将在命令行上每个给出的文件上执行. 下一小节将叙述一些你会最常用到的选项.
     
    GCC 选项
      GCC 有超过100个的编译选项可用. 这些选项中的许多你可能永远都不会用到, 但一些主要的选项将会频繁用到. 很多的 GCC 选项包括一个以上的字符. 因此你必须为每个选项指定各自的连字符, 并且就象大多数 Linux 命令一样你不能在一个单独的连字符后跟一组选项. 例如, 下面的两个命令是不同的:
    gcc -p -g test.c

    gcc -pg test.c
      第一条命令告诉 GCC 编译 test.c 时为 prof 命令建立剖析(profile)信息并且把调试信息加入到可执行的文件里. 第二条命令只告诉 GCC 为 gprof 命令建立剖析信息.
     
      当你不用任何选项编译一个程序时, GCC 将会建立(假定编译成功)一个名为 a.out 的可执行文件. 例如, 下面的命令将在当前目录下产生一个叫 a.out 的文件:
    gcc test.c
      你能用 -o 编译选项来为将产生的可执行文件指定一个文件名来代替 a.out. 例如, 将一个叫 count.c 的 C 程序编译为名叫 count 的可执行文件, 你将输入下面的命令:
    gcc -o count count.c

    --------------------------------------------------------------------------------
    注意: 当你使用 -o 选项时, -o 后面必须跟一个文件名. 
    --------------------------------------------------------------------------------

      GCC 同样有指定编译器处理多少的编译选项. -c 选项告诉 GCC 仅把源代码编译为目标代码而跳过汇编和连接的步骤. 这个选项使用的非常频繁因为它使得编译多个 C 程序时速度更快并且更易于管理. 缺省时 GCC 建立的目标代码文件有一个 .o 的扩展名.
      -S 编译选项告诉 GCC 在为 C 代码产生了汇编语言文件后停止编译. GCC 产生的汇编语言文件的缺省扩展名是 .s . -E 选项指示编译器仅对输入文件进行预处理. 当这个选项被使用时, 预处理器的输出被送到标准输出而不是储存在文件里.

    优 化 选 项
      当你用 GCC 编译 C 代码时, 它会试着用最少的时间完成编译并且使编译后的代码易于调试. 易于调试意味着编译后的代码与源代码有同样的执行次序, 编译后的代码没有经过优化. 有很多选项可用于告诉 GCC 在耗费更多编译时间和牺牲易调试性的基础上产生更小更快的可执行文件. 这些选项中最典型的是-O 和 -O2 选项.
      -O 选项告诉 GCC 对源代码进行基本优化. 这些优化在大多数情况下都会使程序执行的更快. -O2 选项告诉 GCC 产生尽可能小和尽可能快的代码. -O2 选项将使编译的速度比使用 -O 时慢. 但通常产生的代码执行速度会更快.

      除了 -O 和 -O2 优化选项外, 还有一些低级选项用于产生更快的代码. 这些选项非常的特殊, 而且最好只有当你完全理解这些选项将会对编译后的代码产生什么样的效果时再去使用. 这些选项的详细描述, 请参考 GCC 的指南页, 在命令行上键入 man gcc .

    调试和剖析选项
      GCC 支持数种调试和剖析选项. 在这些选项里你会最常用到的是 -g 和 -pg 选项.
      -g 选项告诉 GCC 产生能被 GNU 调试器使用的调试信息以便调试你的程序. GCC 提供了一个很多其他 C 编译器里没有的特性, 在 GCC 里你能使 -g 和 -O (产生优化代码)联用. 这一点非常有用因为你能在与最终产品尽可能相近的情况下调试你的代码. 在你同时使用这两个选项时你必须清楚你所写的某些代码已经在优化时被 GCC 作了改动. 关于调试 C 程序的更多信息请看下一节"用 gdb 调试 C 程序" .
      -pg 选项告诉 GCC 在你的程序里加入额外的代码, 执行时, 产生 gprof 用的剖析信息以显示你的程序的耗时情况. 关于 gprof 的更多信息请参考 "gprof" 一节.
     
    用 gdb 调试 GCC 程序
      Linux 包含了一个叫 gdb 的 GNU 调试程序. gdb 是一个用来调试 C 和 C++ 程序的强力调试器. 它使你能在程序运行时观察程序的内部结构和内存的使用情况. 以下是 gdb 所提供的一些功能:
    它使你能监视你程序中变量的值.
    它使你能设置断点以使程序在指定的代码行上停止执行.
    它使你能一行行的执行你的代码.

      在命令行上键入 gdb 并按回车键就可以运行 gdb 了, 如果一切正常的话, gdb 将被启动并且你将在屏幕上看到类似的内容:
    GDB is free software and you are welcome to distribute copies of it

    under certain conditions; type "show copying" to see the conditions.

    There is absolutely no warranty for GDB; type "show warranty" for details.

    GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc.

    (gdb)
      当你启动 gdb 后, 你能在命令行上指定很多的选项. 你也可以以下面的方式来运行 gdb :
    gdb
      当你用这种方式运行 gdb , 你能直接指定想要调试的程序. 这将告诉gdb 装入名为 fname 的可执行文件. 你也可以用 gdb 去检查一个因程序异常终止而产生的 core 文件, 或者与一个正在运行的程序相连. 你可以参考 gdb 指南页或在命令行上键入 gdb -h 得到一个有关这些选项的说明的简单列表.
     
    为调试编译代码(Compiling Code for Debugging)
      为了使 gdb 正常工作, 你必须使你的程序在编译时包含调试信息. 调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号. gdb 利用这些信息使源代码和机器码相关联.
      在编译时用 -g 选项打开调试选项.
     
    gdb 基本命令
       gdb 支持很多的命令使你能实现不同的功能. 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令, 表27.1列出了你在用 gdb 调试时会用到的一些命令. 想了解 gdb 的详细使用请参考 gdb 的指南页.
     

    表 27.1. 基本 gdb 命令.

    命  令 描 述
    file 装入想要调试的可执行文件.
    kill 终止正在调试的程序.
    list 列出产生执行文件的源代码的一部分.
    next 执行一行源代码但不进入函数内部.
    step 执行一行源代码而且进入函数内部.
    run 执行当前被调试的程序
    quit 终止 gdb
    watch 使你能监视一个变量的值而不管它何时被改变.
    break 在代码里设置断点, 这将使程序执行到这里时被挂起.
    make 使你能不退出 gdb 就可以重新产生可执行文件.
    shell 使你能不离开 gdb 就执行 UNIX shell 命令. 

     
     
       gdb 支持很多与 UNIX shell 程序一样的命令编辑特征. 你能象在 bash 或 tcsh里那样按 Tab 键让 gdb 帮你补齐一个唯一的命令, 如果不唯一的话 gdb 会列出所有匹配的命令. 你也能用光标键上下翻动历史命令.

    gdb 应用举例
      本节用一个实例教你一步步的用 gdb 调试程序. 被调试的程序相当的简单, 但它展示了 gdb 的典型应用.
     
      下面列出了将被调试的程序. 这个程序被称为 greeting , 它显示一个简单的问候, 再用反序将它列出.
    #include 

    main ()
    {
     char my_string[] = "hello there";

     my_print (my_string);
     my_print2 (my_string);
    }

    void my_print (char *string)
    {
     printf ("The string is %s/n", string);
    }

    void my_print2 (char *string)
    {
     char *string2;
     int size, i;

     size = strlen (string);
     string2 = (char *) malloc (size + 1);
     for (i = 0; i < size; i++)
      string2[size - i] = string[i];
     string2[size+1] = `/0';
     printf ("The string printed backward is %s/n", string2);
    }
      用下面的命令编译它:
     
    gcc -o test test.c
      这个程序执行时显示如下结果:
    The string is hello there

    The string printed backward is
      输出的第一行是正确的, 但第二行打印出的东西并不是我们所期望的. 我们所设想的输出应该是:
    The string printed backward is ereht olleh
      由于某些原因, my_print2 函数没有正常工作. 让我们用 gdb 看看问题究竟出在哪儿, 先键入如下命令:
     
    gdb greeting

    --------------------------------------------------------------------------------
    注意: 记得在编译 greeting 程序时把调试选项打开. 
    --------------------------------------------------------------------------------

      如果你在输入命令时忘了把要调试的程序作为参数传给 gdb , 你可以在 gdb 提示符下用 file 命令来载入它:
     
    (gdb) file greeting
      这个命令将载入 greeting 可执行文件就象你在 gdb 命令行里装入它一样.
      这时你能用 gdb 的 run 命令来运行 greeting 了. 当它在 gdb 里被运行后结果大约会象这样:

    (gdb) run
    Starting program: /root/greeting
    The string is hello there
    The string printed backward is
    Program exited with code 041
      这个输出和在 gdb 外面运行的结果一样. 问题是, 为什么反序打印没有工作? 为了找出症结所在, 我们可以在 my_print2 函数的 for 语句后设一个断点, 具体的做法是在 gdb 提示符下键入 list 命令三次, 列出源代码:
    (gdb) list
    (gdb) list
    (gdb) list
    --------------------------------------------------------------------------------
    技巧: 在 gdb 提示符下按回车健将重复上一个命令. 
    --------------------------------------------------------------------------------
      第一次键入 list 命令的输出如下:
     
    1    #include 
    2
    3    main ()
    4    {
    5     char my_string[] = "hello there";
    6
    7     my_print (my_string);

    8     my_print2 (my_string);
    9    }
    10
      如果按下回车, gdb 将再执行一次 list 命令, 给出下列输出:
      11   my_print (char *string)
    12   {
    13    printf ("The string is %s/n", string);
    14   }
    15
    16   my_print2 (char *string)
    17   {
    18    char *string2;
    19    int size, i;

    20
      再按一次回车将列出 greeting 程序的剩余部分:
    21    size = strlen (string);

    22    string2 = (char *) malloc (size + 1);

    23    for (i = 0; i < size; i++)

    24     string2[size - i] = string[i];

    25    string2[size+1] = `/0';

    26    printf ("The string printed backward is %s/n", string2);

    27   }
      根据列出的源程序, 你能看到要设断点的地方在第24行, 在 gdb 命令行提示符下键入如下命令设置断点:
    (gdb) break 24
      gdb 将作出如下的响应:
    Breakpoint 1 at 0x139: file greeting.c, line 24

    (gdb)
     
      现在再键入 run 命令, 将产生如下的输出:
     
    Starting program: /root/greeting

    The string is hello there



    Breakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :24

    24 string2[size-i]=string[i]
      你能通过设置一个观察 string2[size - i] 变量的值的观察点来看出错误是怎样产生的, 做法是键入:
     
    (gdb) watch string2[size - i]
      gdb 将作出如下回应:
    Watchpoint 2: string2[size - i]
      现在可以用 next 命令来一步步的执行 for 循环了:
     
    (gdb) next
      经过第一次循环后, gdb 告诉我们 string2[size - i] 的值是 `h`. gdb 用如下的显示来告诉你这个信息:
     
    Watchpoint 2, string2[size - i]

    Old value = 0 `/000'

    New value = 104 `h'

    my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23

    23 for (i=0; i   这个值正是期望的. 后来的数次循环的结果都是正确的. 当 i=10 时, 表达式 string2[size - i] 的值等于 `e`, size - i 的值等于 1, 最后一个字符已经拷到新串里了.
      如果你再把循环执行下去, 你会看到已经没有值分配给 string2[0] 了, 而它是新串的第一个字符, 因为 malloc 函数在分配内存时把它们初始化为空(null)字符. 所以 string2 的第一个字符是空字符. 这解释了为什么在打印 string2 时没有任何输出了.

      现在找出了问题出在哪里, 修正这个错误是很容易的. 你得把代码里写入 string2 的第一个字符的的偏移量改为 size - 1 而不是 size. 这是因为 string2 的大小为 12, 但起始偏移量是 0, 串内的字符从偏移量 0 到 偏移量 10, 偏移量 11 为空字符保留.

      为了使代码正常工作有很多种修改办法. 一种是另设一个比串的实际大小小 1 的变量. 这是这种解决办法的代码:

    #include 



    main ()


    {

     char my_string[] = "hello there";



     my_print (my_string);

     my_print2 (my_string);

    }



    my_print (char *string)

    {

     printf ("The string is %s/n", string);

    }



    my_print2 (char *string)

    {

     char *string2;

     int size, size2, i;



     size = strlen (string);

     size2 = size -1;

     string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

      string2[size2 - i] = string[i];

     string2[size] = `/0';

     printf ("The string printed backward is %s/n", string2);

    }
    另外的 C 编程工具
      Slackware Linux 的发行版中还包括一些我们尚未提到的 C 开发工具. 本节将介绍这些工具和它们的典型用法.
    xxgdb
      xxgdb 是 gdb 的一个基于 X Window 系统的图形界面. xxgdb 包括了命令行版的 gdb 上的所有特性. xxgdb 使你能通过按按钮来执行常用的命令. 设置了断点的地方也用图形来显示.
     
      你能在一个 Xterm 窗口里键入下面的命令来运行它:
    xxgdb
      你能用 gdb 里任何有效的命令行选项来初始化 xxgdb . 此外 xxgdb 也有一些特有的命令行选项, 表 27.2 列出了这些选项.
     
    表 27.2. xxgdb 命令行选项.


    选 项 描 述
    db_name 指定所用调试器的名字, 缺省是 gdb.
    db_prompt 指定调试器提示符, 缺省为 gdb.
    gdbinit 指定初始化 gdb 的命令文件的文件名, 缺省为 .gdbinit. 
    nx 告诉 xxgdb 不执行 .gdbinit 文件.
    bigicon 使用大图标.

     
     
     
    calls
       你可以在 sunsite.unc.edu FTP 站点用下面的路径:
    /pub/Linux/devel/lang/c/calls.tar.Z

      来取得 calls , 一些旧版本的 Linux CD-ROM 发行版里也附带有. 因为它是一个有用的工具, 我们在这里也介绍一下. 如果你觉得有用的话, 从 BBS, FTP, 或另一张CD-ROM 上弄一个拷贝. calls 调用 GCC 的预处理器来处理给出的源程序文件, 然后输出这些文件的里的函数调用树图.
     


    --------------------------------------------------------------------------------
    注意: 在你的系统上安装 calls , 以超级用户身份登录后执行下面的步骤: 1. 解压和 untar 文件. 2. cd 进入 calls untar 后建立的子目录. 3. 把名叫 calls 的文件移动到 /usr/bin 目录. 4. 把名叫 calls.1 的文件移动到目录 /usr/man/man1 . 5. 删除 /tmp/calls 目录. 这些步骤将把 calls 程序和它的指南页安装载你的系统上. 
    --------------------------------------------------------------------------------

      当 calls 打印出调用跟踪结果时, 它在函数后面用中括号给出了函数所在文件的文件名:
    main [test.c]
      如果函数并不是向 calls 给出的文件里的, calls 不知道所调用的函数来自哪里, 则只显示函数的名字:
    printf
      calls 不对递归和静态函数输出. 递归函数显示成下面的样子:
    fact <<< recursive in factorial.c >>>
      静态函数象这样显示:
    total [static in calculate.c]
      作为一个例子, 假设用 calls 处理下面的程序:
     
    #include



    main ()

    {

    char my_string[] = "hello there";

    my_print (my_string);

    my_print2(my_string);

    }



    my_print (char *string)

    {

    printf ("The string is %s/n", string);

    }



    my_print2 (char *string)

    {

     char *string2;

     int size, size2, i;



     size = strlen (string);

     size2 = size -1;

     string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

      string2[size2 - i] = string[i];

     string2[size] = `/0';

     printf ("The string printed backward is %s/n", string2);

    }
      将产生如下的输出:
      1 main [test.c]

      2    my_print [test.c]

      3       printf

      4    my_print2 [test.c]

      5       strlen

      6       malloc

      7       printf
    calls 有很多命令行选项来设置不同的输出格式, 有关这些选项的更多信息请参考 calls 的指南页. 方法是在命令行上键入 calls -h .
     
     
    cproto
      cproto 读入 C 源程序文件并自动为每个函数产生原型申明. 用 cproto 可以在写程序时为你节省大量用来定义函数原型的时间.
      如果你让 cproto 处理下面的代码:
    #include 



    main ()

    {

     char my_string[] = "hello there";

     my_print (my_string);

     my_print2(my_string);

    }



    my_print (char *string)

    {

     printf ("The string is %s/n", *string);

    }



    my_print2 (char *string)

    {

     char *string2;

     int size, size2, i;



     size = strlen (string);

     size2 = size -1;

     string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

      string2[size2 - i] = string[i];

     string2[size] = `/0';

     printf ("The string printed backward is %s/n", string2);

    }
      你将得到下面的输出:
    /* test.c */

    int main(void);

    int my_print(char *string);

    int my_print2(char *string);
      这个输出可以重定向到一个定义函数原型的包含文件里.
    indent
      indent 实用程序是 Linux 里包含的另一个编程实用工具. 这个工具简单的说就为你的代码产生美观的缩进的格式. indent 也有很多选项来指定如何格式化你的源代码.这些选项的更多信息请看indent 的指南页, 在命令行上键入 indent -h .
     
      下面的例子是 indent 的缺省输出:

      运行 indent 以前的 C 代码:

    #include 



    main () {

       char my_string[] = "hello there";

     my_print (my_string);

       my_print2(my_string); }



    my_print (char *string)

    {

     printf  ("The string is %s/n", *string);

    }



    my_print2      (char *string) {

      char *string2;

       int size, size2, i;



       size = strlen (string);

       size2 = size -1;

       string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

          string2[size2 - i] = string[i];

       string2[size] = `/0';


       printf ("The string printed backward is %s/n", string2);

    }
      运行 indent 后的 C 代码:
    #include 



    main ()

    {

     char my_string[] = "hello there";

     my_print (my_string);

     my_print2 (my_string);

    }



    my_print (char *string)

    {

     printf ("The string is %s/n", *string);

    }



    my_print2 (char *string)

    {

     char *string2;

     int size, size2, i;



     size = strlen (string);

     size2 = size -1;

     string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

      string2[size2 - i] = string[i];

     string2[size] = `/0';

     printf ("The string printed backward is %s/n", string2);

    }
       indent 并不改变代码的实质内容, 而只是改变代码的外观. 使它变得更可读, 这永远是一件好事.
    gprof
      gprof 是安装在你的 Linux 系统的 /usr/bin 目录下的一个程序. 它使你能剖析你的程序从而知道程序的哪一个部分在执行时最费时间.
      gprof 将告诉你程序里每个函数被调用的次数和每个函数执行时所占时间的百分比. 你如果想提高你的程序性能的话这些信息非常有用.

      为了在你的程序上使用 gprof, 你必须在编译程序时加上 -pg 选项. 这将使程序在每次执行时产生一个叫 gmon.out 的文件. gprof 用这个文件产生剖析信息.

      在你运行了你的程序并产生了 gmon.out 文件后你能用下面的命令获得剖析信息:

    gprof
      参数 program_name 是产生 gmon.out 文件的程序的名字.

    --------------------------------------------------------------------------------
    技巧: gprof 产生的剖析数据很大, 如果你想检查这些数据的话最好把输出重定向到一个文件里. 
    --------------------------------------------------------------------------------

    f2c 和 p2c
      f2c 和 p2c 是两个源代码转换程序. f2c 把 FORTRAN 代码转换为 C 代码, p2c 把 Pascal 代码转换为 C 代码. 当你安装 GCC 时这两个程序都会被安装上去.
      如果你有一些用 FORTRAN 或 Pascal 写的代码要用 C 重写的话, f2c 和 p2c 对你非常有用. 这两个程序产生的 C 代码一般不用修改就直接能被 GCC 编译.

      如果要转换的 FORTRAN 或 Pascal 程序比较小的话可以直接使用 f2c 或 p2c 不用加任何选项. 如果要转换的程序比较庞大, 包含很多文件的话你可能要用到一些命令行选项.

      在一个 FORTRAN 程序上使用 f2c , 输入下面的命令:

    f2c my_fortranprog.f

    --------------------------------------------------------------------------------
    注意: f2c 要求被转换的程序的扩展名为 .f 或 a .F . 
    --------------------------------------------------------------------------------

      要把一个Pascal 程序装换为 C 程序, 输入下面的命令:
    p2c my_pascalprogram.pas
      这两个程序产生的 C 源代码的文件名都和原来的文件名相同, 但扩展名由 .f 或 .pas 变为 .c.
    <script src="http://localhost:82/PromoteIcon.aspx?id=130228" type="text/javascript"></script>
    <script type="text/javascript">document.write("pv.aspx?id=24");</script>

     

     
    展开全文
  • 原著:Rick McMullin前言Linux的发行版中包含了很多软件开发工具. 它们中的很多是用于 C 和 C++应用程序开发的. 本文介绍了在 Linux 下能用于 C 应用程序开发和调试的工具. 本文的主旨是介绍如何在 Linux 下使用 C ...
    原著:Rick McMullin

    前言
    Linux的发行版中包含了很多软件开发工具. 它们中的很多是用于 C 和 C++应用程序开发的. 本文介绍了在 Linux 下能用于 C 应用程序开发和调试的工具. 本文的主旨是介绍如何在 Linux 下使用 C 编译器和其他 C 编程工具, 而非 C 语言编程的教程. 在本文中你将学到以下知识:
     

    什么是 C
    GNU C 编译器
    用 gdb 来调试GCC应用程序
      你也能看到随 Linux 发行的其他有用的 C 编程工具. 这些工具包括源程序美化程序(pretty print programs), 附加的调试工具, 函数原型自动生成工具(automatic function prototypers).

    --------------------------------------------------------------------------------
    注意: 源程序美化程序(pretty print programs)自动帮你格式化源代码产生始终如一的缩进格式. 
    --------------------------------------------------------------------------------

    什么是 C?
      C 是一种在 UNIX 操作系统的早期就被广泛使用的通用编程语言. 它最早是由贝尔实验室的 Dennis Ritchie 为了 UNIX 的辅助开发而写的, 开始时 UNIX 是用汇编语言和一种叫 B 的语言编写的. 从那时候起, C 就成为世界上使用最广泛计算机语言.
     
      C 能在编程领域里得到如此广泛支持的原因有以下一些:
    它是一种非常通用的语言. 几乎你所能想到的任何一种计算机上都有至少一种能用的 C 编译器. 并且它的语法和函数库在不同的平台上都是统一的, 这个特性对开发者来说很有吸引力.
    用 C 写的程序执行速度很快.
    C 是所有版本的UNIX上的系统语言.
      C 在过去的二十年中有了很大的发展. 在80年代末期美国国家标准协会(American National Standards Institute)发布了一个被称为 ANSI C 的 C 语言标准.这更加保证了将来在不同平台上的 C 的一致性. 在80年代还出现了一种 C 的面向对象的扩展称为 C++. C++ 将在另一篇文章 "C++ 编程"中描述.
      Linux 上可用的 C 编译器是 GNU C 编译器, 它建立在自由软件基金会的编程许可证的基础上, 因此可以自由发布. 你能在 Linux 的发行光盘上找到它.
     
     
     

    GNU C 编译器
      随 Slackware Linux 发行的 GNU C 编译器(GCC)是一个全功能的 ANSI C 兼容编译器. 如果你熟悉其他操作系统或硬件平台上的一种 C 编译器, 你将能很快地掌握 GCC. 本节将介绍如何使用 GCC 和一些 GCC 编译器最常用的选项.
     
    使用 GCC
      通常后跟一些选项和文件名来使用 GCC 编译器. gcc 命令的基本用法如下:
    gcc [options] [filenames]
      命令行选项指定的操作将在命令行上每个给出的文件上执行. 下一小节将叙述一些你会最常用到的选项.
     
    GCC 选项
      GCC 有超过100个的编译选项可用. 这些选项中的许多你可能永远都不会用到, 但一些主要的选项将会频繁用到. 很多的 GCC 选项包括一个以上的字符. 因此你必须为每个选项指定各自的连字符, 并且就象大多数 Linux 命令一样你不能在一个单独的连字符后跟一组选项. 例如, 下面的两个命令是不同的:
    gcc -p -g test.c

    gcc -pg test.c
      第一条命令告诉 GCC 编译 test.c 时为 prof 命令建立剖析(profile)信息并且把调试信息加入到可执行的文件里. 第二条命令只告诉 GCC 为 gprof 命令建立剖析信息.
     
      当你不用任何选项编译一个程序时, GCC 将会建立(假定编译成功)一个名为 a.out 的可执行文件. 例如, 下面的命令将在当前目录下产生一个叫 a.out 的文件:
    gcc test.c
      你能用 -o 编译选项来为将产生的可执行文件指定一个文件名来代替 a.out. 例如, 将一个叫 count.c 的 C 程序编译为名叫 count 的可执行文件, 你将输入下面的命令:
    gcc -o count count.c

    --------------------------------------------------------------------------------
    注意: 当你使用 -o 选项时, -o 后面必须跟一个文件名. 
    --------------------------------------------------------------------------------

      GCC 同样有指定编译器处理多少的编译选项. -c 选项告诉 GCC 仅把源代码编译为目标代码而跳过汇编和连接的步骤. 这个选项使用的非常频繁因为它使得编译多个 C 程序时速度更快并且更易于管理. 缺省时 GCC 建立的目标代码文件有一个 .o 的扩展名.
      -S 编译选项告诉 GCC 在为 C 代码产生了汇编语言文件后停止编译. GCC 产生的汇编语言文件的缺省扩展名是 .s . -E 选项指示编译器仅对输入文件进行预处理. 当这个选项被使用时, 预处理器的输出被送到标准输出而不是储存在文件里.

    优 化 选 项
      当你用 GCC 编译 C 代码时, 它会试着用最少的时间完成编译并且使编译后的代码易于调试. 易于调试意味着编译后的代码与源代码有同样的执行次序, 编译后的代码没有经过优化. 有很多选项可用于告诉 GCC 在耗费更多编译时间和牺牲易调试性的基础上产生更小更快的可执行文件. 这些选项中最典型的是-O 和 -O2 选项.
      -O 选项告诉 GCC 对源代码进行基本优化. 这些优化在大多数情况下都会使程序执行的更快. -O2 选项告诉 GCC 产生尽可能小和尽可能快的代码. -O2 选项将使编译的速度比使用 -O 时慢. 但通常产生的代码执行速度会更快.

      除了 -O 和 -O2 优化选项外, 还有一些低级选项用于产生更快的代码. 这些选项非常的特殊, 而且最好只有当你完全理解这些选项将会对编译后的代码产生什么样的效果时再去使用. 这些选项的详细描述, 请参考 GCC 的指南页, 在命令行上键入 man gcc .

    调试和剖析选项
      GCC 支持数种调试和剖析选项. 在这些选项里你会最常用到的是 -g 和 -pg 选项.
      -g 选项告诉 GCC 产生能被 GNU 调试器使用的调试信息以便调试你的程序. GCC 提供了一个很多其他 C 编译器里没有的特性, 在 GCC 里你能使 -g 和 -O (产生优化代码)联用. 这一点非常有用因为你能在与最终产品尽可能相近的情况下调试你的代码. 在你同时使用这两个选项时你必须清楚你所写的某些代码已经在优化时被 GCC 作了改动. 关于调试 C 程序的更多信息请看下一节"用 gdb 调试 C 程序" .
      -pg 选项告诉 GCC 在你的程序里加入额外的代码, 执行时, 产生 gprof 用的剖析信息以显示你的程序的耗时情况. 关于 gprof 的更多信息请参考 "gprof" 一节.
     
    用 gdb 调试 GCC 程序
      Linux 包含了一个叫 gdb 的 GNU 调试程序. gdb 是一个用来调试 C 和 C++ 程序的强力调试器. 它使你能在程序运行时观察程序的内部结构和内存的使用情况. 以下是 gdb 所提供的一些功能:
    它使你能监视你程序中变量的值.
    它使你能设置断点以使程序在指定的代码行上停止执行.
    它使你能一行行的执行你的代码.

      在命令行上键入 gdb 并按回车键就可以运行 gdb 了, 如果一切正常的话, gdb 将被启动并且你将在屏幕上看到类似的内容:
    GDB is free software and you are welcome to distribute copies of it

    under certain conditions; type "show copying" to see the conditions.

    There is absolutely no warranty for GDB; type "show warranty" for details.

    GDB 4.14 (i486-slakware-linux), Copyright 1995 Free Software Foundation, Inc.

    (gdb)
      当你启动 gdb 后, 你能在命令行上指定很多的选项. 你也可以以下面的方式来运行 gdb :
    gdb
      当你用这种方式运行 gdb , 你能直接指定想要调试的程序. 这将告诉gdb 装入名为 fname 的可执行文件. 你也可以用 gdb 去检查一个因程序异常终止而产生的 core 文件, 或者与一个正在运行的程序相连. 你可以参考 gdb 指南页或在命令行上键入 gdb -h 得到一个有关这些选项的说明的简单列表.
     
    为调试编译代码(Compiling Code for Debugging)
      为了使 gdb 正常工作, 你必须使你的程序在编译时包含调试信息. 调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号. gdb 利用这些信息使源代码和机器码相关联.
      在编译时用 -g 选项打开调试选项.
     
    gdb 基本命令
       gdb 支持很多的命令使你能实现不同的功能. 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令, 表27.1列出了你在用 gdb 调试时会用到的一些命令. 想了解 gdb 的详细使用请参考 gdb 的指南页.
     

    表 27.1. 基本 gdb 命令.

    命  令 描 述
    file 装入想要调试的可执行文件.
    kill 终止正在调试的程序.
    list 列出产生执行文件的源代码的一部分.
    next 执行一行源代码但不进入函数内部.
    step 执行一行源代码而且进入函数内部.
    run 执行当前被调试的程序
    quit 终止 gdb
    watch 使你能监视一个变量的值而不管它何时被改变.
    break 在代码里设置断点, 这将使程序执行到这里时被挂起.
    make 使你能不退出 gdb 就可以重新产生可执行文件.
    shell 使你能不离开 gdb 就执行 UNIX shell 命令. 

     
     
       gdb 支持很多与 UNIX shell 程序一样的命令编辑特征. 你能象在 bash 或 tcsh里那样按 Tab 键让 gdb 帮你补齐一个唯一的命令, 如果不唯一的话 gdb 会列出所有匹配的命令. 你也能用光标键上下翻动历史命令.

    gdb 应用举例
      本节用一个实例教你一步步的用 gdb 调试程序. 被调试的程序相当的简单, 但它展示了 gdb 的典型应用.
     
      下面列出了将被调试的程序. 这个程序被称为 greeting , 它显示一个简单的问候, 再用反序将它列出.
    #include 

    main ()
    {
     char my_string[] = "hello there";

     my_print (my_string);
     my_print2 (my_string);
    }

    void my_print (char *string)
    {
     printf ("The string is %s/n", string);
    }

    void my_print2 (char *string)
    {
     char *string2;
     int size, i;

     size = strlen (string);
     string2 = (char *) malloc (size + 1);
     for (i = 0; i < size; i++)
      string2[size - i] = string[i];
     string2[size+1] = `/0';
     printf ("The string printed backward is %s/n", string2);
    }
      用下面的命令编译它:
     
    gcc -o test test.c
      这个程序执行时显示如下结果:
    The string is hello there

    The string printed backward is
      输出的第一行是正确的, 但第二行打印出的东西并不是我们所期望的. 我们所设想的输出应该是:
    The string printed backward is ereht olleh
      由于某些原因, my_print2 函数没有正常工作. 让我们用 gdb 看看问题究竟出在哪儿, 先键入如下命令:
     
    gdb greeting

    --------------------------------------------------------------------------------
    注意: 记得在编译 greeting 程序时把调试选项打开. 
    --------------------------------------------------------------------------------

      如果你在输入命令时忘了把要调试的程序作为参数传给 gdb , 你可以在 gdb 提示符下用 file 命令来载入它:
     
    (gdb) file greeting
      这个命令将载入 greeting 可执行文件就象你在 gdb 命令行里装入它一样.
      这时你能用 gdb 的 run 命令来运行 greeting 了. 当它在 gdb 里被运行后结果大约会象这样:

    (gdb) run
    Starting program: /root/greeting
    The string is hello there
    The string printed backward is
    Program exited with code 041
      这个输出和在 gdb 外面运行的结果一样. 问题是, 为什么反序打印没有工作? 为了找出症结所在, 我们可以在 my_print2 函数的 for 语句后设一个断点, 具体的做法是在 gdb 提示符下键入 list 命令三次, 列出源代码:
    (gdb) list
    (gdb) list
    (gdb) list
    --------------------------------------------------------------------------------
    技巧: 在 gdb 提示符下按回车健将重复上一个命令. 
    --------------------------------------------------------------------------------
      第一次键入 list 命令的输出如下:
     
    1    #include 
    2
    3    main ()
    4    {
    5     char my_string[] = "hello there";
    6
    7     my_print (my_string);

    8     my_print2 (my_string);
    9    }
    10
      如果按下回车, gdb 将再执行一次 list 命令, 给出下列输出:
      11   my_print (char *string)
    12   {
    13    printf ("The string is %s/n", string);
    14   }
    15
    16   my_print2 (char *string)
    17   {
    18    char *string2;
    19    int size, i;

    20
      再按一次回车将列出 greeting 程序的剩余部分:
    21    size = strlen (string);

    22    string2 = (char *) malloc (size + 1);

    23    for (i = 0; i < size; i++)

    24     string2[size - i] = string[i];

    25    string2[size+1] = `/0';

    26    printf ("The string printed backward is %s/n", string2);

    27   }
      根据列出的源程序, 你能看到要设断点的地方在第24行, 在 gdb 命令行提示符下键入如下命令设置断点:
    (gdb) break 24
      gdb 将作出如下的响应:
    Breakpoint 1 at 0x139: file greeting.c, line 24

    (gdb)
     
      现在再键入 run 命令, 将产生如下的输出:
     
    Starting program: /root/greeting

    The string is hello there



    Breakpoint 1, my_print2 (string = 0xbfffdc4 "hello there") at greeting.c :24

    24 string2[size-i]=string[i]
      你能通过设置一个观察 string2[size - i] 变量的值的观察点来看出错误是怎样产生的, 做法是键入:
     
    (gdb) watch string2[size - i]
      gdb 将作出如下回应:
    Watchpoint 2: string2[size - i]
      现在可以用 next 命令来一步步的执行 for 循环了:
     
    (gdb) next
      经过第一次循环后, gdb 告诉我们 string2[size - i] 的值是 `h`. gdb 用如下的显示来告诉你这个信息:
     
    Watchpoint 2, string2[size - i]

    Old value = 0 `/000'

    New value = 104 `h'

    my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23

    23 for (i=0; i   这个值正是期望的. 后来的数次循环的结果都是正确的. 当 i=10 时, 表达式 string2[size - i] 的值等于 `e`, size - i 的值等于 1, 最后一个字符已经拷到新串里了.
      如果你再把循环执行下去, 你会看到已经没有值分配给 string2[0] 了, 而它是新串的第一个字符, 因为 malloc 函数在分配内存时把它们初始化为空(null)字符. 所以 string2 的第一个字符是空字符. 这解释了为什么在打印 string2 时没有任何输出了.

      现在找出了问题出在哪里, 修正这个错误是很容易的. 你得把代码里写入 string2 的第一个字符的的偏移量改为 size - 1 而不是 size. 这是因为 string2 的大小为 12, 但起始偏移量是 0, 串内的字符从偏移量 0 到 偏移量 10, 偏移量 11 为空字符保留.

      为了使代码正常工作有很多种修改办法. 一种是另设一个比串的实际大小小 1 的变量. 这是这种解决办法的代码:

    #include 



    main ()


    {

     char my_string[] = "hello there";



     my_print (my_string);

     my_print2 (my_string);

    }



    my_print (char *string)

    {

     printf ("The string is %s/n", string);

    }



    my_print2 (char *string)

    {

     char *string2;

     int size, size2, i;



     size = strlen (string);

     size2 = size -1;

     string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

      string2[size2 - i] = string[i];

     string2[size] = `/0';

     printf ("The string printed backward is %s/n", string2);

    }
    另外的 C 编程工具
      Slackware Linux 的发行版中还包括一些我们尚未提到的 C 开发工具. 本节将介绍这些工具和它们的典型用法.
    xxgdb
      xxgdb 是 gdb 的一个基于 X Window 系统的图形界面. xxgdb 包括了命令行版的 gdb 上的所有特性. xxgdb 使你能通过按按钮来执行常用的命令. 设置了断点的地方也用图形来显示.
     
      你能在一个 Xterm 窗口里键入下面的命令来运行它:
    xxgdb
      你能用 gdb 里任何有效的命令行选项来初始化 xxgdb . 此外 xxgdb 也有一些特有的命令行选项, 表 27.2 列出了这些选项.
     
    表 27.2. xxgdb 命令行选项.


    选 项 描 述
    db_name 指定所用调试器的名字, 缺省是 gdb.
    db_prompt 指定调试器提示符, 缺省为 gdb.
    gdbinit 指定初始化 gdb 的命令文件的文件名, 缺省为 .gdbinit. 
    nx 告诉 xxgdb 不执行 .gdbinit 文件.
    bigicon 使用大图标.

     
     
     
    calls
       你可以在 sunsite.unc.edu FTP 站点用下面的路径:
    /pub/Linux/devel/lang/c/calls.tar.Z

      来取得 calls , 一些旧版本的 Linux CD-ROM 发行版里也附带有. 因为它是一个有用的工具, 我们在这里也介绍一下. 如果你觉得有用的话, 从 BBS, FTP, 或另一张CD-ROM 上弄一个拷贝. calls 调用 GCC 的预处理器来处理给出的源程序文件, 然后输出这些文件的里的函数调用树图.
     


    --------------------------------------------------------------------------------
    注意: 在你的系统上安装 calls , 以超级用户身份登录后执行下面的步骤: 1. 解压和 untar 文件. 2. cd 进入 calls untar 后建立的子目录. 3. 把名叫 calls 的文件移动到 /usr/bin 目录. 4. 把名叫 calls.1 的文件移动到目录 /usr/man/man1 . 5. 删除 /tmp/calls 目录. 这些步骤将把 calls 程序和它的指南页安装载你的系统上. 
    --------------------------------------------------------------------------------

      当 calls 打印出调用跟踪结果时, 它在函数后面用中括号给出了函数所在文件的文件名:
    main [test.c]
      如果函数并不是向 calls 给出的文件里的, calls 不知道所调用的函数来自哪里, 则只显示函数的名字:
    printf
      calls 不对递归和静态函数输出. 递归函数显示成下面的样子:
    fact <<< recursive in factorial.c >>>
      静态函数象这样显示:
    total [static in calculate.c]
      作为一个例子, 假设用 calls 处理下面的程序:
     
    #include



    main ()

    {

    char my_string[] = "hello there";

    my_print (my_string);

    my_print2(my_string);

    }



    my_print (char *string)

    {

    printf ("The string is %s/n", string);

    }



    my_print2 (char *string)

    {

     char *string2;

     int size, size2, i;



     size = strlen (string);

     size2 = size -1;

     string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

      string2[size2 - i] = string[i];

     string2[size] = `/0';

     printf ("The string printed backward is %s/n", string2);

    }
      将产生如下的输出:
      1 main [test.c]

      2    my_print [test.c]

      3       printf

      4    my_print2 [test.c]

      5       strlen

      6       malloc

      7       printf
    calls 有很多命令行选项来设置不同的输出格式, 有关这些选项的更多信息请参考 calls 的指南页. 方法是在命令行上键入 calls -h .
     
     
    cproto
      cproto 读入 C 源程序文件并自动为每个函数产生原型申明. 用 cproto 可以在写程序时为你节省大量用来定义函数原型的时间.
      如果你让 cproto 处理下面的代码:
    #include 



    main ()

    {

     char my_string[] = "hello there";

     my_print (my_string);

     my_print2(my_string);

    }



    my_print (char *string)

    {

     printf ("The string is %s/n", *string);

    }



    my_print2 (char *string)

    {

     char *string2;

     int size, size2, i;



     size = strlen (string);

     size2 = size -1;

     string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

      string2[size2 - i] = string[i];

     string2[size] = `/0';

     printf ("The string printed backward is %s/n", string2);

    }
      你将得到下面的输出:
    /* test.c */

    int main(void);

    int my_print(char *string);

    int my_print2(char *string);
      这个输出可以重定向到一个定义函数原型的包含文件里.
    indent
      indent 实用程序是 Linux 里包含的另一个编程实用工具. 这个工具简单的说就为你的代码产生美观的缩进的格式. indent 也有很多选项来指定如何格式化你的源代码.这些选项的更多信息请看indent 的指南页, 在命令行上键入 indent -h .
     
      下面的例子是 indent 的缺省输出:

      运行 indent 以前的 C 代码:

    #include 



    main () {

       char my_string[] = "hello there";

     my_print (my_string);

       my_print2(my_string); }



    my_print (char *string)

    {

     printf  ("The string is %s/n", *string);

    }



    my_print2      (char *string) {

      char *string2;

       int size, size2, i;



       size = strlen (string);

       size2 = size -1;

       string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

          string2[size2 - i] = string[i];

       string2[size] = `/0';


       printf ("The string printed backward is %s/n", string2);

    }
      运行 indent 后的 C 代码:
    #include 



    main ()

    {

     char my_string[] = "hello there";

     my_print (my_string);

     my_print2 (my_string);

    }



    my_print (char *string)

    {

     printf ("The string is %s/n", *string);

    }



    my_print2 (char *string)

    {

     char *string2;

     int size, size2, i;



     size = strlen (string);

     size2 = size -1;

     string2 = (char *) malloc (size + 1);

     for (i = 0; i < size; i++)

      string2[size2 - i] = string[i];

     string2[size] = `/0';

     printf ("The string printed backward is %s/n", string2);

    }
       indent 并不改变代码的实质内容, 而只是改变代码的外观. 使它变得更可读, 这永远是一件好事.
    gprof
      gprof 是安装在你的 Linux 系统的 /usr/bin 目录下的一个程序. 它使你能剖析你的程序从而知道程序的哪一个部分在执行时最费时间.
      gprof 将告诉你程序里每个函数被调用的次数和每个函数执行时所占时间的百分比. 你如果想提高你的程序性能的话这些信息非常有用.

      为了在你的程序上使用 gprof, 你必须在编译程序时加上 -pg 选项. 这将使程序在每次执行时产生一个叫 gmon.out 的文件. gprof 用这个文件产生剖析信息.

      在你运行了你的程序并产生了 gmon.out 文件后你能用下面的命令获得剖析信息:

    gprof
      参数 program_name 是产生 gmon.out 文件的程序的名字.

    --------------------------------------------------------------------------------
    技巧: gprof 产生的剖析数据很大, 如果你想检查这些数据的话最好把输出重定向到一个文件里. 
    --------------------------------------------------------------------------------

    f2c 和 p2c
      f2c 和 p2c 是两个源代码转换程序. f2c 把 FORTRAN 代码转换为 C 代码, p2c 把 Pascal 代码转换为 C 代码. 当你安装 GCC 时这两个程序都会被安装上去.
      如果你有一些用 FORTRAN 或 Pascal 写的代码要用 C 重写的话, f2c 和 p2c 对你非常有用. 这两个程序产生的 C 代码一般不用修改就直接能被 GCC 编译.

      如果要转换的 FORTRAN 或 Pascal 程序比较小的话可以直接使用 f2c 或 p2c 不用加任何选项. 如果要转换的程序比较庞大, 包含很多文件的话你可能要用到一些命令行选项.

      在一个 FORTRAN 程序上使用 f2c , 输入下面的命令:

    f2c my_fortranprog.f

    --------------------------------------------------------------------------------
    注意: f2c 要求被转换的程序的扩展名为 .f 或 a .F . 
    --------------------------------------------------------------------------------

      要把一个Pascal 程序装换为 C 程序, 输入下面的命令:
    p2c my_pascalprogram.pas
      这两个程序产生的 C 源代码的文件名都和原来的文件名相同, 但扩展名由 .f 或 .pas 变为 .c.
    展开全文
  • 二、用C语言编程解决实际问题 如果你没有深刻地理解C语言的语句的执行过程(或流程) 而我们编写的程序或软件是要根据要解决问题的实际需要控制程序的流程 只是很肤浅的语法知识 却不知道如何下手 三、C语言程序设计...
     二、用C语言编程解决实际问题 如果你没有深刻地理解C语言的语句的执行过程(或流程) 而我们编写的程序或软件是要根据要解决问题的实际需要控制程序的流程 只是很肤浅的语法知识 却不知道如何下手  三、C语言程序设计是一门实践性很强的课程 如果你不知道长方形的面积公式 还需要相关的专业知识 例如 即使C语言学得再好你也编不出求长方形的面积的程序来  出现问题原因清楚了 光听教练讲解相关的知识、规则、技巧 你从来没有骑过自行车 假如 大家都看过精彩自行车杂技表演 “纸上谈兵”式的光学不练是学不好C语言的 “三分编程七分调试” 那么如何学习呢  这个问题或许会有很多种答案  一年左右吧 只能在这里写了 造成变量未定义的错误 
    
    
    |||
    现在一般用VC++6.0版本  我学C也不是很久 你要好好加油哦 
    
    
    |||
    TURBOC2.0集成开发环境挺不错的 值得使用 软件的也很小 而且所看即所得 支持鼠标移动 
    
    
    |||
    TC or VC++
    
    
    |||
    winTC可视化的界面  请你看【C语言学习六步曲】 在程序开发的过程中 不要说上台表演、就是上路你恐怕都不行  第一步、验证性练习 在这一步要求按照教材上的程序实例进行原样输入 上机调试程序是一个不可缺少的重要环节 这里以如何上机调试C程序来说明C语言的学习方法 在这一步基本掌握C语言编程软件的使用方法(包括新建、打开、保存、关闭C程序 说明程序调试的工作量要比编程大得多  初学者最容易犯的错误是: 1、没有区分开教材上的数字1和字母l 运行一下程序是否正确 另一个易错点是将英文状态下的逗号  字母o和数字0的区别 熟练地输入、编辑C程序;初步记忆新学章节的知识点、养成良好的C语言编程风格) 
    
    
    |||
    初学的话 同是C++中的内容是很严谨的 再学C++打造一些优势 为了以后学完C 但也可以编译C 它本来是用来编译C++的 原因是 我一开始是用TC++3.0 只是因人而异的 刚开始用什么软件呢 直接百度里面搜 再用VC++ 6.0
    
    
    |||
    TURBOC2.0很好的
    
    
    |||
    TURBOC2.0 DOS下运行
    
    
    |||
    个人觉得初学者还是应该使用TC比较好
    
    
     用TC3.0吧等学到一定程度了 现在开始用它就会为以后学C++打下良好的习惯  
    
    
    |||
    visual C++ 6.0最好用英文版.
    
    
    |||
    visual C++ 6.0 中文版的  ^_^
    
    
    |||
    c-free 
    
    
    |||
    我不会到网上复制内容  
    
    
    |||
    个人觉得初学者还是应该使用TC比较好 一起学习 可以Q我 有什么不会的 以上都是一些简单建议 有不懂的查MSND或上网找答案 也要实践一下 那怕是最简单的编程题目 一定在实践中学 记住 到后来就可以做软件师了 再深入些就做一些项目 就开始做一些有难度的问题 有一定的基础 先学基础的 基础理论少不了 首先是 也只有在实践中才有高速提高 要在实践中发展理论 分号;括号()双引号""输入出入成中文状态下的逗号 很实用 或则其他网站也可以下 可以找到官方网站 等级考试和大学教学都是这个平台  3、修改C语言语法错误时要注意以下两点: (1)、由于C语言语法比较自由、灵活 分号;括号() 支持中文  2、C语言初学者易犯语法错误:使用未定义的变量、标示符(变量、常量、数组、函数等)不区分大小写、漏掉“;”、“{”与“}”、“(”与“)”不匹、控制语句(选择、分支、循环)的格式不正确、调用库函数却没有包含相应的头文件、调用未C声明的自定义函数、调用函数时实参与形参不匹配、数组的边界超界等 例如 双引号“”造成非法字符错误  (2)、一条语句错误可能会产生若干条错误信息只要修改了这条错误 如果在第10行没有发现错误 当提示第10行发生错误时 因此错误信息定位不是特别精确 特别提示:一般情况下 从第10行开始往前查找错误并修改之  第二步、照葫芦画瓢 在第一步输入的C程序的基础上进行试验性的修改 即 要一个一个地修改 如果还有很多错误 立即运行程序 修改后 所以调试程序时务必根据第一条错误信息进行修改 第一条错误信息最能反映错误的位置和类型 其他错误会随之消失 事实上这和第一步时同步进行的 分析结果变化的原因 运行一下程序看一看程序结果发生了什么变化 每修改一处错误要运行一次程序 记忆和理解是相辅相成的 实现“输入”加深知识的记忆 加深新学知识点的理解 点击查看C语言的编程风格 4.积极参加C、C++兴趣小组 “修改”加深对知识的理解  3.在上机练习时要养成良好的编程风格  2.按照《C语言程序设计入门学习六步曲》进行上机练习 现在正在学C语言的在校学生可以直接进入第2步学习 学c编程首先知道是理论与实践同样重要 
    1.先学习C语言的基础知识  5.及时总结自己的学习经验 养成和老师与同学交流习惯 相互促进 有时别人不经意的一句话可能使你茅塞顿开--“一句话点醒梦中人”  6.从网上或教材上找一个自己感兴趣的题目(选题时根据自己的能力 从而相互收益 软件有编程日记功能  7. 由于C语言灵活、强大 经常通宵达旦地玩游戏也乐而不疲就是这个道理)进行实战训练 同学们喜欢打游戏 即使再苦再累还是感觉C语言学习是一件快乐的事 如果有了成就感 培养自己的成就感 可先易后难 养成写C语言日记的习惯 如果这些知识你学不好 但一定要熟练掌握C语言的流程控制语句、数组、函数、指针等基础知识的应用 实际上是没有意义的) 不要在细枝末节上浪费精力(比如++、--用于表达式的计算 因此在学习C语言的过程中 初学者要全面地掌握它是不可能的 提高自己的C语言综合应用能力  C语言程序设计入门学习六步曲 笔者在从事教学的过程中 要后续学习好C++、可视化的程序设计Visual C++或C++Builder就像空中楼阁 为学习面向对象程序设计打下坚实的基础 发生这种现象的原因有三个: 一、所谓的看懂听明白 可是到自己动手做编程时 书上的例题也能看明白 上课我也能听懂 听到同学抱怨最多的一句话是:老师 是不现实的 例如 所需要的不仅仅是C语言的编程知识 你怎么会编写程序解决这些实际问题呢 
    展开全文
  • ------------------WindowsPhones7手机开发、.Net培训...设计目的:本软件为学习作业,学习C#WinForm应用开发,及 SQL Serer以实战增强学习理解. 开发语言:C# + Ms SQL Server 软件要求:用一个TreeView控件显示出C...
  • Tensorflow的初级入门

    2019-03-07 18:28:45
    Tensorflow是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。在具体编程时有点类似于声明式编程。先介绍几个最基础的概念。 数据流图:tensorflow中使用图来计算任务,就是说如果想完成一个伟大的...
  • C++的初级入门

    2011-05-23 22:19:00
    C++的初级入门 其实C++对这个在21世纪的青少年朋友们来说是很有用的./ 它是我们学很多东西的基础 所以我们要学懂会用基础的 一、 C++的开发工具C++就是一种计算机编程语言。有很多的软件都可以开发C++的。比如...
  • 不好意思,本人发的"北大青鸟ACCP 6.0 软件开发初级程序员:c#语言和数据库技术基础"和 "北大青鸟ACCP 6.0 软件开发初级程序员:使用C#语言开发数据库应用系统" 文件搞反了,如果一起下载不影响的。呵呵 ACCP...
  • 很多初级程序员都很着急想做一个完整的大系统,实际脑袋里面一团浆糊,其实想有一...2,开发环境搭建教程,用什么工具能支持这个编程语言去开发一个软件。 3,系统案例,去看一个系统如何实现的,一般为了提高开发效率
  • Scala基础入门教程,解析Scala集合操作,通过本课程的学习,能够应用Scala进行Spark应用程序开发、掌握Spark的基本运行原理及编程模型,能够熟悉运用Spark SQL进行大数据仓库的开发,掌握Spark流式计算、Spark机器...
  • Spark初级入门(4):Scala 类和对象

    千人学习 2015-12-16 17:27:47
    Scala入门教程,类和对象知识点精讲。能够应用Scala进行Spark应用程序开发、掌握Spark的基本运行原理及编程模型,能够熟悉运用Spark SQL进行大数据仓库的开发,掌握Spark流式计算、Spark机器学习及图计算的原理。 ...
  • 1 何为编程语言 编程语言是一种特殊的语言,是计算机和人...简单来说,编程语言是用来编写软件的,帮助人类改造世界。 2 何为软件 1) 系统软件 直接和硬件交互软件,例如window,linux... 2)应用软件
  • 目录├─其他│├─第二次课上课说明.avi├─初级源代码│├─异常.pptx│├─第十章-网络编程.doc│├─第四章-异常.docx│├─第四章.zip,├─初级项目│├─Flyer队(netmeeting).avi:│├─画图软件.avi│├─...
  • Microsoft.Net(以下简称.Net)框架是微软提出的新一代Web软件开发模型,C#语言是.Net框架中新一代的开发工具。C#语言是一种现代的、面向对象的语言,它简化了C++语言在类、命名空间、方法重载和异常处理等方面的操作...
  • Linux平台C及C++软件开发入门 适合linux 平台下 C C++ 初级编程
  • 郁金香VC++外挂编程全集初级篇1.2.1

    热门讨论 2010-07-05 07:00:59
    1 入门篇.以《QQ连连看为例》 1.1、一个最简单的外挂 1.1.1、游戏窗口数据分析(SPY++) a、取得窗口相对坐标 b、读出游戏窗口信息GetWindowRect c、移动鼠标指针SetCursorPos 1.1.2 用VC++写个最简单的外挂...
  • 甚至在日常生活中编程也成为一种需要,在大家进入大学之前或者是在大学非工科专业不接触编程的时候,平常自学一点程序设计对以后肯定会有一定帮助,但是初级指导之类的东西还真是不好找,所以我就说一下C语言怎么...
  • Scala基础入门教程,解析Scala集合操作,通过本课程的学习,能够应用Scala进行Spark应用程序开发、掌握Spark的基本运行原理及编程模型,能够熟悉运用Spark SQL进行大数据仓库的开发,掌握Spark流式计算、Spark机器...
  • 我相信每一个刚入门软件测试的萌新都会琢磨一个问题:软件测试人员,要不要懂代码?其实这个问题同样困扰着已经在测试行业闯荡了几年的测试小司机们。 那今天,我就来给大家分析一下: 在软件测试初级...
  • 不好意思,本人发的"北大青鸟ACCP 6.0 软件开发初级程序员:c#语言和数据库技术基础"和 "北大青鸟ACCP 6.0 软件开发初级程序员:使用C#语言开发数据库应用系统" 文件搞反了,如果一起下载不影响的。呵呵 ACCP...
  • Python从入门到精通视频教程初级共19天课本套教程针对0基础的同学就python语言基础语法的各个点逐步讲解,由浅入深,通俗易懂,层层深入,从搭建环境到判断语句,再到基础的数据类型,之后对函数进行学习掌握,熟悉...
  •  高强训练编程能力——1年半完成52000行代码编写  ——相当于1名程序员2年代码编写量  项目驱动案例贯穿——提供CRM、ERP中型项目实战训练  ——演练26项全真项目案例,贯穿8大行业  打造COT六脉神剑 ——...
  • 数学建模常用软件初级入门,以及与数学建模相结合的应用,主要是数学建模编程方面相关知识的学习。 包括Matlab,Mathematica,Lingo,以及一些常用程序设计。
  • 1、一定要把基本的数据结构,经典的算法,Unix编程,程序编译链接及计算机原理等基础知识扎牢,这些会长远影响你的职业发展。 2、 推荐从C语言入门,不单是因为很多操作系统、网络协议栈开源代码由C/C++实现,更多是...
  • Eclipse 平台入门

    2005-08-24 12:19:00
    使用 Eclipse 插件来编辑、编译和调试应用程序级别: 初级David Gallardo软件顾问2004 年 1 月 10 日本文为您提供关于 Eclipse 平台的概述,包括其起源和体系结构。本文首先简要讨论 Eclipse 的开放源代码性质及其对...
  • 初级测试工程师: 刚入门的拥有计算机科学学位的个人或具有一些手工测试经验的个人。开发测试脚本并开始熟悉测试生存周期和测试技术。工作通常都是按照软件测试方案和流程对产品进行功能测验,检察产品是否有缺陷。...
  • 微信公众号【程序员江湖】作者黄小斜,斜杠青年,某985硕士,阿里研发工程师,于2018 年秋招拿到 BAT 头条、网易、滴滴等 8 个大厂 offer个人擅长领域 :自学编程、技术校园招聘、软件工程考研(关注公众号后回复”...
  • 学习Java编程需要有一个系统的知识库,本文把初级入门Java软件开发编程学习做出了一个汇总目录,希望能帮助到各位学习的朋友!

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 178
精华内容 71
关键字:

初级编程入门软件