c语言中默认存储器,C语言中内存储器的管理

C语言中内存的管理

一、动态内存分配与释放

1、为什么要使用动态内存分配,下面看一个实例,关于超市中购买记录的一段程序

#include

#include

struct Product

{

char name[128];

int price;

};

struct Product pro[1000];   //1000有限制,所以要使用动态内存分配

struct Product sale;

void waitForSale()

{

printf("Record Product: ");

scanf("%s %d", sale.name, &sale.price);

}

int main()

{

int i = 0;

for(i=0; i<1000; i++)

{

waitForSale();

strcpy(pro[i].name, sale.name);

pro[i].price = sale.price;

printf("%s, %d\n", pro[i].name, pro[i].price);

}

return 0;

}

上面程序的改进方法:

1、定义1000个元素的数组,当元素个数超过1000时就要定义一个指针struct product *p = malloc();

2、一开始就使用malloc动态申请内存空间。

这两种方法都要用到动态内存的分配。

那为什么要动态分配内存?

1、C语言中的一切操作都是基于内存的

2、变量和数组都是内存的别名,如何分配这些内存由编译器在编译期间决定

# 定义数组时必须指定数组长度

# 而数组长度是在编译期间就必须决定的

需求:

程序运行期间,可能需要使用一些额外的内存空间。

# malloc和free用于执行动态内存的分配和释放

系统中有一些内存空间是闲置下来的,它们是留给程序运行期间去动态申请的地址空间。就是我们通常说的内存池,当程序运行期间需要动态申请一块地址空间的时间就使用malloc进行动态申请,使用完成后就要使用free释放掉。

2、malloc和free

# malloc所分配的是一块连续的内存,以字节为单位,并且不带任何的类型信息

# free用于将动态内存归还系统

void *malloc(size_t size);

void free(void * pointer);

注意:

1、malloc实际分配的内存可能会比请求的稍微多一点,但是不能依赖于编译器的这个行为。也就是说我们不要利用这一点做文章,知道就可以了

2、当请求的动态内存无法满足时malloc返回NULL

3、当free的参数为NULL时,函数直接返回

3、calloc和realloc

# 你认识malloc的兄弟吗?

void * calloc(size_t num, size_t size);

void * realloc(void *pointer, size_t new_size);

# calloc的参数代表所返回内存的类型信息

# calloc会将返回的内存初始化为0

# realloc用于修改一个原先已经分配的内存块大小

1、在使用realloc之后应该使用其返回值

2、当pointer的第一个参数为NULL时,等价于malloc

实例分析:

#include

#include

int main()

{

int i = 0;

int* pI = (int*)malloc(5 * sizeof(int));

short* pS = (short*)calloc(5, sizeof(short)); //它带有类型信息,5个元素,每个元素是short类型

for(i=0; i<5; i++)

{

printf("pI[%d] = %d, pS[%d] = %d\n", i, pI[i], i, pS[i]);

}

pI = (int*)realloc(pI, 10 * sizeof(int));

for(i=0; i<10; i++)

{

printf("pI[%d] = %d\n", i, pI[i]);

}

int *pp = (int *)malloc(0);

printf("%s\n", *pp);

free(pI);

free(pS);

return 0;

}

运行结果(注意观察结果)

pI[0] = 4064336, pS[0] = 0

pI[1] = 4064336, pS[1] = 0

pI[2] = 1380930387, pS[2] = 0

pI[3] = 1162103135, pS[3] = 0

pI[4] = 1179210830, pS[4] = 0pI[0] = 4064336

pI[1] = 4064336

pI[2] = 1380930387

pI[3] = 1162103135

pI[4] = 1179210830

pI[5] = 1380930387

pI[6] = 1447382111

pI[7] = 909986885

pI[8] = 1330794496

pI[9] = 1397966147

(null)小结:

1、动态内存分配是C语言中的强大功能

2、程序能够在需要的时候有机会使用更多的内存

3、malloc单纯的从系统中申请固定字节大小的内存

4、calloc能以类型大小为单位申请内存并初始化为0

5、realloc用于重置内存大小

malloc(0);将返回NULL;

