爬爬爬之路:C语言(十) 动态内存分配

前言

C语言内存分为5个区域:
按照地址顺序排序如下:
高(地址编号大):
↑栈区:  程序运行的空间
↑堆区:  程序员手动管理的区域
↑静态区
↑常量区
↑代码区
低(地址编号小):


代码区

这个区域主要存放函数体的二进制代码。


常量区

常量区存储的特点:

常量区中的值是不可以被改变的, 如代码:

    char *str = "zhuang";
    *(str + 1) = 'w’;
    printf("%s", str);

结果在运行到*(str + 1) = 'w'; 这句代码的时候报错了,原因是因为字符串”zhuang”是常量, 它的值是保存在常量区中, 是不可以被改变的, *(str + 1) 访问的是常量区的地址, 所以改值失败.

str = "haha";
printf("%s", str);

结果输出了”haha”. 改值成功, 原因是指针str重指向了值为”haha”的常量

再看一个对比例子

char string[] = "zhuang";
string[1] = 'w';
printf("%s", string);

结果成功打印zwuang
原因是因为表达式 char string[] 在栈中声明了一个字符数组变量, 然后把常量区里的常数字符串”zhuang”拷贝到字符数组的空间里, 这是string[1]访问的是在栈区中的地址, 保存在栈区里的值是可以更改的.
但是得注意的是:
string = "haha"
像这样直接通过数组名让数组地址指向常量字符串”haha”的地址是不行的, 数组名是数组元素的首元素空间的地址, 它是一个常量, 所以它是不能进行重指向的. 这是数组名和指针变量不同的地方.


静态区

静态区的特点:

  1. 如果不给初值, 存放在静态区的变量的初值被默认为0(不代表值在以后不能被改变)
  2. 只能初始化一次

静态区里存储两种变量

  • 静态变量

  • 静态变量
    利用关键字static修饰的变量, 叫做静态变量, 并且存储在静态区
    比如static int number1 = 10;

  • 静态变量与普通变量的差别:

    void function(){
          int i = 10;
          i ++;
          printf(“%d\n”, i);
    }
    // 在main函数中调用3次
    int main(){
        function();
        function();
        function();
    }

    结果显示为 11 11 11.
    若函数改写成

    void function(){
          static int i = 10;
          i ++;
          printf(“%d\n”, i);
     }

    此时打印的结果为11 12 13
    原因是静态变量只初始化一次, 第二次及第三次调用函数的时候初始化语句 static int i = 10 不再执行.

  • 根据静态变量声明的地方, 分为全局静态变量, 和局部静态变量
    静态变量的作用域与普通变量的作用域相同.同样是在同个大括号内, 且在声明后的地区可见

  • 全局变量

  • 全局变量也是保存在静态区的
    全局变量一般是在所有函数(包括main函数)之外, #import 语句后声明的变量

静态区的保存周期:

  • 直到程序运行结束 静态变量才被释放
  • 静态变量和普通变量的作用域相同
  • 静态变量的生命域和普通全局变量生命域相同, 都是整个程序运行期间
  • 程序结束(包括正常退出 和闪退)时, 静态变量才会被释放

