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

1.你的数据存放在哪里

bss段用来存放那些没有初始化和初始化为0的全局变量。bss类型的全局变量只占运行时的内存空间,而不占用文件空间。作为全局变量,在整个程序的运行周期内,bss数据是一直存在的。

data段用来存放那些初始化为非零的全局变量。data类型的全局变量既占文件空间,又占用运行时的内存空间。作为全局变量,data数据是一直存在的。

rodata是用来存放常量数据的。

常量不一定就放在rodata里,有的立即数直接和指令编码放在一起,存放在代码段中。

对于字符串常量,编译器会自动去掉重复的字符,保证一个字符串在一个可执行文件只存在一份复制。

常量是不能修改的。

rodata在多个进程间是共享的,可以大大提高空间利用率。

text段存放代码(如函数)和部分整数常量,它与rodata段很相似,主要不同在于这个段是可以执行的。

栈用于存放临时变量和函数参数

堆 标准C语言提供这么几个函数:malloc、realloc、free。

2.栈(stack )

栈用于存放临时变量和函数参数。栈作为一种基本数据结构,可以用来实现函数的调用。尽管大多数编译器在优化时,会把常用的参数或者局部变量放入寄存器中。但用栈来管理函数调用时的临时变量(局部变量和参数) 是通用做法,前者只是辅助手段,且只在当前函数中使用,一旦调用下一层函数,这些值仍然要存入栈中才行。

通常情况下,栈向下(低地址) 增长,每向栈中PUSH一个元素,栈顶就向低地址扩展,每从栈中POP一个元素,栈顶就向高地址回退。一个问题: 在X86平台上,栈顶寄存器为ESP,那么ESP的值在是PUSH操作之前修改呢,还是在PUSH操作之后呢? PUSHESP 这条指令会向栈中存入什么数据呢? 据说X86 系列CPU 中,除了286 外,都是先修改ESP 再压栈的。由于286 没有CPUID 指令,有的OS 用这种方法检查286 的型号。要注意的是,存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也自动释放了,继续访问这些变量会造成意想不到的错误。

3.内存分配方式

内存分配方式有三种。

(1) 从静态存储区域分配。内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在,如全局变量、static 变量等。

(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算使用内置于处理器的指令集,效率很高,但分配的内存容量有限。

(3) 从堆上分配,亦称动态内存分配。程序在运行时用malloc 或new 申请所需要的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

4.野指针

野指针不是NULL指针,是指向“垃圾”内存的指针。一般不会错用NULL 指针,因为用if语句很容易判断。但是野指针是很危险的,i语句对它不起作用。

野指针的成因主要有两种。

1) 指针变量没有被初始化。

2) 指针p 被free或者delete之后,没有置为NULL,让人误以为p 是个合法的指针。

 

5.常见的内存错误及对策

(1) 内存分配未成功,却使用了它。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针P是函数的参数,那么在函数的入口处用“assert(p!=NULL)”进行检查; 如果是用malloc 或new 来申请内存,应该用“if(p==NULL)”或“if(p!=NULL)”进行防错处理。

(2) 内存分配虽然成功,但是尚未初始化就引用它。犯这种错误主要有两个原因:一是没有初始化的观念; 二是误以为内存的默认初值全为零,导致引用初值错误(如数组)。

(3) 内存分配成功并且已经初始化,但操作越过了内存的边界。

(4) 忘记了释放内存,造成内存泄漏。含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误,总有一次程序会突然死掉,系统出现内存耗尽的提示。动态内存的申请与释放必须配对,程序中malloc 与free 的使用次数一定要相同,否则肯定有错误(new/delete 同理)。

(5) 释放了内存却继续使用它,有以下三种情况。

①程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

②函数的return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

③使用free 或delete 释放了内存后,没有将指针设置为NULL,导致产生野指针。

6.带参宏定义与自定义函数的区别

(1)在带参宏定义中,形式参数不分配内存单元,因此不必作类型转换;而宏调用中的实参有具体的值,要用他们去代换形参,因此必须作类型说明。这与函数中的情况是不同的,在函数中,形参和实参时两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存在值传递问题.

(2)在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。

注意:在宏定义中,字符串内的形参通常要用括号括起来以避免出错。

7.文件包含

(1) 包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来。例如,以下写法都是允许的。

#include "stdio.h"

#include <math.h>

但是这两种形式是有区别的: 使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找; 使用双引号则表示首先在当前的源文

件录中查找,若未找到才到包含目录中去查找。用户编程时可根据自己文件所在的目录来选择一种命令形式。

(2) 一个include 命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。

(3) 文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。

8.结构体与数组的比较

(1)都由多个元素组成;

(2)各个元素在内存中的存储空间是连续的;

(3)数组中各个元素的数据类型相同,而结构体中的各个元素的数据类型可以不相同。

9.内存对齐正式原则

(1)数据类型自身的对齐值: 基本数据类型的自身对齐值,等于“sizeof(基本数据类型)”。

(2)指定对齐值:“#pragma pack(value)”时的指定对齐值value。

(3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。

(4)数据成员、结构体和类的有效对齐值: 自身对齐值和指定对齐值中较小的那个值。

10.结构体和联合体的区别

struct 和union 都是由多个不同的数据类型成员组成的,但在任何同一时刻,union 中只存放了一个被选中的成员,而struct 的所有成员都存在。在struct 中,各成员都占有自己的内存空间,它们是同时存在的,一个struct 变量的总长度等于所有成员长度之和; 在union 中,所有成员不能同时占用它的内存空间,它们不能同时存在,union 变量的长度等于最长的成员的长度。

对于union的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对于struct的不同成员赋值是互不影响的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值