二、程序中的三国天下

栈、堆、静态存储区

1、程序中的栈

# 栈是现代计算机程序里最为重要的概念之一

# 栈在程序中用于维护函数调用上下文,没有函数,没有局部变量。

# 栈保存了一个函数调用所需要的维护信息

1、函数参数,函数返回地址

2、局部变量

3、函数调用上下文

所以我们不要返回局部变量的指针

111011565.jpg

2、程序中的堆

# 为什么有了栈还需要堆?

栈上的数据在函数返回后就会被释放掉,无法传递到函数外部,如:局部数组

# 堆是程序中一块巨大的内存空间,可有程序自由使用

# 堆中被程序申请使用的内存在程序主动释放前将一直有效

栈与堆的一个重要区别就是栈是系统分配和释放,而堆必须通过申请才能获得。

#系统对堆空间的管理方式

空闲链表法,位图法,对象池发等等

111011566.jpg

由图可以看出,如果没有合适的内存块,就会返回NULL。所以malloc(0)会返回NULL。

空闲链表是内核记录内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻址第一个空间大于所申请空间的堆结点,然后将该结点从空闲链表中删除,并将该结点的空间分配给程序。另外,对于大多数系统,会在这块内存空间的首地址处记录内存分配的大小,这样代码中的delete或free就能够正确的释放本内存空间。另外由于找到的堆结点的大小不一定正好等于申请大小,系统会将多余的那部分重新放入空闲链表中。

3、程序中的静态存储区

# 程序静态存储区随着程序的运行而分配空间,直到程序运行结束

# 在程序的编译期静态存储区的大小就已经确定

# 程序的静态存储区主要用于保存程序中的全局变量和静态变量

# 与栈和堆不用,静态存储区的信息最终会保存到可执行程序中

程序三国天下,分工明确

小结:

1、栈、堆和静态存储区是C语言程序涉及的三个基本内存区

2、栈区主要用于函数调用的使用

3、堆区主要是用于内存的动态申请和归还

4、静态存储区用于保存全局变量和静态变量

三、程序内存布局

1、程序文件的一般布局

# 代码在可执行程序中的对应关系

111011567.jpg

# 文件布局在内存中的映射(进程)

111011568.jpg

# 各个段的作用

1、堆栈段在程序运行后才正式存在,是程序运行的基础

2、.bss段存放的是未初始化的全局变量和静态变量,bss段并不给该段的数据分配空间,只是记录数据所需空间大小

3、.text段存放的是程序中的可执行代码

4、.data段保存的是那些已经初始化了的全局变量和静态变量,data段已经为数据分配了空间,数据保存在目标文件中。

5、.rodata段存放程序中的常量值,如字符串常量,.rodata段的数据不可以修改,如果修改会出现segment error的错误

例子:

void main()

{

char *p = "hello, world\n";

p[0] = 'H';

printf(p);

}

程序术语对应关系

1、静态存储区通常指程序中的.bss和.data段

2、只读区通常指程序中的.rodata段

3、局部变量所占空间为栈上空间

4、动态空间为堆中的空间

5、程序可执行代码存放于.text段

四、野指针和内存操作分析

1、初识野指针

# 野指针通常是因为指针变量中保存的值不是一个合法的内存地址而造成的

# 野指针不是NULL指针,是指向不可用内存的指针

# NULL指针不容易用错,因为if语句能很好的判断一个指针是不是NULL

C语言中没有任何手段可以判断一个指针是否为野指针!

2、野指针的由来

1、局部指针变量没有被初始化

#include

#include

struct Student

{

char* name;

int number;

};

int main()

{

struct Student s;

strcpy(s.name, "Delphi Tang"); // s是局部变量,并没有初始化,s.name是一个随机值,也就是一个野指针

s.number = 99;

return 0;

}

2、使用已经释放过后的指针

#include

#include

#include

void func(char* p)

{

printf("%s\n", p);

free(p);

}

int main()

{

char* s = (char*)malloc(5);

strcpy(s, "Delphi Tang"); //数组越界

func(s);

printf("%s\n", s);    / 使用了已经释放的指针,后果可大可小,

//小的话,正好野指针没有被使用,大的话,有些程序会莫名其妙的关闭

return 0;

}

