您的位置:首页 > 其它

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

2018-01-29 10:26 381 查看
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的不同成员赋值是互不影响的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: