admin 管理员组

文章数量: 887018


2024年2月26日发(作者:网页素材名称)

第四讲存储器与指针(Memory & Pointer)凌明trio@2.0 2008.3.31东南大学国家专用集成电路系统工程技术研究中心目录存储器,还是存储器!内存陷阱!动态内存分配算法动态内存分配代码讲解1

2.1 指针的基本概念2.1.1 指针是什么?指针是一个变量,它的值是另外一个变量的地址。例1指针变量int*p变量inta0x00C70x00C7上面例中的两个0x00C7有什么区别?2.1.2

指针的类型指针所存储的那个变量类型,就称为指针的类型。例2 有三个不同类型的指针:intI[2], *pI= &I[0]; 右边的三个运算有何不同?pI++;char C[2], *pC= &C[0]; pC++;float F[2], *pF= &F[0]; pF++;2.1.3 指针的三个要素1.指针指向的地址(指针的内容);2.指针指向的地址上的内容;3.指针本身的地址。例3:intA, *pA, **ppA;pA= &A;ppA= &pA;在复杂的指针都可以通过下表来分析:&ppAppA*ppA**ppA&pApA*pA&A 2

第二章指针2.1.4 指针的大小(指针变量占用的内存空间)与所用的CPU寻址空间大小和类型有关,而与指针类型无关。8位CPU的指针长度为1~2个字节(51单片机的情况较为复杂,是1~3个字节);16位CPU的指针长度为2个字节(如MSP430);32位CPU的指针长度为4个字节(如Intel 80386)。上面所述是通常情况,并不是全部符合。2.1.5 指针的初始化变量在没有赋值之前,其值不定的。对于指针变量,可以表述为:指向不明。程序访问了一个没p 的内存是随机的程序随即访问内存有初始化的指针:一个数,比如:地址:int* p;0x3FF0073D0x3FF0073D一个指向不明的指针,是非常危险的!!!0x3FF0073D 是哪里的内存?说不定正好是Windows老大要用的内存,你竟敢访问!Windows一生气,蓝屏。因此,指针在使用前一定要初始化;在使用前一定要确定指针是非空的!!!2.2 数组与指针对于数组的两个概念:1. C语言中只有一维数组,数组的大小必须在编译时作为一个常数确定下来。数组的元素可以是任何类型,甚至是数组,由此可以方便地得到多维数组;2. 数组的任何操作,即使采用数组下标进行的运算都等于对应的指针运算。可以用指针行为替代数组下标的运算。例4 :inta[4], *p;p = a; //等价于p =&a[0];*(a+2) = 0; //等价于a[2] = 0;p[2] = 0; //等价于a[2] = 0;

3

第二章指针但数组不同于指针:数组名a是指向数组起始位置的“常量”。因此,不能对数组名进行赋值操作。例5:inta[4], *p;p = a; //正确a = p; //错误p++; //正确a++; //错误第二章指针2.3 空指针与通用指针(1). 空指针是个特殊指针值,也是唯一对任何指针类型都合法的指针值。一个指针变量具有空指针值,表示它当时没指向有意义的东西,处于闲置状态。空指针值用0 表示,这个值绝不会是任何程序对象的地址。给一个指针赋值0 就表示要它不指向任何有意义的东西。为了提高程序的可读性,标准库定义了一个与0 等价的符号常量NULL,程序里可以写:p = NULL; //注意不要与空字符NUL混淆,NUL等价于‘0’或者:p = 0;注意:在编程时,应该将处于闲置的指针赋为空指针;在调用指针前一定要判断是否为空指针,只有在非空情况下才能调用。4

第二章指针(2).通用指针通用指针可以指向任何类型的变量。通用指针的类型用(void *)表示,因此也称为void 指针。下面的第三行定义了两个通用指针:intn, *p;double *q;void *gp1, *gp2;可以直接把任何变量的地址赋给通用指针。例如,有了上面定义,下面赋值是合法的:gp1 = (void *) &n;可以把通用指针的值赋给普通的指针。如果被赋值指针与通用指针所指变量的类型不符,需要写强制转换:p = (int*)gp1;2.4 函数指针2.4.1 函数指针的定义函数指针即指向函数地址的指针。利用该指针可以知道函数在内存中的位置。因此也可以利用函数指针调用函数。函数指针的定义方法:<类型> (* <函数指针名>)(......)例如:int(*func)(void)这里,func就是一个函数指针。注意:int*func(void)和int(*func)(void)的区别int*func(void); //这是返回一个整型指针的函数int(*func)(void);//这是一个函数指针5