# 指针所指向的变量在指针之前被销毁

#include

char* func()

{

char p[] = "Delphi Tang";

return p;

}

int main()

{

char* s = func();//s指向一块已经被释放的空间

printf("%s\n", s); //局部变量已经被释放了,这里打印结果是不可预料的。

return 0;

}

3、非法内存操作分析

# 结构体成员指针未初始化

# 没有为结构体指针分配足够的内存

#include

#include

struct Demo

{

int* p;

};

int main()

{

struct Demo d1;

struct Demo d2;

int i = 0;

for(i=0; i<10; i++)

{

d1.p[i] = 0;  // p已经是野指针了

}

d2.p = (int*)calloc(5, sizeof(int));

for(i=0; i<10; i++)

{

d2.p[i] = i; // 超出申请空间,改写了申请空间后5个字节,会导致逻辑混乱

}

free(d2.p);

return 0;

}

# 内存分配成功,但并未初始化

#include

#include

int main()

{

char* s = (char*)malloc(10);

printf(s); // 内存未初始化

free(s);

return 0;

}

# 内存越界

#include

void f(int a[10])

{

int i = 0;

for(i=0; i<10; i++)

{

a[i] = i; // 数组越界

printf("%d\n", a[i]);

}

}

int main()

{

int a[5];

f(a);

return 0;

}

# 内存泄露分析

#include

#include

void f(unsigned int size)

{

int* p = (int*)malloc(size*sizeof(int));

int i = 0;

if( size % 2 != 0 )

{

return; // 返回时没有释放堆空间,所以出现了内存泄露,单入口,多出口造成的问题。

}

for(i=0; i

{

p[i] = i;

printf("%d\n", p[i]);

}

free(p);

}

int main()

{

f(9);

f(10);

return 0;

}

//修改为单入口,单出口;

void f(unsigned int size)

{

int* p = (int*)malloc(size*sizeof(int));

int i = 0;

if( size % 2 = 0 )

{

for(i=0; i

{

p[i] = i;

printf("%d\n", p[i]);

}

}

free(p);

}

# 多次指针释放

#include

#include

void f(int* p, int size)

{

int i = 0;

for(i=0; i

{

p[i] = i;

printf("%d\n", p[i]);

}

free(p); //不正常

}

//养成习惯,谁申请,谁释放。避免bug,避免加班。

int main()

{

int* p = (int*)malloc(5 * sizeof(int));

f(p, 5);

free(p); // 二次释放指针

return 0;

}

# 使用已释放的指针

#include

#include

void f(int* p, int size)

{

int i = 0;

for(i=0; i

{

printf("%d\n", p[i]);

}

free(p);

}

int main()

{

int* p = (int*)malloc(5 * sizeof(int));

int i = 0;

f(p, 5);

for(i=0; i<5; i++)

{

p[i] = i; // 使用已释放的指针

}

return 0;

}

5、C语言中的交通规则

# 用malloc申请了内存之后,应该立即检查指针值是否为NULL,防止使用值为NULL的指针

int *p = (int *)malloc(5 * sizeof(int));

if (p != NULL)

{

// do something here!

}

free(p);

#牢记数组的长度,防止数组越界操作,考虑使用柔性数组

typedef struct _soft_array

{

int len;

int array[];

}softArray;

int i = 0;

softArray *sa = (sottArrray *)malloc(sizeof(softArray) + sizeof(int) * 10);

sa - >len = 10;

for(i=0; ilen; i++)

{

sa->array[i] = i + 1;

}

# 动态申请操作必须和释放操作匹配,防止内存泄露和多次释放

void f()

{

int *p = (int *)malloc(5);

free(p);

}

int main()

{

int *p = (int *)malloc(10);

f();

free(p);

return 0;

}

# free指针之后必须立即赋值为NULL

int *p = (int *)malloc(10);

free(p);

p = NULL;

//...

//...

if(p ! = NULL)

{

int i = 10;

for(i = 0; i < 5; i++)

{

p[i] = i;

}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值