1、动态内存分配
了解原理,熟练使用
- 为什么需要动态分配内存?
例子:求每个部门平均身高
int main()
{
int a[60], n = 0, average = 0;
scanf("%d", &n);
for (int i = 0; i<n; i++)
{
scanf("%d", &a[i]);
average += a[i];
}
average /= n;
}
缺点:事先确定了数组的大小,可能会浪费空间也可能会溢出
嵌入式程序的实际情况:CPU和内存资源有限
1.1 动态分配和静态分配的区别
1.1.1 静态分配
- 在程序编译或运行过程中,按事先规定大小分配内存空间的分配方式,必须事先知道所需空间的大小
- 分配在栈区或全局变量区,一般以数组的形式
- 按计划分配
1.1.2动态分配
- 在程序运行过程中,根据需要大小自由分配所需空间
- 分配在堆区,一般使用特定的函数进行分配
- 按需分配
1.2 常用的动态内存分配函数
1.2.1 malloc
1.2.2 free
1.2.3 malloc和calloc的区别
- 函数的名字不一样
- 参数的个数不一样
- malloc申请的内存,内存中存放的内容是随机的,不确定的, 而calloc函数申请的内存中的内容为0
注意:malloc申请空间后一般需要赋初值再使用
1.2.4 realloc
1.2.5 举例
- 例1
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* ptr = (int*)malloc(40);
*ptr = 20;
free(ptr);
return 0;
}
存在的问题:
万一动态开辟内存失败,malloc会返回空值,那么*ptr就会成为空指针,空指针不可进行解引用操作!
正确做法:
每次动态开辟进行指针接收时,应该加以判断:
若指针为空,表明开辟失败,return ERR;
若开辟成功,则可以进行解引用等其他操作。
- 例2
int main()
{
int* p =(int*)malloc(40);
if (ptr == NULL)
{
printf("%s\n", strerror(errno));
return -1;
}
int i = 0;
for (i = 0; i <= 10; i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
存在的问题:
动态开辟了创建了40字节的空间, 强转为int*表示开辟了40*4=10个整型空间, 通过循环赋值,为10个整型空间赋值1~10, 但多写了一次循环,导致造成空间越界访问。
- 例3
int main
{
int a=10;
int* p=&a;
free(p);
p=NULL;
return 0;
}
存在的问题:
对非动态开辟内存使用free释放。
- 例4
int main()
{
int* p =(int*) malloc(40);
//.......
free(p);
//......
free(p);
p = NULL;
return 0;
}
存在的问题:
对同一块动态内存多次释放
1.3 动态内存分配容易出现的问题
- 对NULL指针进行解引用操作
- 对分配的内存进行操作时越过边界
- 释放并非动态分配的内存
- 试图释放一块动态分配的内存的一部分以及一块内存被释放之后被继续使用
- 对同一块动态内存多次释放
- 忘记释放内存导致内存泄露
1.4 总结
- 对内存相关处理一定要谨慎
- 内存问题往往编译时不报错,程序直接崩溃,甚至开始用的时候没问题运行一段时间后崩溃
2、数据结构(链表)
了解链表的思想,能实现简单链表
2.1 数据结构的定义
由某一数据对象及该对象中所有数据成员之间的关系组成。记为:
Data_Structure = {D, R}
其中,D 是某一数据对象,R 是该对象中所有数据成员之间的关系的有限集合。
2.2 数据结构的基本分类
- 线性结构
直接存取类:数组, 文件
顺序存取类:链表, 栈, 队列, 优先队列
广义索引类:线性索引, 搜索树
- 非线性结构
层次结构类:树,二叉树,堆
群结构类:集合,图
树形结构
2.3 数据存储方式
- 顺序存储结构
顺序存储结构是指用一组地址连续的存贮单元依次存储线性表的元素,通常用数组实现。
- 链式存储结构
在链式存储结构的线性表中,逻辑上相邻的两元素,其物理位置不要求相邻。
2.4 链表
每个元素(表项)由结点(Node)构成。
线性结构
- 结点可以连续,可以不连续存储
- 结点的逻辑顺序与物理顺序可以不一致
- 表可扩充
2.4.1 链表在内存中的存储方式
2.4.2 链表的抽象数据类型描述
定义:同类型数据元素构成有序序列的线性结构。
数据对象集:n个元素构成的有序序列
操作集:线性表L属于List, 整数表示位置,元素x属于ElementType。
操作:
- int Find(ElementType x, List L); 在表中查找X第一次出现的位置
- void insert(ElementType X, int i, List L); 插入
- void Delete(int i, List l); 删除
- int length(List L); 返回长度
-
链表节点定义
typedef struct LNode *PtrToLNode;
struct LNode
{
ElementType Data;
PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;
-
链表节点查找
Position Find( List L, ElementType X )
{
Position p = L; /* p指向L的第1个结点 */
while ( p && p->Data!=X )
p = p->Next;
return p;
}
-
链表节点插入
bool Insert( List L, ElementType X, Position P )
{
Position tmp, pre;
/* 查找P的前一个结点 */
for (pre=L; pre&&pre->Next!=P; pre=pre->Next) ;
if (pre==NULL)
{
/* P所指的结点不在L中 */
printf("插入位置参数错误\n");
return false;
}
else
{
/* 找到了P的前一个结点pre */
/* 在P前插入新结点 */
tmp = (Position)malloc(sizeof(struct LNode));
/* 申请、填装结点 */
tmp->Data = X;
tmp->Next = P;
pre->Next = tmp;
return true;
}
}
在第 i-1 (1≤i≤n+1) 个结点后插入一个值为X的新结点
1. 先构造一个新结点,用s指向;
2. 再找到链表的第 i-1个结点,用p指向;
3. 然后修改指针,插入结点 ( p之后插入新结点是 s)
-
链表节点删除
bool Delete( List L, Position P )
{
Position tmp, pre;
/* 查找P的前一个结点 */
for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ;
if ( pre==NULL || P==NULL)
{
/* P所指的结点不在L中 */
printf("删除位置参数错误\n");
return false;
}
else
{
/* 找到了P的前一个结点pre */
/* 将P位置的结点删除 */
pre->Next = P->Next;
free(P);
return true;
}
}
删除链表的第 i (1≤i≤n) 个位置上的节点
1. 先找到链表的第 i-1个结点,用p指向;
2. 再用指针s指向要被删除的结点(p的下一个结点);
3. 然后修改指针,删除s所指结点; 4. 最后释放s所指结点的空间。
-
更复杂的链表
- 循环链表
- 双向链表
3、代码工具
了解各种工具的优缺点,根据项目和自己的习惯选择工具
3.1 编辑器、编译器和IDE
- 编辑器:代码编辑软件
- 编译器:将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”的程序
- IDE(集成开发环境):是用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面工具