




已阅读5页,还剩13页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
在性能优化方面永远注意80-20原则,即20%的程序消耗了80%的运行时间,因而我们要改进效率,最主要是考虑改进那20%的代码。不要优化程序中开销不大的那80%,这是劳而无功的。第一招:以空间换时间计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决问题的第1招-以空间换时间。比如说字符串的赋值:方法A:通常的办法#define LEN 32char string1 LEN;memset (string1,0,LEN);strcpy (string1,This is a example!);方法B:const char string2LEN =This is a example!;char * cp;cp = string2 ;使用的时候可以直接用指针来操作。从上面的例子可以看出,A和B的效率是不能比的。在同样的存储空间下,B直接使用指针就可以操作了,而A需要调用两个字符函数才能完成。B的缺点在于灵活性没有A好。在需要频繁更改一个字符串内容的时候,A具有更好的灵活性;如果采用方法B,则需要预存许多字符串,虽然占用了大量的内存,但是获得了程序执行的高效率。如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。第二招: 使用宏而不是函数。 这也是第一招的变招。函数和宏的区别就在于,宏占用了大量的空间,而函数占用了时间。大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选 项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,CPU也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一 些CPU时间。 而宏不存在这个问题。宏仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏的时候,该现象尤其突出。举例如下:方法C:#define bwMCDR2_ADDRESS 4#define bsMCDR2_ADDRESS 17int BIT_MASK(int _bf)return (1U (bw # _bf) - 1) (bs # _bf);void SET_BITS(int _dst,int _bf, int _val)_dst = (_dst) & (BIT_MASK(_bf) |(_val) (bs # _bf)& (BIT_MASK(_bf)SET_BITS(MCDR2, MCDR2_ADDRESS,ReGISterNumber);方法D:#define bwMCDR2_ADDRESS 4#define bsMCDR2_ADDRESS 17#define bmMCDR2_ADDRESS BIT_MASK(MCDR2_ADDRESS)#define BIT_MASK(_bf)(1U (bw # _bf) - 1) (bs # _bf)#define SET_BITS(_dst, _bf, _val)(_dst) = (_dst) & (BIT_MASK(_bf)| (_val) (bs # _bf)& (BIT_MASK(_bf)SET_BITS(MCDR2, MCDR2_ADDRESS,RegisterNumber);D方法是我看到的最好的置位操作函数,是ARM公司源码的一部分,在短短的三行内实现了很多功能,几乎涵盖了所有的位操作功能。C方法是其变体,其中滋味还需大家仔细体会。三招:数学方法解决问题现在我们演绎高效C语言编写的第二招-采用数学方法来解决问题。数学是计算机之母,没有数学的依据和基础,就没有计算机的发展,所以在编写程序的时候,采用一些数学方法会对程序的执行效率有数量级的提高。举例如下,求 1100的和。方法E:int I , j;for (I = 1 ;I3;J = 456 - (456 4 4);在字面上好像H比G麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法G调用了基本的取模函数和除法函数,既有函数调用,还有很多汇编代码和寄存器参与运算;而方法H则仅仅是几句相关的汇编,代码更简洁,效率更高。当然,由于编译器的不同,可能效率的差距不大,但是,以我目前遇到的MS C ,ARM C 来看,效率的差距还是不小。 对于以2的指数次方为*、/或%因子的数学运算,转化为移位运算通常可以提高算法效率。因为乘除运算指令周期通常比移位运算大。C语言位运算除了可以提高运算效率外,在嵌入式系统的编程中,它的另一个最典型的应用,而且十分广泛地正在被使用着的是位间的与(&)、或(|)、非()操作,这跟嵌入式系统的编程特点有很大关系。我们通常要对硬件寄存器进行位设置,譬如,我们通过将AM186ER型80186处理器的中断屏蔽控制寄存器的第低6位设置为0(开中断2),最通用的做法是:#define INT_I2_MASK 0x0040wTemp = inword(INT_MASK);outword(INT_MASK, wTemp &INT_I2_MASK);而将该位设置为1的做法是:#define INT_I2_MASK 0x0040wTemp = inword(INT_MASK);outword(INT_MASK, wTemp | INT_I2_MASK);判断该位是否为1的做法是:#define INT_I2_MASK 0x0040wTemp = inword(INT_MASK);if(wTemp & INT_I2_MASK) /* 该位为1 */运用这招需要注意的是,因为CPU的不同而产生的问题。比如说,在PC上用这招编写的程序,并在PC上调试通过,在移植到一个16位机平台上的时候,可能会产生代码隐患。所以只有在一定技术进阶的基础下才可以使用这招。第五招:汇编嵌入 在熟悉汇编语言的人眼里,C语言编写的程序都是垃圾。这种说法虽然偏激了一些,但是却有它的道理。汇编语言是效率最高的计算机语言,但是,不可能靠着它来写一个操作系统吧?所以,为了获得程序的高效率,我们只好采用变通的方法-嵌入汇编,混合编程。嵌入式C程序中主要使用在线汇编,即在C程序中直接插入_asm 内嵌汇编语句。举例如下,将数组一赋值给数组二,要求每一字节都相符。char string11024,string21024;方法Iint I;for (I =0 ;I1024;I+)*(string2 + I) = *(string1 + I)方法J#ifdef _PC_int I;for (I =0 ;I1024;I+)*(string2 + I) = *(string1 + I);#else#ifdef _ARM_asmMOV R0,string1MOV R1,string2MOV R2,#0loop:LDMIA R0!, R3-R11STMIA R1!, R3-R11ADD R2,R2,#8CMP R2, #400BNE loop#endif再举个例子:/* 把两个输入参数的值相加,结果存放到另外一个全局变量中 */int result;void Add(long a, long *b)_asmMOV AX, aMOV BX, bADD AX, BXMOV result, AX方法I是最常见的方法,使用了1024次循环;方法J则根据平台不同做了区分,在ARM平台下,用嵌入汇编仅用128次循环就完成了同样的操作。这里有朋友会说,为什么不用标准的内存拷贝函数呢?这是因为在源数据里可能含有数据为0的字节,这样的话,标准库函数会提前结束而不会完成我们要求的操作。这个例程典型应用于LCD数据的拷贝过程。根据不同的CPU,熟练使用相应的嵌入汇编,可以大大提高程序执行的效率。虽然是必杀技,但是如果轻易使用会付出惨重的代价。这是因为,使用了嵌入汇编,便限制了程序的可移植性,使程序在不同平台移植的过程中,卧虎藏龙,险象环生!同时该招数也与现代软件工程的思想相违背,只有在迫不得已的情况下才可以采用。第六招, 使用寄存器变量 当对一个变量频繁被读写时,需要反复访问内存,从而花费大量的存取时间。为此,C语言提供了一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,从而提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量,而循环计数是应用寄存器变量的最好候选者。(1) 只有局部自动变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式,凡需要采用静态存储方式的量都不能定义为寄存器变量,包括:模块间全局变量、模块内全局变量、局部static变量;(2) register是一个建议型关键字,意指程序建议该变量放在寄存器中,但最终该变量可能因为条件不满足并未成为寄存器变量,而是被放在了存储器中,但编译器中并不报错(在C+语言中有另一个建议型关键字:inline)。下面是一个采用寄存器变量的例子:/* 求1+2+3+.+n的值 */WORD Addition(BYTE n)register i,s=0;for(i=1;i外部同步RAM外部异步RAMFLASH/ROM对于程序代码,已经被烧录在FLASH或ROM中,我们可以让CPU直接从其中读取代码执行,但通常这不是一个好办法,我们最好在系统启动后将FLASH或ROM中的目标代码拷贝入RAM中后再执行以提高取指令速度;对于UART等设备,其内部有一定容量的接收BUFFER,我们应尽量在BUFFER被占满后再向CPU提出中断。例如计算机终端在向目标机通过RS-232传递数据时,不宜设置UART只接收到一个BYTE就向CPU提中断,从而无谓浪费中断处理时间;如果对某设备能采取DMA方式读取,就采用DMA读取,DMA读取方式在读取目标中包含的存储信息较大时效率较高,其数据传输的基本单位是块,而所传输的数据是从设备直接送入内存的(或者相反)。DMA方式较之中断驱动方式,减少了CPU 对外设的干预,进一步提高了CPU与外设的并行操作程度。用C语言编写Windows服务程序的五个步骤Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。为了学习这种控制台应用程序的基础知识,C(不是C+)是最佳选择。本文将建立并实现一个简单的服务程序,其功能是查询系统中可用物理内存数量,然后将结果写入一个文本文件。最后,你可以用所学知识编写自己的 Windows 服务。当初我写第一个 NT 服务时,我到 MSDN 上找例子。在那里我找到了一篇 Nigel Thompson 写的文章:“Creating a Simple Win32 Service in C+”,这篇文章附带一个 C+ 例子。虽然这篇文章很好地解释了服务的开发过程,但是,我仍然感觉缺少我需要的重要信息。我想理解通过什么框架,调用什么函数,以及何时调用,但 C+ 在这方面没有让我轻松多少。面向对象的方法固然方便,但由于用类对底层 Win32 函数调用进行了封装,它不利于学习服务程序的基本知识。这就是为什么我觉得 C 更加适合于编写初级服务程序或者实现简单后台任务的服务。在你对服务程序有了充分透彻的理解之后,用 C+ 编写才能游刃有余。当我离开原来的工作岗位,不得不向另一个人转移我的知识的时候,利用我用 C 所写的例子就非常容易解释 NT 服务之所以然。服务是一个运行在后台并实现勿需用户交互的任务的控制台程序。Windows NT/2000/XP 操作系统提供为服务程序提供专门的支持。人们可以用服务控制面板来配置安装好的服务程序,也就是 Windows 2000/XP 控制面板|管理工具中的“服务”(或在“开始”|“运行”对话框中输入 services.msc /s译者注)。可以将服务配置成操作系统启动时自动启动,这样你就不必每次再重启系统后还要手动启动服务。本文将首先解释如何创建一个定期查询可用物理内存并将结果写入某个文本文件的服务。然后指导你完成生成,安装和实现服务的整个过程。第一步:主函数和全局定义首先,包含所需的头文件。例子要调用 Win32 函数(windows.h)和磁盘文件写入(stdio.h):#include#include接着,定义两个常量:#define SLEEP_TIME 5000#define LOGFILE C:MyServicesmemstatus.txtSLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。在第二步中编写服务工作循环的时候要使用该常量。LOGFILE 定义日志文件的路径,你将会用 WriteToLog 函数将内存查询的结果输出到该文件,WriteToLog 函数定义如下:int WriteToLog(char* str)FILE* log;log = fopen(LOGFILE, a+);if (log = NULL)return -1;fprintf(log, %sn, str);fclose(log);return 0;声明几个全局变量,以便在程序的多个函数之间共享它们值。此外,做一个函数的前向定义:SERVICE_STATUS ServiceStatus;SERVICE_STATUS_HANDLE hStatus;void ServiceMain(int argc, char* argv);void ControlHandler(DWORD request);int InitService();现在,准备工作已经就绪,你可以开始编码了。服务程序控制台程序的一个子集。因此,开始你可以定义一个 main 函数,它是程序的入口点。对于服务程序来说,main 的代码令人惊讶地简短,因为它只创建分派表并启动控制分派机。void main()SERVICE_TABLE_ENTRY ServiceTable2;ServiceTable0.lpServiceName = MemoryStatus;ServiceTable0.lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;ServiceTable1.lpServiceName = NULL;ServiceTable1.lpServiceProc = NULL;/ 启动服务的控制分派机线程StartServiceCtrlDispatcher(ServiceTable);一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中(为此该程序定义了一个 ServiceTable 结构数组)。这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。它有两个域:lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;lpServiceProc: 指向服务主函数的指针(服务入口点);分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。服务控制管理器(SCM:Services Control Manager)是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。注意:如果 StartServiceCtrlDispatcher 函数30秒没有被调用,便会报错,为了避免这种情况,我们必须在 ServiceMain 函数中(参见本文例子)或在非主函数的单独线程中初始化服务分派表。本文所描述的服务不需要防范这样的情况。分派表中所有的服务执行完之后(例如,用户通过“服务”控制面板程序停止它们),或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。第二步:ServiceMain 函数Listing 1 展示了 ServiceMain 的代码。该函数是服务的入口点。它运行在一个单独的线程当中,这个线程是由控制分派器创建的。ServiceMain 应该尽可能早早为服务注册控制处理器。这要通过调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数传递给此函数:服务名和指向 ControlHandlerfunction 的指针。它指示控制分派器调用 ControlHandler 函数处理 SCM 控制请求。注册完控制处理器之后,获得状态句柄(hStatus)。通过调用 SetServiceStatus 函数,用 hStatus 向 SCM 报告服务的状态。Listing 1 展示了如何指定服务特征和其当前状态来初始化 ServiceStatus 结构,ServiceStatus 结构的每个域都有其用途: dwServiceType:指示服务类型,创建 Win32 服务。赋值 SERVICE_WIN32; dwCurrentState:指定服务的当前状态。因为服务的初始化在这里没有完成,所以这里的状态为 SERVICE_START_PENDING; dwControlsAccepted:这个域通知 SCM 服务接受哪个域。本文例子是允许 STOP 和 SHUTDOWN 请求。处理控制请求将在第三步讨论; dwWin32ExitCode 和 dwServiceSpecificExitCode:这两个域在你终止服务并报告退出细节时很有用。初始化服务时并不退出,因此,它们的值为 0; dwCheckPoint 和 dwWaitHint:这两个域表示初始化某个服务进程时要30秒以上。本文例子服务的初始化过程很短,所以这两个域的值都为 0。 调用 SetServiceStatus 函数向 SCM 报告服务的状态时。要提供 hStatus 句柄和 ServiceStatus 结构。注意 ServiceStatus 一个全局变量,所以你可以跨多个函数使用它。ServiceMain 函数中,你给结构的几个域赋值,它们在服务运行的整个过程中都保持不变,比如:dwServiceType。在报告了服务状态之后,你可以调用 InitService 函数来完成初始化。这个函数只是添加一个说明性字符串到日志文件。如下面代码所示:/ 服务初始化int InitService() int result;result = WriteToLog(Monitoring started.);return(result); 在 ServiceMain 中,检查 InitService 函数的返回值。如果初始化有错(因为有可能写日志文件失败),则将服务状态置为终止并退出 ServiceMain:error = InitService(); if (error) / 初始化失败,终止服务ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = -1; SetServiceStatus(hStatus, &ServiceStatus); / 退出 ServiceMainreturn; 如果初始化成功,则向 SCM 报告状态:/ 向 SCM 报告运行状态 ServiceStatus.dwCurrentState = SERVICE_RUNNING; SetServiceStatus (hStatus, &ServiceStatus);接着,启动工作循环。每五秒钟查询一个可用物理内存并将结果写入日志文件。如 Listing 1 所示,循环一直到服务的状态为 SERVICE_RUNNING 或日志文件写入出错为止。状态可能在 ControlHandler 函数响应 SCM 控制请求时修改。第三步:处理控制请求在第二步中,你用 ServiceMain 函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。每次你调用 SetServiceStatus 函数的时候,必须指定服务接收 STOP 和 SHUTDOWN 请求。Listing 2 示范了如何在 ControlHandler 函数中处理它们。STOP 请求是 SCM 终止服务的时候发送的。例如,如果用户在“服务”控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。两种情况的处理方式相同:写日志文件,监视停止; 向 SCM 报告 SERVICE_STOPPED 状态; 由于 ServiceStatus 结构对于整个程序而言为全局量,ServiceStatus 中的工作循环在当前状态改变或服务终止后停止。其它的控制请求如:PAUSE 和 CONTINUE 在本文的例子没有处理。控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus。第四步:安装和配置服务程序编好了,将之编译成 exe 文件。本文例子创建的文件叫 MemoryStatus.exe,将它拷贝到 C:MyServices 文件夹。为了在机器上安装这个服务,需要用 SC.EXE 可执行文件,它是 Win32 Platform SDK 中附带的一个工具。(译者注:Visaul Studio .NET 2003 IDE 环境中也有这个工具,具体存放位置在:C:Program FilesMicrosoft Visual Studio .NET 2003Common7ToolsBinwinnt)。使用这个实用工具可以安装和移除服务。其它控制操作将通过服务控制面板来完成。以下是用命令行安装 MemoryStatus 服务的方法:sc create MemoryStatus binpath= c:MyServicesMemoryStatus.exe发出此创建命令。指定服务名和二进制文件的路径(注意 binpath= 和路径之间的那个空格)。安装成功后,便可以用服务控制面板来控制这个服务。用控制面板的工具栏启动和终止这个服务。 MemoryStatus 的启动类型是手动,也就是说根据需要来启动这个服务。右键单击该服务,然后选择上下文菜单中的“属性”菜单项,此时显示该服务的属性窗口。在这里可以修改启动类型以及其它设置。你还可以从“常规”标签中启动/停止服务。以下是从系统中移除服务的方法:sc delete MemoryStatus指定 “delete” 选项和服务名。此服务将被标记为删除,下次西通重启后,该服务将被完全移除。第五步:测试服务从服务控制面板启动 MemoryStatus 服务。如果初始化不出错,表示启动成功。过一会儿将服务停止。检查一下 C:MyServices 文件夹中 memstatus.txt 文件的服务输出。在我的机器上输出是这样的:Monitoring started.273469440273379328273133568273084416Monitoring stopped.为了测试 MemoryStatus 服务在出错情况下的行为,可以将 memstatus.txt 文件设置成只读。这样一来,服务应该无法启动。去掉只读属性,启动服务,在将文件设成只读。服务将停止执行,因为此时日志文件写入失败。如果你更新服务控制面板的内容,会发现服务状态是已经停止。C语言中可变参数的用法我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,它的定义是这样的:int printf( const char* format, .);它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的,例如我们可以有以下不同的调用方法:printf(%d,i);printf(%s,s);printf(the number is %d ,string is:%s, i, s);究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现的呢?本文就这个问题进行一些探讨,希望能对大家有些帮助.会C+的网友知道这些问题在C+里不存在,因为C+具有多态性.但C+是C的一个超集,以下的技术也可以用于C+的程序中.限于本人的水平,文中如果有不当之处,请大家指正.(一)写一个简单的可变参数的C函数下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的C函数要在程序中用到以下这些宏:void va_start( va_list arg_ptr, prev_param );type va_arg( va_list arg_ptr, type );void va_end( va_list arg_ptr );va在这里是variable-argument(可变参数)的意思.这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.void simple_va_fun(int i, .)va_list arg_ptr;int j=0;va_start(arg_ptr, i);j=va_arg(arg_ptr, int);va_end(arg_ptr);printf(%d %dn, i, j);return;我们可以在我们的头文件中这样声明我们的函数:extern void simple_va_fun(int i, .);我们在程序中可以这样调用:simple_va_fun(100);simple_va_fun(100,200);从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型.4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.如果我们用下面三种方法调用的话,都是合法的,但结果却不一样:1)simple_va_fun(100);结果是:100 -123456789(会变的值)2)simple_va_fun(100,200);结果是:100 2003)simple_va_fun(100,200,300);结果是:100 200我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果正确,但和我们函数最初的设计有冲突.下面一节我们探讨出现这些结果的原因和可变参数在编译器中是如何处理的.(二)可变参数在编译器中的处理我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面以VC+中stdarg.h里x86平台的宏定义摘录如下(号表示折行):typedef char * va_list;#define _INTSIZEOF(n)(sizeof(n)+sizeof(int)-1)&(sizeof(int) - 1) )#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )#define va_arg(ap,t)( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) )#define va_end(ap) ( ap = (va_list)0 )定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址,如图:高地址|-|函数返回地址 |-|. |-|第n个参数(第一个可变参数) |-|-va_start后ap指向|第n-1个参数(最后一个固定参数)|低地址|-|- &v图( 1 )然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我们看一下va_arg取int型的返回值:j= ( *(int*)(ap += _INTSIZEOF(int)-_INTSIZEOF(int) );首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j.高地址|-|函数返回地址 |-|. |-|-va_arg后ap指向|第n个参数(第一个可变参数) |-|-va_start后ap指向|第n-1个参数(最后一个固定参数)|低地址|-|- &v链表的C语言实现之动态内存分配一、为什么用动态内存分配 但我们未学习链表的时候,如果要存储数量比较多的同类型或同结构的数据的时候,总是使用一个数组。比如说我们要存储一个班级学生的某科分数,总是定义一个float型(存在0.5分)数组:float score30; 但是,在使用数组的时候,总有一个问题困扰着我们:数组应该有多大?在很多的情况下,你并不能确定要使用多大的数组,比如上例,你可能并不知道该班级的学生的人数,那么你就要把数组定义得足够大。这样,你的程序在运行时就申请了固定大小的你认为足够大的内存空间。即使你知道该班级的学生数,但是如果因为某种特殊原因人数有增加或者减少,你又必须重新去修改程序,扩大数组的存储范围。这种分配固定大小的内存分配方法称之为静态内存分配。但是这种内存分配的方法存在比较严重的缺陷,特别是处理某些问题时:在大多数情况下会浪费大量的内存
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 公主交换礼物活动方案
- 2025至2030年中国伸缩洗衣机排水管行业投资前景及策略咨询报告
- 2025至2030年中国人棉平纹布行业投资前景及策略咨询报告
- 2025至2030年中国乌木提琴配件行业投资前景及策略咨询报告
- 2025至2030年中国PVC毛毛袋行业投资前景及策略咨询报告
- 浙江国企招聘2025台州临海市市属国有企业招聘69人笔试参考题库附带答案详解
- 公司业绩比拼策划方案
- 公司会议室征名活动方案
- 公司包饺子年会活动方案
- 公司团体拓展活动方案
- 新公司法试题及答案
- 中医专科护士进修汇报
- 绩效管理手册(知名电器公司)
- 基于分布式光纤传感的交通振动信号识别算法研究
- 形势与政策(2025春)超星尔雅学习通答案满分章节测试
- 全断面岩石掘进机刀盘振动理论及应用
- 工业机器人安全培训
- 煤炭贸易业务指导手册
- 华莱士加盟合同范本
- 人力资源开发与管理模拟试题及答案
- 辽宁省沈阳市皇姑区2023年小升初语文试卷(学生版+解析)
评论
0/150
提交评论