2.4.2 函数指针的使用例6 :假定有下面的函数声明intptr;intfn(int);int(*fp)(int);指出下面的语句是否合法?,为什么?。fp= fn; //正确,将函数fn的地址赋给fpfp= fn(5); //错误,返回给fp的结果不是一个函数地址。fp= &ptr;// 错误,ptr的地址不在程序代码区,两种数据类型不能转换。从上面的例子可以看出:(1)不能将普通变量的地址赋给函数指针;(2)不能将函数的调用赋给函数指针(3)可以将函数名赋给一个函数指针2.4.2 函数指针的用途一旦函数可以通过指针被传递、被记录,这开启了许多应用,特别是下列三者:1.多态(polymorphism):指用一个名字定义不同的函数,这函数执行不同但又类似的操作,从而实现“一个接口,多种方法”。2.多线程(multithreading):将函数指针传进负责建立多线程的API 中:例如Win32 的CreateThread(...pF...)。3.回调(call-back):所谓的回调机制就是:「当发生某事件时,自动呼叫某段程序代码」。事件驱动(event-driven) 的系统经常透过函数指针来实现回调机制,例如Win32 的WinProc 其实就是一种回调,用来处理窗口的讯息。6

2.4.3 函数指针数组例7 :在一个计算器的例子中,有如下一些语句:switch(oper){caseADD:result=add(op1,op2);break;caseSUB:result=sub(op1,op2); break;... }对于一个复杂的计算器,switch语句将非常长。我们可以用函数指针数组来完成。doubleadd(double,double);doublesub(double,double);...double(*oper_func[])(double,double)={add,sub,...};第2个步骤是用下面语句替换前面整条switch语句:result=oper_func[oper](op1,op2);oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。关于函数指针引用的说明标准写法:(*pf)(arg1,arg2,…);ANSI C中允许的简写:pf(arg1,arg2,…);注意:以上的写法是简写形式7

