首页 C语言进阶-第四讲存储器与指针

C语言进阶-第四讲存储器与指针

举报
开通vip

C语言进阶-第四讲存储器与指针 1 www.cnasic.comwww.cnasic.com 第四讲第四讲 存储器与指针存储器与指针 ((Memory & PointerMemory & Pointer)) 凌 明 trio@seu.edu.cn 东南大学国家专用集成电路系统工程技术研究中心 www.cnasic.comwww.cnasic.com 目 录 存储器,还是存储器! 内存陷阱! 动态内存分配算法 动态内存分配代码讲解 2 www.cnasic.comwww.cnasic.com 2.1 指针的基本概念 2.1.1...

C语言进阶-第四讲存储器与指针
1 www.cnasic.comwww.cnasic.com 第四讲第四讲 存储器与指针存储器与指针 ((Memory & PointerMemory & Pointer)) 凌 明 trio@seu.edu.cn 东南大学国家专用集成电路系统工程技术研究中心 www.cnasic.comwww.cnasic.com 目 录 存储器,还是存储器! 内存陷阱! 动态内存分配算法 动态内存分配代码讲解 2 www.cnasic.comwww.cnasic.com 2.1 指针的基本概念 2.1.1 指针是什么? 指针是一个变量,它的值是另外一个变量的地址。 例1 2.1.2 指针的类型 指针所存储的那个变量类型,就称为指针的类型。 例 2 有三个不同类型的指针: int I[2], *pI = &I[0]; 右边的三个运算有何不同? pI++; char C[2], *pC = &C[0]; pC++; float F[2], *pF = &F[0]; pF++; 0x00C7 指针变量 int *p 0x00C7 变量 int a 上面例中的两个0x00C7有什么区别? www.cnasic.comwww.cnasic.com 2.1.3 指针的三个要素 1. 指针指向的地址(指针的内容); 2. 指针指向的地址上的内容; 3. 指针本身的地址。 例 3: int A, *pA, **ppA; pA = &A; ppA = &pA; 在复杂的指针都可以通过下表来分析: &ppA ppA *ppA **ppA &pA pA *pA &A A 3 www.cnasic.comwww.cnasic.com 第二章 指针 2.1.4 指针的大小(指针变量占用的内存空间) 与所用的CPU寻址空间大小和类型有关,而与指针类型无关。 8位CPU的指针长度为1~2个字节(51单片机的情况较为复杂,是1~3个字节); 16位CPU的指针长度为2个字节(如MSP430); 32位CPU的指针长度为4个字节(如Intel 80386)。 上面所述是通常情况,并不是全部符合。 2.1.5 指针的初始化 变量在没有赋值之前,其值不定的。对于指针变量,可以表述为:指向不明。 程序访问了一个没 有初始化的指针: int* p; p 的内存是随机的 一个数,比如: 0x3FF0073D 程序随即访问内存 地址: 0x3FF0073D 0x3FF0073D 是哪里的内存?说不定正好是 Windows老大要用的内存,你竟敢访问! Windows一生气,蓝屏。 一个指向不明的指 针,是非常危险的!!! 因此,指针在使用前一定要初始化;在使用前一定要确定指针是非空的!!! www.cnasic.comwww.cnasic.com 2.2 数组与指针 对于数组的两个概念: 1. C语言中只有一维数组,数组的大小必须在编译时作为一个常数确定下来。 数组的元素可以是任何类型,甚至是数组,由此可以方便地得到多维数组; 2. 数组的任何操作,即使采用数组下标进行的运算都等于对应的指针运算。 可以用指针行为替代数组下标的运算。 例4 : int a[4], *p; p = a; //等价于 p =&a[0]; *(a+2) = 0; //等价于 a[2] = 0; p[2] = 0; //等价于 a[2] = 0; 4 www.cnasic.comwww.cnasic.com 但数组不同于指针: 数组名 a是指向数组起始位置的“常量”。 因此,不能对数组名进行赋值操作。 例 5: int a[4], *p; p = a; //正确 a = p; //错误 p++; //正确 a++; //错误 第二章 指针 www.cnasic.comwww.cnasic.com 第二章 指针 2.3 空指针与通用指针 (1). 空指针 是个特殊指针值,也是唯一对任何指针类型都合法的指针值。一个指针变量具有 空指针值,表示它当时没指向有意义的东西,处于闲置状态。空指针值用0 表 示,这个值绝不会是任何程序对象的地址。给一个指针赋值0 就表示要它不指向 任何有意义的东西。为了提高程序的可读性,标准库定义了一个与0 等价的符号 常量NULL,程序里可以写: p = NULL; //注意不要与空字符NUL混淆,NUL等价于‘\0’ 或者: p = 0; 注意: 在编程时,应该将处于闲置的指针赋为空指针; 在调用指针前一定要判断是否为空指针,只有在非空情况下才能调用。 5 www.cnasic.comwww.cnasic.com (2).通用指针 通用指针可以指向任何类型的变量。通用指针的类型用(void *)表示,因此也称为 void 指针。 下面的第三行定义了两个通用指针: int n, *p; double *q; void *gp1, *gp2; 可以直接把任何变量的地址赋给通用指针。 例如,有了上面定义,下面赋值是合法的: gp1 = (void *) &n; 可以把通用指针的值赋给普通的指针。如果被赋值指针与通用指针所指变量的类 型不符,需要写强制转换: p = (int *)gp1; 第二章 指针 www.cnasic.comwww.cnasic.com 2.4 函数指针 2.4.1 函数指针的定义 函数指针即指向函数地址的指针。利用该指针可以知道函数在内存中的位置。因 此也可以利用函数指针调用函数。 函数指针的定义方法: <类型> (* <函数指针名>)(......) 例如: int (*func)(void ) 这里,func就是一个函数指针。 注意:int *func(void)和int (*func)(void)的区别 int *func(void); //这是返回一个整型指针的函数 int (*func)(void); //这是一个函数指针 6 www.cnasic.comwww.cnasic.com 2.4.2 函数指针的使用 例6 :假定有下面的函数声明 int ptr; int fn(int); int (*fp)(int); 指出下面的语句是否合法?,为什么?。 fp = fn; //正确,将函数fn的地址赋给fp fp = fn(5); //错误,返回给fp的结果不是一个函数地址。 fp = &ptr; // 错误,ptr的地址不在程序代码区,两种数据类型不能转换。 从上面的例子可以看出: (1)不能将普通变量的地址赋给函数指针; (2)不能将函数的调用赋给函数指针 (3)可以将函数名赋给一个函数指针 www.cnasic.comwww.cnasic.com 2.4.2 函数指针的用途 一旦函数可以通过指针被传递、被记录,这开启了许多应用,特别是下列三 者: 1. 多态 (polymorphism):指用一个名字定义不同的函数,这函数执行不同但 又类似的操作,从而实现“一个接口,多种方法”。 2. 多线程 (multithreading):将函数指针传进负责建立多线程的 API 中:例如 Win32 的 CreateThread(...pF...)。 3. 回调 (call-back):所谓的回调机制就是:「当发生某事件时,自动呼叫某 段程序代码」。事件驱动 (event-driven) 的系统经常透过函数指针来实现 回调机制,例如Win32 的WinProc 其实就是一种回调,用来处理窗口的 讯息。 7 www.cnasic.comwww.cnasic.com 2.4.3 函数指针数组 例7 :在一个计算器的例子中,有如下一些语句: switch(oper){ case ADD: result=add(op1,op2); break; case SUB: result=sub(op1,op2); break; ... } 对于一个复杂的计算器,switch语句将非常长。我们可以用函数指针数组来完成。 double add(double,double); double sub(double,double); ... double (*oper_func[])(double,double)={add,sub,...}; 第2个步骤是用下面语句替换前面整条switch语句: result=oper_func[oper](op1,op2); oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。 www.cnasic.comwww.cnasic.com ASIX Window中的函数指针 typedef struct window_class { U8 wndclass_id; STATUS (*create)(char *caption, U32 style, U16 x, U16 y, U16 width, U16 hight, U32 wndid, U32 menu, void **ctrl_str, void *exdata); 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)(void *ctrl_str, U16 msg_type, U32 areaId, P_U16 data, U32 size, PMSG trans_msg); STATUS (*repaint)(void *ctrl_str, U32 lparam); STATUS (*move)(void *ctrl_str, U16 x, U16 y, U16 width, U16 hight, void *reserved); STATUS (*enable)(void *ctrl_str, U8 enable); STATUS (*caption)(void *ctrl_str, char *caption, void *exdata); STATUS (*information)(void *ctrl_str, struct asix_window *wndinfo); } WNDCLASS; 8 www.cnasic.comwww.cnasic.com ASIX Window中的函数指针 WNDCLASS WindowClass[] = { {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}, #ifdef ASIX_DEBUG {WNDCLASS_TEST,tst_create, tst_destroy, tst_msgproc, tst_msgtrans, NULL,NULL,NULL,NULL,NULL} #endif }; www.cnasic.comwww.cnasic.com 内存陷阱!内存陷阱! 9 www.cnasic.comwww.cnasic.com 看看这段代码有什么问题? char *DoSomething(…) { char i[32*1024]; memset(i,0,32*1024); … return i; {两个重大问题:两个重大问题: 11,临时变量是通过堆栈实现的,太大的临时变量数组会冲掉堆栈,临时变量是通过堆栈实现的,太大的临时变量数组会冲掉堆栈 22,返回堆栈中的地址是非常危险的,因为堆栈中的值永远是不确定的,返回堆栈中的地址是非常危险的,因为堆栈中的值永远是不确定的 www.cnasic.comwww.cnasic.com 看看这段代码有什么问题? void DoSomething(…) { int i; int j; int k; memset(&k,0,3*sizeof(int) ); … { 这段代码的作用是将这段代码的作用是将33个临时变量清零个临时变量清零 但是这段代码有两个假设:但是这段代码有两个假设: 11,编译器将,编译器将I,j,kI,j,k三个变量通过堆栈表示三个变量通过堆栈表示 22,压栈顺序是,压栈顺序是 I, j , k (I, j , k (假设堆栈是满递减堆栈假设堆栈是满递减堆栈)) 33,如果,如果KK在寄存器怎么办?对在寄存器怎么办?对KK取地址操作将产生取地址操作将产生Data Data AboartAboart 10 www.cnasic.comwww.cnasic.com 关于临时变量 不要对临时变量作取地址操作,因为你不知道编译器是否 将这个变量映射到了寄存器 不要返回临时变量的地址,或临时指针变量,因为堆栈中 的内容是不确定的(出了这个函数,存放在堆栈中的局部 变量就没有意义了!) 不要在申请大的临时变量数组,你的临时变量是在堆栈中 实现的,你有多大的堆栈呢? www.cnasic.comwww.cnasic.com 问题? 现在要为一个矩形区域申请一块内存保存 这块的数据,如果每个Pixle占用2个bit,如 何分配内存? Char *buffer; buffer = malloc (x*y/4); Buffer = Buffer = malloc(xmalloc(x*y/4 + 1);*y/4 + 1); 11 www.cnasic.comwww.cnasic.com 看看这段代码有什么问题? char *DoSomething(…) { char *p, *q; if ( (p = malloc(1024)) == NULL ) return NULL; if ( (q = malloc(2048)) == NULL ) return NULL; … return p; { 如果如果qq没有申请到,首先应该释放没有申请到,首先应该释放pp,然后再返回,然后再返回NULLNULL!! www.cnasic.comwww.cnasic.com 看看这段代码有什么问题? 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); } } PwndPwnd已经被释放了,但是在已经被释放了,但是在forfor循环中被再次引用循环中被再次引用 12 www.cnasic.comwww.cnasic.com 关于动态内存 总是检查动态内存分配是否成功后再引用 该指针! 在分配struct空间是总是使用sizeof 分配内存时宁滥勿缺(别忘了加一) 总是Free由malloc()函数返回的指针 按照ANSI C 标准Free函数是没有返回值的 错误处理时不要忘了其他已分配空间的释 放 www.cnasic.comwww.cnasic.com 动态内存分配算法动态内存分配算法 13 www.cnasic.comwww.cnasic.com 问题的提出 按照调用者的要求分配合适大小的Mem, 返回该内存块的首指针。 如果没有足够的内存返回空指针。 用户不再使用该内存时可以调用Free函数 释放该内存块 快速分配算法并尽量减少内存碎片的情况 www.cnasic.comwww.cnasic.com ptr Size=1 为了方便管理,我们将内存按照为了方便管理,我们将内存按照88个字节进行组织-分配块个字节进行组织-分配块 每个分配块包含两个部分:每个分配块包含两个部分:FreeFree链表指针和本块的大小链表指针和本块的大小 ptr Size=7 BASEBASEAllocpAllocp FirstFirst 分配16个字节 ptr Size=1 ptr Size=4 ptr Size=3 用 户 空 间 BASEBASEAllocpAllocp FirstFirst 返回的 指针 Ptr指向自己 用于Free函数 的校验 14 www.cnasic.comwww.cnasic.com ptr Size=1 ptr Size=4 ptr Size=2 ptr Size=3 AllocpAllocp FirstFirst 用户 指针 释放 ptr Size=1 ptr Size=4 ptr Size=5 ptr Size=1 ptr Size=9 再合并 www.cnasic.comwww.cnasic.com 分配和释放策略 通过分配块为单位简化管理,并使得内存 块更规整,便于以后的合并操作 总是在空闲块的高端地址分配内存,减少 内存碎片 通过ptr指针实现空闲块链表,对于已分配 内存块使用该指针作为校验 释放内存块的时候进行空闲块合并操作 15 www.cnasic.comwww.cnasic.com 动态内存分配代码讲解动态内存分配代码讲解 www.cnasic.comwww.cnasic.com 头部的定义(分配块) union header { struct { union header *ptr; unsigned long size; } s; char c[8];// For debugging; also ensure size is 8 bytes }; typedef union header HEADER; 16 www.cnasic.comwww.cnasic.com 为了方便大家理解的头部 struct sheader { struct sheader *ptr; unsigned long size; }; www.cnasic.comwww.cnasic.com 两个宏定义 #define ABLKSIZE (sizeof (HEADER)) /*将用户申请字节数转换成为分配块+1*/ #define BTOU(nb) ((((nb) + ABLKSIZE - 1) / ABLKSIZE) + 1) 17 www.cnasic.comwww.cnasic.com 代码的分析代码的分析 范例代码已在VC++编译通过, Just Try It!
本文档为【C语言进阶-第四讲存储器与指针】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_827176
暂无简介~
格式:pdf
大小:95KB
软件:PDF阅读器
页数:17
分类:互联网
上传时间:2011-06-12
浏览量:22