在 C 和 C++ 语言开发中, 指针内存一直是学习的重点因为 C 语言作为一种偏底层的中低级语言, 提供了大量的内存直接操作的方法, 这一方面使程序的灵活度最大化, 同时也为 bug 埋下很多隐患
小编推荐一个学 C 语言 / C++ 的学习裙 六九九, 四七零, 五九六 , 无论你是大牛还是小白, 是想转行还是想入行都可以来了解一起进步一起学习! 裙内有开发工具, 很多干货和技术资料分享!
因此, 无论如何, 我们都要对内存有一个清晰的理解
1 对内存的分配
32 位操作系统支持 4GB 内存的连续访问, 但通常把内存分为两个 2GB 的空间, 每个进程在运行时最大可以使用 2GB 的私有内存 (0x000000000x7FFFFFFF) 即理论上支持如下的大数组:
char szBuffer[2*1024*1024*1024];
当然, 由于在实际运行时, 程序还有代码段临时变量段动态内存申请等, 实际上是不可能用到上述那么大的数组的
至于高端的 2GB 内存地址(0x800000000xFFFFFFFF), 操作系统一般内部保留使用, 即供操作系统内核代码使用
在 Windows 和 Linux 平台上, 一些动态链接库 (Windows 的 dll,Linux 的 so) 以及 ocx 控件等, 由于是跨进程服务的, 因此一般也在高 2GB 内存空间运行
可以看到, 每个进程都能看到自己的 2GB 内存以及系统的 2GB 内存, 但是不同进程之间是无法彼此看到对方的当然, 操作系统在底层做了很多工作, 比如磁盘上的虚拟内存交换(请看下以标题), 不同的内存块动态映射等等
2 虚拟内存
虚拟内存的基本思想是: 用廉价但缓慢的磁盘来扩充快速却昂贵的内存
在一定时刻, 程序实际需要使用的虚拟内存区段的内容就被载入物理内存中当物理内存中的数据有一段时间未被使用, 它们就可能被转移到硬盘中, 节省下来的物理内存空间用于载入需要使用的其他数据
在进程执行过程中, 操作系统负责具体细节, 使每个进程都以为自己拥有整个地址空间的独家访问权这个幻觉是通过虚拟内存实现的
所有进程共享机器的物理内存, 当内存使用完时就用磁盘保存数据在进程运行时, 数据在磁盘和内存之间来回移动
内存管理硬件负责把虚拟地址翻译为物理地址, 并让一个进程始终运行于系统的真正内存中, 应用程序员只看到虚拟地址, 并不知道自己的进程在磁盘与内存之间来回切换
从潜在的可能性上说, 与进程有关的所有内存都将被系统所使用, 如果该进程可能不会马上运行(可能它的优先级低, 也可能是它处于睡眠状态), 操作系统可以暂时取回所有分配给它的物理内存资源, 将该进程的所有相关信息都备份到磁盘上
进程只能操作位于物理内存中的页面当进程引用一个不在物理内存中的页面时, MMU 就会产生一个页错误
内存对此事做出响应, 并判断该引用是否有效如果无效, 内核向进程发出一个 segmentation violation(段违规)的信号, 内核从磁盘取回该页, 换入内存中, 一旦页面进入内存, 进程便被解锁, 可以重新运行进程本身并不知道它曾经因为页面换入事件等待了一会
3 内存的使用
对于程序员, 我们最重要的是能理解不同进程间私有内存空间的含义 C 和 C++ 的编译器把私有内存分为 3 块: 基栈浮动栈和堆
基栈: 也叫静态存储区, 这是编译器在编译期间就已经固定下来必须要使用的内存, 如程序的代码段静态变量全局变量 const 常量等
浮动栈: 很多书上称为栈, 就是程序开始运行, 随着函数对象的一段执行, 函数内部变量对象的内部成员变量开始动态占用内存, 浮动栈一般都有生命周期, 函数结束或者对象析构, 其对应的浮动栈空间的就拆除了, 这部分内容总是变来变去, 内存占用也不是固定, 因此叫浮动栈
堆: C 和 C++ 语言都支持动态内存申请, 即程序运行期可以自由申请内存, 这部分内存就是在堆空间申请的堆位于 2GB 的最顶端, 自上向下分配, 这是避免和浮动栈混到一起, 不好管理我们用到 malloc 和 new 都是从堆空间申请的内存, new 比 malloc 多了对象的支持, 可以自动调用构造函数另外, new 创建对象, 其成员变量位于堆里面
我们来看一个例子:
- const int n = 100;
- void Func(void)
- {
- char ch = 0;
- char* pBuff = (char*)malloc(10);
- //
- }
这个函数如果运行, 其中 n 由于是全局静态变量, 位于基栈, i 和 pBuff 这两个函数内部变量, i 位于浮动栈, 而 pBuff 指向的由 malloc 分配的内存区, 则位于堆栈
在内存理解上, 最著名的例子就是线程启动时的参数传递
函数启动一个线程, 很多时候需要向线程传参数, 但是线程是异步启动的, 即很可能启动函数已经退出了, 而线程函数都还没有正式开始运行, 因此, 绝不能用启动函数的内部变量给线程传参
道理很简单, 函数的内部变量在浮动栈, 但函数退出时, 浮动栈自动拆除, 内存空间已经被释放了当线程启动时, 按照给的参数指针去查询变量, 实际上是在读一块无效的内存区域, 程序会因此而崩溃
那怎么办呢? 我们应该直接用 malloc 函数给需要传递的参数分配一块内存区域, 将指针传入线程, 线程收到后使用, 最后线程退出时, free 释放
小编推荐一个学 C 语言 / C++ 的学习裙 六九九, 四七零, 五九六 , 无论你是大牛还是小白, 是想转行还是想入行都可以来了解一起进步一起学习! 裙内有开发工具, 很多干货和技术资料分享!
我们来看例子:
- // 这个结构体就是参数表
- typedef struct _CListen_ListenAcceptTask_Param_
- {
- Linux_Win_SOCKET m_nSocket;
- // 其他参量
- }SCListenAcceptTaskParam;
- // 习惯性写法, 设置结构体后, 立即声明结构体的尺寸, 为后续 malloc 提供方便
- const ULONG SCListenAcceptTaskParamSize=sizeof(SCListenAcceptTaskParam);
- // 这里接收到连接请求, 申请参数区域, 将关键信息带入参数区域, 帮助后续线程工作
- bool CListen::ListenTaskCallback(void* pCallParam,int& nStatus)
- {
- // 正常的函数逻辑
- // 假定 s 是 accept 到的 socket, 需要传入后续线程工作
- // 在此准备一块参数区域, 从远堆上申请
- SCListenAcceptTaskParam* pParam=(SCListenAcceptTaskParam*) malloc(SCListenAcceptTaskParamSize);
- // 给参数区域赋值
- pParam->m_nSocket=s;
- // 此处启动线程, 将 pParam 传递给线程
- // 正常的函数逻辑
- }
- // 这是线程函数, 负责处理上文 accept 到的 socket
- bool CListen::ListenAcceptTask(void* pCallParam,int& nStatus)
- {
- // 第一句话就是强制指针类型转换, 获得外界传入的参数区域
- SCListenAcceptTaskParam* pParam= (SCListenAcceptTaskParam*)pCallParam;
- // 正常的函数逻辑
- // 退出前, 必须要做的工作, 确保资源不被泄露
- close(pParam->m_nSocket); // 关闭 socket
- free(pCallParam); // free 传入的参数区域
- //
- }
4 内存 bug
无规则的滥用内存和指针会导致大量的 bug, 程序员应该对内存的使用保持高度的敏感性和警惕性, 谨慎地使用内存资源
使用内存时最容易出现的 bug 是:
(1)坏指针值错误:
在指针赋值之前就用它来引用内存, 或者向库函数传送一个坏指针, 第三种可能导致坏指针的原因是对指针进行释放之后再访问它的内容
可以修改 free 语句, 在指针释放之后再将它置为空值
free(p); p = NULL;
这样, 如果在指针释放之后继续使用该指针, 至少程序能在终止之前进行信息转储
(2)改写 (overwrite) 错误:
越过数组边界写入数据, 在动态分配的内存两端之外写入数据, 或改写一些堆管理数据结构(在动态分配内存之前的区域写入数据就很容易发生这种情况)
p = malloc(256); p[-1] = 0; p[256] = 0;
(3)指针释放引起的错误:
释放同一个内存块两次, 或释放一块未曾使用 malloc 分配的内存, 或释放仍在使用中的内存, 或释放一个无效的指针
一个极为常见的与释放内存有关的错误就是在 for(p=start;p=p->next)这样的循环中迭代一个链表, 并在循环体内使用 free(p)语句这样, 在下一次循环迭代时, 程序就会对已经释放的指针进行解除引用操作, 从而导致不可预料的结果
我们可以这样迭代:
- struct node *p, *tart, *temp;
- for(p = start; p ; p = temp)
- {
- temp = p->next;
- free(p);
- }
小编推荐一个学 C 语言 / C++ 的学习裙 六九九, 四七零, 五九六 , 无论你是大牛还是小白, 是想转行还是想入行都可以来了解一起进步一起学习! 裙内有开发工具, 很多干货和技术资料分享!
这些是 C/C++ 能做的
服务器开发工程师人工智能云计算工程师信息安全 (黑客反黑客) 大数据 数据平台嵌入式工程师流媒体服务器数据控解图像处理音频视频开发工程师游戏服务器分布式系统游戏辅助等
来源: http://www.jianshu.com/p/fc3407fed793