栈区
  • 栈区大小: 大约7MB - 8MB之间 (1024*1024*7~1024*1024*8)
  • 函数中的变量都保存在栈区
  • 栈区的内存执行原则:

    1. 出栈入(压)栈原则: 先进后出
    2. 出栈的顺序: 从栈顶开始出栈

      void function(){
          int a = 5;
          int b = 6;
          printf("%d, %d\n", a, b);
      }
      void function2(){
          int a = 5;
          int b = 6;
      printf("%d, %d\n", a, b);
      }
      
      int main(int argc, const char * argv[]) {
          int num1 = 5;
          int num2 = 10;
          int num3 = 15;
          printf("%p\n", &num1);
          printf("%p\n", &num2);
          printf("%p\n", &num3);
      
          function();
          function2(); 
          return 0;
      }

      出栈入栈的顺序如下:
      最左代表栈底, 最右代表栈顶(省略入栈操作, 主要观察出栈操作)
      1.num1 num2 num3 function()
      2.num1 num2 num3 a b
      3.num1 num2 num3 a
      4.num1 num2 num3
      5.num1 num2 num3 function()
      6.num1 num2 num3 a b
      7.num1 num2 num3 a
      8.num1 num2 num3
      9.num1 num2
      10.num1
      11.NULL
      之所以栈区容量不是很大甚至很小, 但是又不会出现崩溃的现象, 因为栈区频繁的进行出栈和入栈的操作, 只要一次性不把栈区堆满, 就不会轻易的出现崩溃

    3. 关于变量不给初值的问题:

      void function(){
      
          int a = 5;
          printf(“%d,", a);
      }
      void function2(){
          int temp;
          printf(“%d", temp);
      }
      int main(){
          function();
          function2();
          return 0;
      }

      结果打印出了5,5
        原因是因为main函数中调用了函数function(); 变量a入栈, 为变量a所在空间赋值为5, 调用结束后, 变量a出栈, 但此时值空间没有被释放, 因为变量空间指向值空间的指针也没有被释放. 在下一次调用函数function()的时候, 变量temp入栈, 重新占据了原来变量a的空间, 此时这个空间所指向的值空间的指针依然存在, 既值为常量5. 因为此时temp没有赋初值, 于是这个变量的值依然指向了常量5. 导致的结果就是在temp未赋值的时候就已经指向了未知来源的(其实我们知道在这里是来自于变量a)值.
        所以建议大家在定义一个变量的时候, 需要赋一个初值, 以防这种不明AOE.


堆区
  • 堆区是程序员手动开辟和释放操作的空间
  • 堆区的空间大小, 大概就是内存全部的空间

1.开辟空间函数: malloc

  • void *malloc(size_t);
    void* 表示返回值是无类型指针, 可以转化成任何类型的指针
    参数size_t 表示开辟空间的大小(或者说开辟多少字节的空间, 为整型数字)
    调用方法为:
    int *p = malloc(4);
    意为:  给整型指针p 指向的位置开辟了4个字节大小的堆内存空间

    这里要注意: 开辟空间大小和指针的类型所占空间. 如果开辟的空间太小, 会导致放置的数据太大太多而越界, 这会造成多余出来的这块空间无法被释放.
    最好开辟多少空间就用多少空间, 否则在没有free之前会浪费内存, 或者free之后溢出的空间内存没有被释放, 这都会导致大型工程时可能导致内存不够用的问题

  • 释放内存

    1. 标记删除:
      通过调用方法free方法
      void free(void *);
      p.s 删除只是对这块内存进行标记, 标记这块内存空间可以被覆盖使用, 事实上内存里的东西还在(根据目前的技术可言, 不完全统计, 10次以上的重复利用 才可能会无法恢复第一次写进内存的数据)
    2. 把指针置为空
  • 注意事项:

    int *p = malloc(sizeof(int) * 5);
    for(int i = 0 ;i < 5 ; i++) {
         *p = i + 1;
         p++;
    }
    free(p);

      如本段代码, 指针p随着for循环的进行而更改. 在调用free方法时, p已经不再是初始的地址值了, 这时候会导致程序崩溃(系统回收新地址往后的未被声明开辟的20个字节空间, 导致回收错误).
      结论:指针变量保存的地址发生了变化(重指向), 释放了不属于程序员开辟的区域时, 会导致程序崩溃. 为了避免该错误, 应将代码改为:
      

    int *p = malloc(sizeof(int) * 5);
    for(int i = 0 ;i < 5 ; i++) {
        *(p + i) = i + 1;
    }
    free(p);
    p = NULL;

    或者

    int *p = malloc(sizeof(int) * 5);
    for(int i = 0 ;i < 5 ; i++) {
         *p = i + 1;
         p++;
    }
    for(int i = 0 ;i < 5 ; i++) {
         p--;
    }
    free(p);
    p = NULL;

2.其余的分配空间函数:

  • calloc函数

    void *calloc(size_t, size_t);
    第一个参数为开辟空间的个数, 后一个参数为开辟空间的大小.

    1. 功能: 分配了指定个数 指定大小的空间, 并且把该内存上的所有字节清零
    2. 注意事项: 由于本函数有把内存清零的功能, 所以在时间效率上会比malloc差
    3. 释放内存方法同malloc
  • realloc函数
    void *realloc(void *, size_t);
    重新分配空间函数
    第一个参数是原来已分配空间的地址, 第二个参数是新分配空间的大小

    1. 情况1:
      如果原来分配的地址, 可以扩充空间, 那么就再原地址扩充

    2. 情况2:
      如果原来分配的地址, 不能继续扩充了, 那么系统会重新分配一个kongijan, 并且把原来地址储存的数据拷贝到新空间里, 然后系统自动释放原来地址的空间

    3. 使用场景:
      在malloc后重新
      释放内存 释放新的地址.


内存操作函数
  • memset方法
    void *memset(void *, int, size_t);
    第一个参数是已开辟空间的指针, 中间是想要重置成的整数, 最后一个是字节数

    1. 作用:
      把开辟的空间 size_t个字节都重置为预期希望重置成为的数.
    2. 用途:
      通常用来把开辟好的空间清零
      和malloc合用, 可以达到calloc相同的效果(清零的效果)

      若不用于清零时:

      int *p = malloc(4*4);
      memset(p, 1, 2);
      for (int i = 0; i < 4; i++) {
          printf("%d ", *(p + i));
      }

      得到的效果是 257 0 0 0
      原因memset(p, 1, 2)函数把地址p指向的地址开始的两个字节, 赋值为1 而int型变量是读取4个字节的, 00000001 00000001 被int型读取为257

  • memcpy方法
    void *memcpy(void *, const void *, size_t);
    第一个参数是目的地的地址, 第二个参数是来源地的地址, 第三个参数是字节数
    作用: 从来源地 拷贝到目的地 拷贝内容为 size_t个字节长度内的值

    char str1[] = "hello Word";
    char str2[] = "55";
    memcpy(str1, "W", 1);
    memcpy(str1 + 3, str2, 2);
    printf("%s", str1);

    结果为 Wel55 Word

  • memcmp方法
    int memcmp(const void *, const void *, size_t);
    第一个参数是比较地址1, 第二个参数是比较地址2, 第三个参数是需要比较的字节数
    原理: 两个地址, 按字节进行比较 返回第一个不同的差值
    类似于字符串比较函数strcmp();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值