04_静态开辟内存与动态开辟内存

静态开辟内存与动态开辟内存

1. 上节课的内容(函数指针相关)

#include <stdio.h>
void add1(int i, int j) {
    printf("1 i + j = %d\n", i + j);
}
void add2(int i, int j) {
    printf("2 i + j = %d\n", i + j);
}
// 函数,函数指针声明来接收函数
void test(void(*p)(int, int)) {
    p(9, 9); // 上节课 省略*
    (*p)(9, 9);
    (p)(9, 9); // 省略*
    //(&p)(9, 9); 在源码没有看到这种写法,是错误的

    // 思考:p为什么也可以不用写 *
    // 函数的上面已经声明就是函数指针,所以可以省略*
}

int mainT1() {
    test(add1);
    test(add2);
    return 0;
}

1. 如何自己学会阅读C语言API文档.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 1.如何自己学会阅读C语言API文档
/*
int main() {
    // 看文档的思路:
    // 1.看到文档,不要Copy,初学者
    // 2. 传入 &t  &p   可以直接传递 NULL 尝试

    // 时间单位 给他才行
    srand((unsigned) time(NULL));
    int i;
    for (i = 0;  i< 10 ; i++) {
        printf("随机数%d\n", rand() % 100);
    }
    return(0); // return 0;
}
*/

// 字符串的Copy工作
/*函数名: strcpy
        功  能: 串拷贝
        用  法: char *strcpy(char *str1, char *str2);
程序例:*/

#include <stdio.h>
#include <string.h>
/*
int mainT2(void)
{
    char string[10]; // 定义一个数组
    char *str1 = "abcdefghi"; // 字符串定义

    strcpy(string, str1); // 就是把 str1 复制到 数组
    printf("%s\n", string); // 打印数组
    return 0;
}*/

// C中的 布尔
int mainT1() {
    // Java Boolean
    // 非0即true, 0,0.0,-0.0,NULL就是false
    if (0) {
        printf("真\n");
    } else {
        printf("假\n");
    }
    return 0;
}

2. 静态开辟

在这里插入图片描述

#include <stdio.h>
#include <unistd.h>
// 函数进栈 定义一个int arr[5];定义一个 int i;  (静态的范畴)
// 进栈
void staticAction() {
    printf("%s\n", "staticAction");
    int arr[5]; // 静态开辟 栈区 (栈成员)

    for (int i = 0; i <5; ++i) {
        arr[i] = i;
        printf("%d, %p\n", *(arr + i), arr + i);
    }
} // 函数的末尾会弹栈(隐士):执行完毕会弹栈  会释放所有的栈成员

// 2.静态开辟。
int mainT2() {
    // int 4 * 10 = 40M
    // int arr[10 * 1024 * 1024]; // 10M * 4 = 40M  会栈溢出

    // int arr[1 * 1024 * 1024]; 会栈溢出

    int arr[(int)(0.2 * 1024 * 1024)]; //  不会栈溢出

    // 栈区:占用内存大小 最大值: 大概 2M  大于2M会栈溢出  (其大小与平台相关)
    // 堆区:占用内存大小 最大值: 大概80%  40M没有任何问题,基本上不用担心 堆区很大的
    // 大概80%: Windows系统 给我们的编译器给予的空间  的 百分之百八十
    while (9) {
        sleep(1);
        staticAction(); // 调用开辟20
    }
    return (0);
}
栈区

在这里插入图片描述

3. 动态开辟之malloc

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 函数进栈,定义一个int arr[5];定义一个int i;(静态的范畴)
// (malloc)在堆区开辟的内存空间;(动态的范畴)
// C的开发过程中,不能出现野指针和悬空指针
void dynamicAction() {
    int * p;    //野指针,没有地址的,空的. 如何避免野指针?即int * p = NULL.

    //void*   可以任意转变成int*, double*,...
    int * arr = malloc(1 * 1024 * 1024);    //堆区开辟1M

    printf("dynamicAction函数,arr自己的内存地址:%p,堆区开辟的内存地址:%p\n", &arr, arr);

    //C工程师在堆区开辟的空间必须释放.如果不释放那每次堆区开辟的内存地址都不一样.
    free(arr);  //释放掉,只是断开了堆区的引用,但是地址还在.不置空会出现悬空指针.
    //如果只释放了arr但是没有置空,printf("dynamicAction函数 打印出的地址不会重复.
    //反之则会有重复.
    arr = NULL; //重新指向一块内存地址0x00000

    printf("dynamicAction函数2 堆区开辟的内存地址:%p\n", arr);
    //如果没有写arr=NULL这行代码,那么arr将变成悬空指针
}

