内存管理、预处理、结构体预习

1. 栈(stack )
栈用于存放临时变量和函数参数。栈作为一种基本数据结构,可以用来实现的数的调用。尽管大多数编译器在优化时,会把常用的参数或者局部变量放入寄存器中。但用栈来管理函数调用时的临时变量(局部变量和参数) 是通用做法,前者只是辅助手段,且只在当前函数中使用,一旦调用下一层函数,这些值仍然要存入栈中才行。
通常情况下,栈向下(低地址) 增长,每向栈中PUSH一个元素,栈顶就向低地址扩展,每从栈中POP一个元素,栈顶就向高地址回退。一个问题: 在X86平台上,栈顶寄存器为ESP,那么ESP的值在是PUSH操作之前修改呢,还是在PUSH操作之后呢? PUSHESP 这条指令会向栈中存入什么数据呢? 据说X86 系列CPU 中,除了286 外,都是先修改ESP 再压栈的。由于286 没有CPUID 指令,有的OS 用这种方法检查286 的型号。要注意的是,存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也自动释放了,继续访问这些变量会造成意想不到的错误。


2.内存分配方式
(1) 从静态存储区域分配。内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在,如全局变量、static 变量等。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算使用内置于处理器的指令集,效率很高,但分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行时用malloc 或new 申请所需要的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。


3.常见的内存错误及对策
发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。常见的内存错误及其对策如下所述。
(1) 内存分配未成功,却使用了它。编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针P是函数的参数,那么在函数的入口处用“assert(p!=NULL)”进行检查; 如果是用malloc 或new 来申请内存,应该用“if(p==NULL)”或“if(p!=NULL)”进行防错处理。
(2) 内存分配虽然成功,但是尚未初始化就引用它。犯这种错误主要有两个原因:一是没有初始化的观念; 二是误以为内存的默认初值全为零,导致引用初值错误(如数组)。内存的默认初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有,所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
 (3) 内存分配成功并且已经初始化,但操作越过了内存的边界。例如,常发生下标“多1”或者“少1”的操作,特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。
(4) 忘记了释放内存,造成内存泄漏。含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误,总有一次程序会突然死掉,系统出现内存耗尽的提示。动态内存的申请与释放必须配对,程序中malloc 与free 的使用次数一定要相同,否则肯定有错误(new/delete 同理)。
(5) 释放了内存却继续使用它,有以下三种情况。
①程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
②函数的return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
③使用free 或delete 释放了内存后,没有将指针设置为NULL,导致产生野指针。


4.野指针
 野指针不是NULL指针,是指向“垃圾”内存的指针。一般不会错用NULL 指针,因为用if语句很容易判断。但是野指针是很危险的,i语句对它不起作用。
野指针的成因主要有两种。
(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的默认值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如:
char *p = NULL;
char *str = (char *) malloc(100) ;
(2) 指针p 被free或者delete之后,没有置为NULL,让人误以为p 是个合法的指针。别看free 和delete 的名字“恶狠狠的”(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。用调试器跟踪,发现指针p 被free 以后其地址仍然不变( 非NULL ),只是该地址对应的内存是垃圾,p 成了“野指针”。如果此时不把p 设置为NULL,会让人误以为p 是个合法的指针。如果程序比较长,我们有时记不住p 所指的内存是否已经被释放,在继续使用p 之前,通常会用语句if(p != NULL)进行防错处理。很遗憾,此时if 语句起不到防错作用,因为即便p 不是NULL 指针,它也不指向合法的内存块。
char *p= (char *) malloc(100) ;
strepy (P,"hello") ;       //P 所指的内存被释放,但是P所指的地址仍然不变
free (p) ;
.........
if(P!= NULL)           //没有起到防错作用
{
 strcpy (P,"world") ;      //出错
}


5.宏定义
 (1) 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。

(2) 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换。

(3) 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用“# undef" 命令。
#define PI 3.14159
int main()
{
 ........
}
#undef PI
f1()
{
 ......
}

(4) 宏名在源程序中若用引号括起来, 则预处理程序不对其进行宏代换。
#define OK 100
int main ()
{
printf ("OK") ;
 printf ("\n") ;
}

上例中定义宏名OK 表示100,但在prinf语句中OK 被引号括起来,因此不作宏代换。程序的运行结果为OK,这表示把“OK”当字符串处理。

(5) 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。

#define PI 3.1415926
#define S PI*y*y /*PI 是已定义的宏名*/
对于语句
printf("%f",S) ;

在宏代换后变为 printf("%f",3.1415926*y*y);

(6) 对“输出格式”作宏定义,可以减少书写麻烦。

#define P printf

#define D "%d\n"

#define F "%f\n"

int main ()

 int a=5,c=8,e=11;

float b=3.8,d=9.7, f=2 1. 0 8 ;
 P(D F,a,b);
 P(D F,c,d);
 P(D E,e,f);
}


6.文件包含
 (1) 包含命令中的文件名可以用双引号括起来, 也可以用尖括号括起来。例如,以下写 法都是允许的。
#include "stdio.h"
#include <math.h>
但是这两种形式是有区别的: 使用尖括号表示在包含文件目录中去查找(包含目录是由 用户在设置环境时设置的),而不在源文件目录去查找; 使用双引号则表示首先在当前的源文
件录中查找,若未找到才到包含目录中去查找。用户编程时可根据自己文件所在的目录来 选择一种命令形式。
(2)- 一个include 命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include 命令。
(3) 文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。


7.条件编译
第一种形式:#ifdef 标识符
程序段1
#else
程序段2
#endif
第二种形式:#ifndef 标识符
程序段1
#else
程序段2
#endif
第三种形式:#if 常量表达式
程序段1
#else
程序段2
#endif


8. 结构体与数组的比较
都由多个元素组成;
各个元素在内存中的存储空间是连续的;
数组中各个元素的数据类型相同,而结构体中的各个元素的数据类型可以不相同。


9.内存对齐正式原则
① 数据类型自身的对齐值: 基本数据类型的自身对齐值,等于“sizeof(基本数据类型)”。
指定对齐值:“#pragma pack(value)”时的指定对齐值value.
③结构体或者类的自身对齐值: 其成员中自身对齐值最大的那个值。
④数据成员、结构体和类的有效对齐值: 自身对齐值和指定对齐值中较小的那个值。


10.结构体和联合体的区别
 struct 和union 都是由多个不同的数据类型成员组成的,但在任何同一时刻,union 中只存放了一个被选中的成员,而struct 的所有成员都存在。在struct 中,各成员都占有自己的内存空间,
它们是同时存在的,一个struct 变量的总长度等于所有成员长度之和; 在union 中,所有成员不能同时占用它的内存空间,它们不能同时存在,union 变量的长度等于最长的成员的长度。
对于union的不同成员赋值,将会对其他成员重写, 原来成员的值就不存在了,而对于struct的不同成员赋值是互不影响的。







1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可 6私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。、可私 6信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值