嵌入式团队培训


动态分配内存

一、思维导图

二、为什么需要动态分配内存

C语言的各种操作都是基于内存的。变量、数组都是内存的别名。 拿数组举例,程序所需要的内存在编译期间就已经被决定,所以定义数组时需要定义长度,以便于编译器在编译期给程序分配足够的内存。但并不是每次都能确定数组的长度到底要定为多少,或者前期定好了,后期有需要一些额外的空间时,定长数组就会带来问题。所以就需要用到动态内存分配的支持。

三、图解

四、相关函数

头文件

想要使用动态内存分配需要<stdlib.h>标准库的支持

相关函数

1、malloc()函数
该函数会向操作系统请求内存块,并返回内存块的首地址。可以用一个指针变量保存这个地址。

代码及结果

int main() {
    int i;
    int *testArray1 = (int *)malloc(5 * sizeof(int)); //用malloc()函数分配内存
    if (testArray1 == NULL) {
        exit(-1); //内存分配失败,退出程序
    }
    for(i=0; i<5; i++) {
        printf("Array1[%d] = %d\n", i, testArray1[i]);
    }
    free(testArray1);//释放内存

    return 0;
}

2、calloc()函数
该函数与malloc函数大体相同,calloc函数接受两个参数,单元数量和单元大小,并且内存块中的值为0

代码及结果

int main() {
    int i;
    int *testArray2 = (int *)calloc(5, sizeof(int)); //用calloc()函数分配内存
    if (testArray2 == NULL) {
        exit(-1); //内存分配失败,退出程序
    }
    for(i=0; i<5; i++) {
        printf("Array2[%d] = %d\n", i, testArray2[i]);
    }
    free(testArray2);//释放内存

    return 0;
}

3、realloc()函数
用于修改一个原先已经分配的内存块大小

代码及结果

int main() {
        int i;
        int *testArray1 = (int *)malloc(5 * sizeof(int)); //用malloc()函数分配内存
        if (testArray1 == NULL) {
            exit(-1); //内存分配失败,退出程序
        }

        int *arrayNew = (int *)realloc(testArray1, 5 * sizeof(int));//用realloc()函数给arrayNew增加内存空间
        if (arrayNew == NULL) {
            exit(-1); //内存分配失败,退出程序
        } else {
            testArray1 = arrayNew; //将增添了内存的数组返回给testArray1
        }
             for(i=0; i<10; i++) {
            printf("Array1[%d] = %d\n", i, testArray1[i]);
        }

        free(testArray1);//释放内存

        return 0;
    }
  

4、free()函数
用于释放已分配的内存,将动态内存归还系统

注意事项

1、malloc实际分配的内存可能会比请求的稍多一些,但是不能依赖此行为来分配内存。必须按需求分配需要的内存。

2、当请求分配内存时,如果内存无法满足需要时,会返回NULL。所以在动态分配内存时需要加以判断。

3、使用动态分配内存函数分配的内存在程序结束之前必须释放(调用free()函数)。

4、用realloc函数增添数组内存地址时,要将重新分配的内存地址返回给新地址,然后再该地址付给需要增添内存的数组的地址

为什么一般更常使用malloc()函数

因为calloc虽然对内存进行了初始化(全部初始化为0),但是同样也要降低效率的
calloc相当于

p = malloc();
memset(p, 0,size);

多了对内存的写零操作,而写零这个操作我们有时候需要,而大部分时间不需要。

五、内存泄漏

什么是内存泄漏

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

什么情况可能会发生内存泄漏

1、动态分配的内存在使用后没有即时释放(没有被free)

2、为指针变量分配了一个内存,然后又让指针变量指向其他的值,导致泄漏

3、用realloc函数给已有数组直接增添内存地址,但内存不足时,realloc函数返回值为NULL,导致该数组指向丢失,内存泄漏

结构体

一、思维导图

二、为什么需要结构体

在实际的编程过程中,我们往往还需要一组类型不同的数据,例如对于学生信息登记表,姓名为字符串,年龄为整数,性别为字符。因为数据类型不同,显然不能用一个数组来存放。这时就需要要用到结构体了。

三、如何定义结构体

一般定义形式

struct 结构体名{
结构体所包含的变量或数组
};

结构体是一种集合,他可以位于块的内部,也可以位于块的外部,区别是他们的作用区间不同。它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员。请看下面的一个例子:

struct Student {
char *name; //学生姓名
int age; //学生年龄
char sex; //学生性别
}; //不要忘记分号

此处便定义了一个可以表示学生信息的结构体,该结构体中可以保存学生的姓名、年龄还有性别信息。

其他定义形式

第一种:在创建结构体时就定义该结构体的变量

struct Student {
char *name; //学生姓名
int age; //学生年龄
char sex; //学生性别
}student1, student2; //此处便有了Student类型的两个变量,student1和student2

此种方法等同于:

struct Student {
char *name; //学生姓名
int age; //学生年龄
char sex; //学生性别
};

struct Student student1;
struct Student student2;

第二种:使用typedef类型定义关键字

typedef struct Student {
char *name; //学生姓名
int age; //学生年龄
char sex; //学生性别
}Stu; //此处Stu就相当于struct Student,Stu是其别名,两者等价

为什么要使用typedef:

在定义结构体变量时,可以省去struct关键字,直接使用别名便可以定义变量。

struct Student student1;
Stu student2; //student1和student2的类型是一样的

第三种:直接定义结构体变量

struct {
char *name; //学生姓名
int age; //学生年龄
char sex; //学生性别
}student1, student2; //该结构体只有student1和student两个变量 

采用此方法,该结构体只有student1和student2两个变量,无法在其他地方再定义该结构体的新变量。

四、如何使用结构体里的属性

int main() {
Stu student; //定义一个学生变量
student.name = "小明"; //给该学生的姓名赋值
student.age = 10; //给该学生的年龄赋值
student.sex = 'm'; //给该学生的性别赋值

//打印学生信息,在变量名后面加上 .号 便可取出结构体变量中的属性
printf("该学生的的姓名是:%s 年龄是:%d 性别是:%c", student.name, student.age, student.sex);
}

打印结果

五、结构体数组

什么是结构体数组

顾名思义,结构体数组就是相同类型的结构体变量组成的集合,和数组类似。

结构体数组的定义方式

结构体数组的定义方式可以看作是结构体定义和数组定义的结合,在此简单列举一些。

第一种:先定义结构体,再定义结构体数组

struct Student{
    char *name; //学生姓名
    int age; //学生年龄
    char sex; //学生性别
}Stu;

int main() {
    struct Stu students[5]; //定义了一个学生类型的数组
}

第二种:定义结构体的同时定义结构体数组

struct Student{
    char *name; //学生姓名
    int age; //学生年龄
    char sex; //学生性别
}students[5];//和第一种定义结果相同

此方法下,还可以在定义结构体数组时对其进行初始化

struct Student{
    char *name; //学生姓名
    int age; //学生年龄
    char sex; //学生性别
}students[] = {
        //定义的同时初始化数组
        {"小明", 10, 'm'},
        {"小红", 11, 'f'}
};

int main() {
    for(int i=0; i<2; i++) {
        printf("该学生的的姓名是:%s 年龄是:%d 性别是:%c\n", students[i].name, students[i].age, students[i].sex);
    }

}

打印结果

结构体数组的使用方法

借用上面的打印可以看出,结构体数组的使用方法是:结构体数组名[下标].属性

六、结构体指针

什么是结构体指针

当一个指针变量指向结构体时,我们就称它为结构体指针。

如何定义结构体指针

第一种:定义结构体以后再定义结构体指针

结构体也是一种数据类型,只是它是由你自己定义的,所以结构体指针的定义类型和一般数据类型的指针定义方式相同。

struct Student{
    char *name; //学生姓名
    int age; //学生年龄
    char sex; //学生性别
};

int main() {
    struct Student student = {"小明", 10, 'm'}; //定义一个结构体变量
    struct Student *pStudent = &student; //定义一个结构体指针并传入student的地址
}

第二种:使用typedef关键字定义(常用)

typedef struct Student{
    char *name; //学生姓名
    int age; //学生年龄
    char sex; //学生性别
}Stu, *PStu; // PStu相当于struct student * 或者 Stu *

int main() {
    Stu student = {"小明", 10, 'm'}; //定义一个结构体变量
    PStu pStudent = &student; //PStu即为Stu的指针类型
}

如何使用结构体指针中的属性

第一种写法:

typedef struct Student{
    char *name; //学生姓名
    int age; //学生年龄
    char sex; //学生性别
}Stu, *PStu; // PStu相当于struct student * 或者 Stu *

int main() {
    Stu student; //此处不初始化
    PStu pStudent = &student; //PStu即为Stu的指针类型
    //用指针给结构体中的属性赋值
    (*pStudent).name = "小明";
    (*pStudent).age = 10;
    (*pStudent).sex = 'm'; //注意加括号
}

注意事项:因为 . 号的优先级高于 * 号,所以(*pStudent)的括号不能少!

第二种写法:使用 -> 运算符(常用)

typedef struct Student{
    char *name; //学生姓名
    int age; //学生年龄
    char sex; //学生性别
}Stu, *PStu; // PStu相当于struct student * 或者 Stu *

int main() {
    Stu student; //此处不初始化
    PStu pStudent = &student; //PStu即为Stu的指针类型
    //用指针给结构体中的属性赋值
    pStudent->name = "小明";
    pStudent->age = 10;
    pStudent->sex = 'm'; //使用 -> 运算符
}

这也是 -> 运算符在C语言中唯一的用途

01-26 06:06