int mainT3() {
    while (9) {
        sleep(1);
        dynamicAction();
    }
    return 0;
}

4. 动态开辟的使用场景

#include <stdio.h>
#include <stdlib.h>

// 动态开辟的使用场景
int mainT2() {
    // 静态开辟的内存空间大小,是不能修改的,如果不需要动态修改空间大小,当然使用 栈区 【尽量使用 静态开辟的,如果实在是需要动态改变,才使用下面】
    // int arr [6];

    // ===================================  下面是 动态开内存的使用场景
    // 开辟的空间 想要变化,动态范畴

    int num;
    printf("请输入数的个数:");

    // 获取用户输入的值
    scanf("%d", &num);

    // 动态开辟 用户输入的值 空间的大小   【堆区】
    int * arr = malloc(sizeof(int) * num);
    // int arr2 []   ==   int * arr  一样的了

    int print_num;
    // 循环接收
    for (int i = 0; i < num; ++i) {
        printf("请输入第%d个的值:", i);

        // 获取用户输入的值
        scanf("%d", &print_num);

        arr[i] = print_num;    // arr[i] 隐士 等价与 * (arr + i)
        printf("每个元素的值:%d, 每个元素的地址:%p\n", *(arr + i), arr + i);
    }

    // for 循环打印
    for (int i = 0; i < num; ++i) {
        printf("输出元素结果是:%d\n", arr[i]);
    }

    return 0;
}

5. 动态开辟之realloc

在这里插入图片描述
出现异常的时候(内存不够用等),开辟前后的首地址不同。

#include <stdio.h>
#include <stdlib.h>

// 动态开辟之realloc
int main() {

    int num;
    printf("请输入个数");
    // 获取用户输入的值
    scanf("%d", &num);

    // 5个值
    int * arr = (int *) malloc(sizeof(int) * num);
    for (int i = 0; i < num; ++i) {
        arr[i] = (i + 10001); // arr[i]的内部隐士 == *(arr+i)
    }
    printf("开辟的内存指针: %p\n", arr);

    // 打印 内容
    for (int i = 0; i < num; ++i) {
        // Leo装B的打印
        // &取出内存地址 *然后取值
        // &取出内存地址 *然后取值
        // .....
        printf("元素的值:%d, 元素的地址:%p\n",
               *&*&*&*&*&*&*&*&
                       *&*(arr + i)
               ,
               (arr + i)
        );
    }

    // =================================   在堆区开辟新的空间(malloc)  加长空间大小(realloc)

    // C的岗位
    // C工程师的面试题:
    // realloc 为什么一定要传入arr指针(防止出现意外的时候方便copy)
    //         为什么要传总大小(防止出现意外的时候有总大小可以算出所需内存的总的长度)

    // 新增
    int new_num;
    printf("请输入新增加的个数");
    scanf("%d", &new_num);

    // 原来的大小4 + 新增加的大小4  =  总大小 8
    // void *realloc (void *前面开辟的指针, size_t总大小);
    int * new_arr = (int *) realloc(arr, sizeof(int) * (num + new_num));

    if (new_arr) { // new_arr != NULL 我才进if  【非0即true】
        int j = num; // 4开始
        for (; j < (num + new_num); j++) { // 5 6 7 8
            arr[j] = (j + 10001);
        }

        printf("新 开辟的内存指针: %p\n", new_arr);

        // 后 打印 内容
        for (int i = 0; i < (num + new_num); ++i) {

            printf("新 元素的值:%d, 元素的地址:%p\n",
                   *(arr + i),
                   (arr + i)
            );
        }
    }

    // 我已经释放
    free(new_arr);
    new_arr = NULL;

    // 1000行代码
    // ...

    //  重复释放/重复free VS会奔溃,   CLion会优化(发现不奔溃)   [错误的写法]
    /*free(new_arr);
    new_arr = NULL;*/

    // 必须释放【规则】
    /*if (arr) {
        free(arr);   // 如果不赋值给NULL,就是悬空指针了
        arr = NULL;
    }*/

    if (new_arr) {
        free(new_arr);   // 如果不赋值给NULL,就是悬空指针了
        new_arr = NULL;
    }

    return 0;
}
  1. 静态开辟内存申请的是栈区的,动态开辟申请的是堆区的。
  2. realloc返回null 后,原来的数组没有扩容成功。
  3. 野指针不是没有初始化, 悬空指针是指向的地址被释放掉了没有置为NULL。
  4. malloc的地址比如1M 再realloc1M 内存,这两个地址一定是连续的。
  5. 如果malloc 的内存后面的物理地址被占了,再realloc会返回新的地址。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

左绍骏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值