ASIX Window中的函数指针typedefstructwindow_class{U8wndclass_id;STATUS (*create)(charU16 hight, U32 wndid, U32 menu, void **ctrl_str, void *exdata);*caption, U32 style, U16 x, U16 y, U16 width,

STATUS (*destroy)(void*ctrl_str);STATUS (*msg_proc)( U32 win_id, U16 asix_msg, U32 lparam, void

*data, U16 wparam, void *reserved);STATUS (*msg_trans)(voidP_U16 data, U32 size, PMSG trans_msg);*ctrl_str, U16 msg_type, U32 areaId,

STATUS (*repaint)(void*ctrl_str, U32 lparam);STATUS (*move)(voidvoid *reserved);*ctrl_str, U16 x, U16 y, U16 width, U16 hight,

STATUS (*enable)(void*ctrl_str, U8 enable);STATUS (*caption)(void*ctrl_str, char *caption, void *exdata);STATUS (*information)(void*ctrl_str, structasix_window*wndinfo);} WNDCLASS;X Window中的函数指针WNDCLASSWindowClass[] = {{WNDCLASS_WIN,wn_create,wn_destroy,wn_msgproc,wn_msgtrans, wn_repaint,NULL,NULL,wn_caption, NULL},{WNDCLASS_BUTTON,Btn_create,Btn_destroy,Btn_msg_proc,Btn_msg_trans,Btn_repaint,NULL,Btn_enable,Btn_caption,

NULL},{WNDCLASS_SELECT,sl_create, sl_destroy, sl_msg_proc, sl_msg_trans, sl_repaint,NULL, sl_enable,sl_caption, NULL},{WNDCLASS_SELECTCARD,NULL, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL},{WNDCLASS_MENU,menu_create, menu_destroy, menu_msgproc, menu_msgtrans, mn_repaint, NULL, NULL,NULL,NULL},{WNDCLASS_LIST,Lbox_create,Lbox_destroy, Lbox_msgproc, Lbox_msgtrans, lb_repaint, NULL, NULL,NULL,NULL},{WNDCLASS_KEYBD, kbd_create,kbd_destroy,kbd_msgproc, kbd_msgtrans, kbd_repaint, NULL, NULL,NULL,NULL},{WNDCLASS_SCROLL,sb_create,sb_destroy, sb_msgproc,sb_msgtrans, sb_repaint,NULL,sb_enable, NULL,NULL},{WNDCLASS_KEYBAR,kb_create,kb_destroy,kb_msgproc,kb_msgtrans,NULL,NULL,NULL,NULL,NULL},#ifdefASIX_DEBUG{WNDCLASS_TEST,tst_create, tst_destroy, tst_msgproc, tst_msgtrans, NULL,NULL,NULL,NULL,NULL}#endif};8

内存陷阱!看看这段代码有什么问题?char *DoSomething(…){char i[32*1024];memset(i,0,32*1024);…return i;{两个重大问题:两个重大问题:1,临时变量是通过堆栈实现的,太大的临时变量数组会冲掉堆栈2,返回堆栈中的地址是非常危险的,因为堆栈中的值永远是不确定的9

看看这段代码有什么问题?void DoSomething(…){inti;intj;intk;memset(&k,0,3*sizeof(int));这段代码的作用是将3个临时变量清零但是这段代码有两个假设:…{1,编译器将I,j,k三个变量通过堆栈表示2,压栈顺序是I, j , k (假设堆栈是满递减堆栈)3,如果K在寄存器怎么办?对K取地址操作将产生Data 关于临时变量不要对临时变量作取地址操作,因为你不知道编译器是否将这个变量映射到了寄存器不要返回临时变量的地址,或临时指针变量,因为堆栈中的内容是不确定的(出了这个函数,存放在堆栈中的局部变量就没有意义了!)不要在申请大的临时变量数组,你的临时变量是在堆栈中实现的,你有多大的堆栈呢?10

问题?现在要为一个矩形区域申请一块内存保存这块的数据,如果每个Pixle占用2个bit,如何分配内存?Char *buffer;

buffer = malloc(x*y/4);Buffer = malloc(x*y/4 + 1);看看这段代码有什么问题?char *DoSomething(…){char *p, *q;if ( (p = malloc(1024)) == NULL ) return NULL;

if ( (q = malloc(2048)) == NULL ) return NULL;

…return p;{如果q没有申请到,首先应该释放p,然后再返回NULL!11

内存泄漏(Memory Leak)的原因在差错处理时,忘了释放已分配的动态存储空间由于程序复杂性的增加,现在的嵌入式软件往往是采用团队开发的模式,在程序员沟通的过程中,往往会出现一些误会与推……诿。比如,A程序员在他的代码中分配了一块存储器,B程序员另一个用户指针q的代码将使用这块内存,但是他们没有沟通好到底由谁来释放q的头部信息这块内存,这就非常容易造成内存泄漏第三个原因最复杂,也最难以被发现。由于free()函数是根据Malloc返回的用户指针malloc()P的头部信息pfree()的入口参数不是正确的指针,或者函数只能释放由函数分配的空间头部的控制信息来进行释放的,所以malloc()函数返回的指针。如果malloc()分配空间的头部信息free()函数P所指向的动态被破坏(往往是因为其他人在往动态存储器写数据溢出造成分配内的),都将造成free()函数无法正确释放存空间void DoSomething(char* ptr;){void DoSomething(char* ptr;){……char *p;intchar *p;if (ptrinti;i;if ((p =(char*)malloc(1024)) == NULL ) return ;

if (ptr== NULL) return; /*== NULL) return; /*入口参数合法性检查入口参数合法性检查*/*/if ((p =(char*)malloc(1024)) == NULL ) return ;

for(ifor(i= 0; i < 1024; i++)*p++ = *ptr++;= 0; i < 1024; i++)*p++ = *ptr++;…free(p);

…/*freefree(p);

语句是无效的,因为preturn ;/*free语句是无效的,因为p的值已经改变了,的值已经改变了,freefree函数将无法释放函数将无法释放*/*/}

return ;}

如何避免内存泄漏?一般情况下,在系统启动后通过处理时都应该通过理,避免漏掉释放哪些已经被分配的存储空间free()函数及时地释放。程序员要非常小心地处malloc()函数分配的内存空间在错误注意一定要保存正确释放这块内存的必要条件malloc()函数返回的动态内存区的首指针,这是我们避免在访问动态内存区时发生数据溢出的情况,程序员要特别小心数组的访问越界,strcpy()、memcpy()、sprintf()等标准库函数在往动态分配的存储区写数据时边界条件。因为对这块动态存储区的写越界不仅有可能破坏其他的动态存储区中的数据,也有可能破坏相邻动态存储区的头部信息,从而造成free()函数的失败对于团队开发的情况,应该本着谁申请,谁释放的原则,也就是由程序员申请的动态存储区最好由A各司其职,保证自己申请的动态存储区在不需要时被正确地释放A程序员负责释放,这样每个程序员12

看看这段代码有什么问题?void FreeWindowsTree(windows*Root){if(Root!= NULL){window *pwnd;/* 释放pwndRoot的子窗口.. */for(pwnd= Root->Child;pwnd!= NULL;pwnd= pwnd->Sibling)FreeWindowTree(pwnd);if(Root->strWndTitle!= NULL)野指针!FreeMemory(Root->strWndTitle);FreeMemory(Root);}}Pwnd已经被释放了,但是在for循环中被再次引用“野指针”产生的原因指针在初始化之前就被直接引用。这个问题主要是针对局部变量,因为大多数编译器在处理全局变量时,会为全局变量静态分配存储器空间,并且要么以程序中的初值对其进行初始化,要么以零对没有初值的全局变量进行初始化一个合法的指针p所指向的内存空间已经被释放了,但是这个指针的值(也就是被释放内存空间的地址)并没有被置为NULL,如果我们通过这个指针p继续访问这块已经被释放的内存空间,后果可能是非常危险的造成“野”指针的第三个原因是返回局部变量的指针对于上一页的程序范例:我们释放了对于上一页的程序范例:这段代码将工作的非常好。但是,我们来考虑下面的情况:在释放我们释放了pwndpwnd所指向的内存空间,并在下一次所指向的内存空间,并在下一次forfor循环时重新引用循环时重新引用pwndpwnd所指向的内容所指向的内容SiblingSibling。我敢打赌在。我敢打赌在9999%的情况下,%的情况下,任务这段代码将工作的非常好。但是,我们来考虑下面的情况:在释放pwnd所指向的空间后,由于中断而唤醒了更高优先级的A。A任务紧接着通过malloc()函数申请了一块动态内存区,而pwnd所指向的空间后,由于中断而唤醒了更高优先级的给了任务任务A。AA,任务紧接着通过malloc()函数申请了一块动态内存区,而malloc()malloc()函数又恰好将刚被释放的函数又恰好将刚被释放的pwndpwnd所指向的空间分配所指向的空间分配pwnd给了任务A,AA对这块内存区进行了修改。当控制权最终回到我们的对这块内存区进行了修改。当控制权最终回到我们的forfor循环时,程序将通过循环时,程序将通过pwndpwnd引用引用SiblingSibling。但是请注意此时pwnd所指向的空间已经属于所指向的空间已经属于AA,并且已经被,并且已经被AA修改过了,因此修改过了,因此SiblingSibling的内容已经不存在了,对它的引用是错误的。但是请注意此时的内容已经不存在了,对它的引用是错误的13

关于动态内存总是检查动态内存分配是否成功后再引用该指针!在分配struct空间是总是使用sizeof分配内存时宁滥勿缺(别忘了加一)总是Free由malloc()函数返回的指针按照ANSI C 标准Free函数是没有返回值的错误处理时不要忘了其他已分配空间的释放动态内存分配算法14

问题的提出按照调用者的要求分配合适大小的Mem,返回该内存块的首指针。如果没有足够的内存返回空指针。用户不再使用该内存时可以调用Free函数释放该内存块快速分配算法并尽量减少内存碎片的情况ocpBASEAllocpBASEptrptrSize=1Size=1FirstptrFirstptrSize=7Size=4分配16个字节ptrPtr指向自己Size=3用于Free函数返回的用的校验指针户空间为了方便管理,我们将内存按照8个字节进行组织-分配块每个分配块包含两个部分:Free链表指针和本块的大小15

AllocpptrptrptrSize=1FirstSize=1Size=1ptrptrptrSize=4Size=4Size=9ptrptr用户Size=2Size=5指针释放ptr再合并Size=分配和释放策略通过分配块为单位简化管理,并使得内存块更规整,便于以后的合并操作总是在空闲块的高端地址分配内存,减少内存碎片通过ptr指针实现空闲块链表,对于已分配内存块使用该指针作为校验释放内存块的时候进行空闲块合并操作16

动态内存分配代码讲解头部的定义(分配块)union header {struct{union header *ptr;unsigned long size;} s;char c[8];// For debugging; also ensure size is 8 bytes

};typedefunion header HEADER;17

为了方便大家理解的头部structsheader{structsheader*ptr;unsigned longsize;};两个宏定义#defineABLKSIZE(sizeof(HEADER))/*将用户申请字节数转换成为分配块+1*/#defineBTOU(nb)((((nb) +

ABLKSIZE -1) / ABLKSIZE) + 1)18

代码的分析范例代码已在VC++编译通过,Just Try It!19


本文标签: 指针 函数 分配 内存 释放