C专家编程:第七章:对内存的思考

本文深入探讨了Intel 80x86架构下的内存模型及其工作原理,解析了内存分配的测试方法,并列举了常见的内存管理问题,如内存损坏与泄漏等。此外,还详细介绍了总线错误与段错误的原因及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Intel 80x86内存模型以及它的工作原理:

     在Intel 80x86内存模型中,段是内存模型设计的结果,在86x86的内存模型中,各处理器的地址空间并不一致(因为要保持兼容性),但它们都被分割成以64KB为单位的区域,每个这样的区域便称为段。

      作为80x86内存模型最基本的形式,8086中的段是一块64KB的区域,由一个段寄存器所指向。内存地址的形成经过是:取得段寄存器的值,左移4位(相当于乘上16),或者看成把段寄存器的值看成是20位的,也就是在值的右边扩充4个0。

       然后就是16位的偏移地址,它表示段内的地址。如果把段寄存器的值(经过移位)加上偏移地址,就得到最终的地址。(不同的段地址加上偏移地址所形成的值可能指向同一个内存地址,如:A0000 + FFFF = AFFFF; AFFF0 + 000F = AFFFF)

2、内存媒介的速度与成本关系:

慢速访问 <--------------------------------快速访问

磁带      磁盘     内存     Cache存储器      CPU寄存器

成本低、容量大------------------------>成本高、容量小

3、用程序查看可以分配多大的内存:

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

int MB = 0;

 

while(malloc(1 << 20))

++MB;

printf("Allocated %d  MB total\n", MB);

 

return 0;

}

4、堆经常会出现两种类型的问题:

1)释放或改写仍在使用的内存(称为“内存损坏”)。

2)未释放不再使用的内存(称为“内存泄漏”)。

5、总线错误(bus error)

union {

char a[10];

int i;

}u;

int *p = (int *)&(u.a[1]);

*p = 17;           /*p中未对齐的地址会引起一个总线错误*/

 

错误原因:假设数组a 的地址从0x1000开始,强制类型转换将0x1001--0x1004的内容拷贝给 p ( p 为 int 型),而数据项只能存储在地址是数据项大小的整数倍的内存位置上,此处 int 型数据地址必须为4的整数倍,0x1001不满足。

 

然而有的不显示总线错误,原因是:

       x86体系结构会把地址对齐之后,访问两次,然后把第一次的尾巴和第二次的头拼起来。如果不是x86,那种体系结构下的机器不肯自动干这活,就会产生core。如果在代码中将对齐检查功能打开,运行后能显示buserror

将程序改成下面形式,可以显示总线错误:

#include <stdlib.h>  

  

int main(int argc, char **argv) {  

  

#if defined(__GNUC__)  

# if defined(__i386__)  

        /* Enable Alignment Checking on x86 */  

        __asm__("pushf\norl $0x40000,(%esp)\npopf");  

# elif defined(__x86_64__)  

        /* Enable Alignment Checking on x86_64 */  

        __asm__("pushf\norl $0x40000,(%rsp)\npopf");  

# endif  

#endif  

  

        union{  

                char a[10];  

                int i;  

        }u;  

  

        int *p =(int*)&(u.a[1]);  

        *p =17;  

}  

6、导致段错误的几个直接原因:

1)解除引用一个包含非法值的指针。(解除引用:*)

2)解除引用一个空指针(常常由于从系统程序中返回空指针,并未经检查就使用)。

3)在未得到正确的权限时进行访问。例如,试图往一个只读的文本段存储值就会引起段错误。

4)用完了堆栈或堆空间(虚拟内存虽然巨大但绝非无限)。

 

可能导致段错误的常见编程错误:

1)坏指针值错误:在指针赋值之前就用它来引用内存,或者向库函数传送一个坏指针。第三种是对指针进行释放之后再访问它的内容。可以修改 free语句,在指针释放后将它置为空值。

free(p);    p = NULL;

2)改写错误:越过数组边界写入数据,在动态分配的内存两端之外写入数据,或改写一些堆管理数据结构(在动态分配内存之前的区域写入数据就很容易发生这种情况)

p = malloc(256);  p[-1] =0;  p[256] = 0;

3)指针释放引起的错误:释放同一个内存块两次,或释放一块未曾使用 malloc 分配的内存,或释放仍在使用中的内存,或释放一个无效的指针。一个常见错误:for(p = start; p; p = p->next)这样的循环中迭代一个链表,并在循环体内使用 free(p) 语句。

7、使用setjmp/longjmp从信号中恢复:程序在收到一个 control-C(作为 SIGINT 信号)时将重新启动,而不是退出。

 

#include<setjmp.h>

#include<signal.h>

#include<stdio.h>

 

jmp_bufbuf;

 

voidhandler(int s)

{

if(s == SIGINT)tff

{

printf("nowgot a SIGINT signal\n");

longjmp(buf,1);

}

}

 

intmain()

{

signal(SIGINT,handler);

if(setjmp(buf)!= 0)

{

printf("backin main\n");

return0;

}

else

{

printf("firsttime through\n");

}

while(1);     //循环等待ctrl-C

}

运行结果:

first  time  through

now  got  a SIGINT  signal

back  in  main

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值