二十九.动态内存分配
为什么使用动态内存分配:c语言中的一切都是基于内存的,变量和数组都是内存的别名,如何分配这些内存由编译器在编译期 间决定,如果需要一些额外的内存空间,就需要动态分配
malloc所分配的是一块连续的内存,以字节为单位,并且不带任何的类型信息;free用于将动态内存归还系统
void* malloc(size_t size);
void free(void* pointer);
注意:malloc实际分配的内存可能会比请求的稍微多一点,但不能依赖编译器这个行为
当请求的动态内存无法满足时malloc返回NULL,所以一定要记得检查返回值是否为NULL
当free的参数为NULL时,函数直接返回
typedef struct data_type{
int age;
char name[20];
} data;
data *bob;
bob = (data *) malloc( sizeof(data) );
if( bob != NULL ) {// 检查返回值
bob->age = 22;
strcpy( bob->name, "Robert" );
printf( "%s is %d years old\n", bob->name, bob->age );
}else{
printf("malloc error!\n");
exit(1);
}
free( bob );
bob = NULL;
calloc和realloc:void* calloc(size_t num,size_t size);void* realloc(void* pointer,size_t new_size);
calloc的参数代表返回内存的类型信息,会将返回的内存初始化为0
realloc用于修改一个原先已经分配的内存块大小,当pointer为NULL时等价于malloc
小结:malloc单纯地从系统申请固定字节大小的内存
calloc能以类型大小为单位申请固定字节大小的内存
realloc用于重制内存大小
三十.程序中的堆栈和静态存储
栈在程序中用于维护函数调用上下文,没有栈就没有函数,没有局部变量,先进后出,压入栈
栈保存了一个函数调用所需的维护信息:函数参数,函数返回地址,局部变量,函数调用上下文
堆是程序中一块巨大的内存空间,可由程序自由使用,堆中被程序申请的内存在程序主动释放前将一直有效
静态存储区:1.随着程序运行而分配空间,直到程序运行结束
2.在编译期静态存储区的大小就确定了
3.主要用于保存程序中的全局变量和静态变量
4.与堆和栈不同静态存储区的信息最终会保存到可执行程序中
小结:1.栈,堆和静态存储区是c语言程序经常设计的三个基本存区
2.栈区主要用于函数调用的使用
3.堆区主要是用于内存的动态申请和归还
4.静态存储区用于保存全局变量和静态变量
三十一.程序的内存布局
文件布局在内存中的映射:文件布局 文件在内存运行后的布局
各个段的作用:
堆栈段:在程序运行后才正式存在,是程序运行的基础
.bss段存放的是未初始化的全局变量和静态变量
.text段存放的是程序中的可执行代码
.data段保存的是那些已经初始化了的全局变量和静态变量
.rodata段存放程序中的常量值,如字符串常量 //修改会产生段错误
程序术语对应关系:
静态存储区通常指程序中的.bss和.data段
只读区通常指程序中的.rodata段
局部变量所占空间为栈上空间
动态空间为堆中的空间
程序可执行代码存放于.text段
函数的地址对应程序的哪个段??代码段
三十二.野指针和内存操作分析
野指针:1.野指针通常是因为指针变量中保存的值不是一个合法的内存地址而造成的
2.野指针不是null指针,是指向不可用内存的指针
3.NULL指针不容易用错,因为if语句很好判断一直指针是不是NULL
注意:c语言中没有任何手段可以判断一个指针是否为野指针
野指针的由来:
1.局部指针变量没有被初始化
#include <stdio.h>
#include <string.h>
struct Student
{
char* name;
int number;
};
int main()
{
struct Student s;
strcpy(s.name, "Delphi Tang"); // OOPS!
s.number = 99;
return 0;
}
输出:程序崩溃或者不确定结果
2.使用已经释放过后的指针
#include <stdio.h>
#include <malloc.h>
#include <string.h>
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); // OOPS!
return 0;
}
3.指针指向的变量在指针之前被销毁
#include <stdio.h>
char* func()
{
char p[] = "Delphi Tang";//局部变量字符串常量在栈里面
return p;
}
int main()
{
char* s = func();
printf("%s\n", s); // OOPS!
return 0;
}
1.非法内存操作分析:
结构体成员指针未初始化
没有为结构体指针分配足够的内存
#include <stdio.h>
#include <malloc.h>
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; // OOPS!
}
d2.p = (int*)calloc(5, sizeof(int));
for(i=0; i<10; i++)
{
d2.p[i] = i; // OOPS!会修改变量的值,恶劣的错误
}
free(d2.p);
return 0;
}
2.内存初始化分析 : 内存分配成功,但并未初始化
#include <stdio.h>
#include <malloc.h>
int main()
{
char* s = (char*)malloc(10);
printf(s); // OOPS!
free(s);
return 0;
}
3.内存越界分析:数组越界
#include <stdio.h>
void f(int a[10])
{
int i = 0;
for(i=0; i<10; i++)
{
a[i] = i; // OOPS!
printf("%d\n", a[i]);
}
}
int main()
{
int a[5];
f(a);
return 0;
}
4.内存泄露分析:
#include <stdio.h>
#include <malloc.h>
void f(unsigned int size)
{
int* p = (int*)malloc(size*sizeof(int));
int i = 0;
if( size % 2 != 0 )
{
return; // OOPS!
}
for(i=0; i<size; i++)
{
p[i] = i;
printf("%d\n", p[i]);
}
free(p);
}
int main()
{
f(9);
f(10);
return 0;
}
5.多次释放指针
#include <stdio.h>
#include <malloc.h>
void f(int* p, int size)
{
int i = 0;
for(i=0; i<size; i++)
{
p[i] = i;
printf("%d\n", p[i]);
}
free(p);//已经释放
}
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
f(p, 5);
free(p); // OOPS!
return 0;
}
6.使用已释放的内存
#include <stdio.h>
#include <malloc.h>
void f(int* p, int size)
{
int i = 0;
for(i=0; i<size; 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; // OOPS!
}
return 0;
}
C语言中的规则:
1.用malloc申请内存后,应该立即检查指针值是否为NULL,防止使用值为NULL的指针
int* p = (int*)malloc(5 * sizeof(int));
if( p != NULL)
{
//do something
}
free(p)
2.牢记数组的长度,放置数组越界操作,考虑使用柔性数组
3.动态申请操作必须和释放操作匹配,防止内存泄露和多次释放
4.free指针后必须立即赋值为NULL