1.使用内存的时候,可以创建一个变量或者创建数组,局部的属于栈区,全局的属于静态区。
2.栈区放置局部变量和函数的形式参数, 堆区进行动态内存分配,静态区放置全局变量和静态变量。
基本函数的理解
malloc
void*malloc(size_t size)
//向内存中申请size个字节,申请成功后返回地址,失败的话返回空指针
//打印失败strerror
(1)这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
(2)返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
#include<stdlib.h>
#include<string.h>
#include<error.h>
int main()
{
//向内存中申请10个整型的空间
int *p=(*int)malloc(10*sizeof(int));
//把*void强制转换为*int
if(p==NULL)
{
printf("%s\n",strerror(error);
//把错误码转换为错误信息后打印
}
else{
//正常使用空间
int i=0;
for(i=0;i<10;i++)
*(p+i)=i;
//当申请完的内存空间不再使用的时候用free来释放空间
free(p);
p=NULL;
//p只free了相当于分手之后还留着电话号码,通过电话号码还可以给人家打电话,但是变成空号了就不行了
return 0;
}
free
对动态内存空间进行回收和释放
(1)如果参数ptr指向的空间不是动态内存开辟的,那么free的行为是未曾定义的。
(2)如果参数是NULL,则函数什么都不做。
calloc
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
int main()
{
int *p=(int*)calloc(10,sizeof(int));
if(p==NULL)
{
printf("%s",strerror(error));
}
else{
int i=0;
for(i=0;i<10;i++)
{
printf("%d",*(p+i));
}
return 0;
free(p);
p=NULL;
}
realloc
对动态开辟的内存大小进行调整
如果原有空间后面的空间足够用,直接追加上再返回原有的地址。
如果不够用,就再开辟一块新空间满足需求,并且把原来内存的数据拷贝回来,释放旧的内存空间,最后返回新开辟的内存空间地址.得用一个新的变量来接受realloc的返回值
void*realloc(void*ptr,size-t size)
//ptr是要调整的内存地址 size是调整之后的新大小
//返回值为内存调整之后的起始位置
//这个函数调整原内存空间大小的基础上,还会把原来内存中的数据移到新的空间
实操案例:
int main()
{
int *p=(int*)malloc(20);
if(p==NULL)
{
printf("%s",strerror(errno));
}
else{
int i=0;
for(i=0;i<5;i++)
{
*(p+i)=i;
}
}
//就是在使用malloc开辟的20个空间 到这里发现不够用了 我们希望有一个40个字节的空间 这里就能用realloc来调整动态开辟的内存
int *ptr=realloc(p,INT_MAX);
if(ptr!=NULL)
{
p=ptr;
int i=0;
for(i=5;i<10;i++)
{
*(p+i)=i;
}
for(i=0;i<10;i++)
{
printf("%d",*(p+i));
}
free(p);
p=NULL;
}
使用的误点
//对NULL指针的解引用操作
int main()
{
int *p=malloc(40);
*p=10;//malloc开辟的空间失败,对NULL进行解引用操作
}
//对动态开辟的内存空间越界访问
void text()
{
int i;
int *p=(int*)malloc(10*sizeof(int));
if(NULL==p)
{
exit(EXIT_FAILURE);
}
for(i=0;i<=10;i++)
{
*(p+i)=i;//对数组越界访问
}
}
3.
//对非动态开辟的内存使用free释放
{
int a=10;
int*p=&a;
free(p);
p=NULL;
}
#include<stdlib.h>
#include<stdio.h>
//使用free释放动态内存的一部分
int main()
{
int *p=(int*)malloc(40);
if(p==NULL)
{
return 0;
}
int i=0;
for(i=0;i<10;i++)
{
*p++=i;
}
free(p);
p=NULL;//由于p在不断变化,已经不是指向malloc刚刚第一次开辟的空间了
5.
//动态开辟的空间忘记释放(内存泄漏)
#include<windows.h>
#include<stdio.h>
int main()
{
while(1)
{
malloc(1);
sleep(1000);
}
}
经典笔试题
题目一:
int main()
{
Test();
return 0;
}
void Test(void)
{
char *str=NULL;
GetMemory(str);
//str传的是值 传自己过去是传值 传地址过去是传址
strcmp(str,"hello world");
printf(str);//写法没问题
}
void GetMemory(char*p)//接收指针变量的地址得用二级指针
{
p=(char*)malloc(100);
}//运行的结果是什么,bug有什么
1.开辟空间后,p里面放的是开辟空间的首地址。
p是形参变量,在函数内部使用完以后返回主函数就消失了。当strcmp的时候把hello world传到空指针,使得程序崩溃了。
2.程序存在内存泄漏问题。 str以值传递的形式给了p.
p是GetMemory的一个形参,只有在函数内部有效。
等GetMemory函数返回之后,动态开辟内存尚未释放会造成内存释放。
修改:
int main()
{
Test();
return 0;
}
void Test(void)
{
char *str=NULL;
GetMemory(*str);
strcmp(str,"hello world");
printf(str);
free(str);
str=NULL;
}
void GetMemory(char**p)
{
*p=(char*)malloc(100);
}
题目二:返回栈空间地址的问题
int main()
{
Text();
return 0;
}
void Text(void)
{
char*str=NULL;
str=GetMemory();
printf(str);
}
char* GetMemory(void)
{
char p[]="Hello world";
return p;
)
p[]是局部数组,在函数内部存在,倒是也把p返回来了。
但是当使用str的时候这个p已经被销毁了。会存在内存的非法访问问题。
题目三:
int*text()
{
int a=10;//保存在栈区之中
//static int a=20;保存在静态区就可以了
//堆区开辟一块空间也行,只有当free的时候才会被回收
return &a;//出栈的时候a被销毁
}
int main()
{
int *p=text();
*p=20;
return 0;
}
题目四:
int *f2(void)
{
int *ptr;
*ptr=10;
return ptr;
}
ptr没有赋值,是一块随机值,当解引用之后存在非法访问的问题。
题目五:
int main()
{
test();
return 0;
}
void Text()
{
char*str=(char*)malloc(100);
strcmp(str,"hello");
free(str);
//释放内存后仍然非法访问并且使用
//free释放str指向的空间之后,并不会把str置为NULL
//篡改动态内存区的内容,后果难以预测,非常危险
//因为free(str)之后,str成为野指针,if(str!=NULL)不起作用
if(str!=NULL)
{
strcmp(str,"world");
printf(str);
}
}
柔性数组
C99中结构体中的最后一个元素允许是未知大小的数组,是柔性数组的成员。
开辟方式一:
struct s{
int n;
int a[];
//未知大小柔性数组成员—柔性数组——数组的大小是可以改变的 或者写成int a[0];
};
int main()
{
struct s S;
//计算包含柔性数组的结构体的大小的时候不包含柔性数组的大小
struct S* ps=(struct S*)malloc(sizeof(struct)+5*sizeof(int));//开辟了24个字节
ps->n=100;
int i=0;
for(i=0;i<5;i++)
{
ps->arr[i]=i;//0 1 2 3 4
}
struct S*ptr=realloc(ps,44);//用realloc来调整数组的大小
if(ptr!=NULL)
{
ps=ptr;
}//查空
for(i=5;i<10;i++)
{
ps->arr[i]=i;
}
for(i=0;i<10;i++)
{
printf("%d",ps->arr[i])
}
free(ptr);
return 0;
}
开辟方式二:
struct S{
int n;
int*arr;
};
int main()
{
struct S*PS=(struct S*)malloc(sizeof(struct S));
//ps开辟了一块空间,有n和arr
ps->arr=malloc(5*sizeof(int));//arr又指向一块空间
int i=0;
for(i=0;i<5;i++)
{
ps->arr[i]=i;
}
//调整空间大小
int *ptr=realloc(ps->ptr,40);
if(ptr!=NULL)
{
ps->arr=ptr;
}
for(i=0;i<10;i++)
{
arr[i]=i;
}
//释放
free(ps->arr);
ps->arr=NULL;
free(ps);
ps=NULL;
return 0;
}
柔性数组的好处:
(1)第二个方式使用俩次malloc释放内存的时候更容易出错
(2)malloc开辟空间的时候,可能会有一些地方没有被开辟,内存碎片较多。柔性数组的内存碎片更少,内存利用率更高啦!
(3)柔性数组的开辟空间的内存是连续的,访问的效率会更高。
寄存器,cache——高速缓存,内存,硬盘。 越往上速度越快,空间越小,造价越高。从寄存器里面拿数据,更快一些。
局部性原理:当你访问内存中的数据的时候,接下来百分之八十的可能性访问的是它周边的数据。
柔性数组的特点
1.结构中的柔性数组成员前面一定要有一个其他成员
2.sizeof返回的这种结构大小不包括柔性数组的内存
3.包含柔性数组成员的结构用配malloc()函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,来适应柔性数组的预期大小。
数据类型
c语言类型==内置类型+自定义类型(构造类型)
==整型+浮点型+构造+数组类型**int arr[10] 类型int [10]**
结构体类型 枚举类型 联合类型)+ 指针 +空类型
类型一来决定了这个类型开辟的空间大小,一来决定了如何看待内存空间的视角
整型存储
整型存储 int a=-10;
内存中存二进制补码:1111 1111 1111 1111 1111 1111 1111 0110
转换为16进制 ff ff ff f6
unsigned char(0----2^8-1) (-127----128)
大端小端:
大端:数据的高位存在低地址中,低位存在高地址中
小端:数据的低位存在高地址中,高位存在低地址中
低地址————————>高地址
11 22 33 44(大端 44 33 22 11(小端
指针类型解引用决定了指针解引用操作可以访问几个字节
决定了+1或-1加的是几个字节
int main()
{ int a=1;
char*p=(char*)&a;
if(*p==1)
{
printf("小端");
}
else printf("大端");
return 0;
}
习题
无符号数>=0 ,所以下面的程序进入死循环。
int main()
{
unsigned int i;
for(i=9;i>=0;i--)
{ printf("%u",i)}
}
i== -1 -2 -3… -128 ----->127 3 2 1 0
字符串长度 (到0之前)(127+128=255)
int main()
{
char a[1000];
int i;
for(i=0;i<1000;i++)
{
a[i]=-1-i;
}
printf("%d",strlen(a));
}
浮点型在内存中的存储
int main()
{
int n=9;
float *pFloat=(float*)&n;
printf("%d",n);
printf("%f",*pFloat);
*pFloat=9.0;
printf("%d",n);
printf("%f",*pFloat);
}
结果为9 0.000000 1091567616 9.000000
内存的具体存储
-
计算机内部的程序的内存相互独立(A不可访问B)
内存2区(B)
内存1区(A)
2.栈区:存储临时变量(当变量超出当前作用域会弹出)
3.堆区:开辟大的内存空间(对动态内存进行分配)————开发人员分配,使用完都释放
4.数据区:存放全局变量,常量,静态变量
5.代码区:存储可执行代码