C语言动态内存分配+柔性数组处理+数据类型


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

内存的具体存储

  1.      计算机内部的程序的内存相互独立(A不可访问B)
    

内存2区(B)
内存1区(A)

2.栈区:存储临时变量(当变量超出当前作用域会弹出)

3.堆区:开辟大的内存空间(对动态内存进行分配)————开发人员分配,使用完都释放

4.数据区:存放全局变量,常量,静态变量

5.代码区:存储可执行代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太一TT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值