背景
相信有很多数据结构的初学者在学习链表的时候对于malloc感觉很困惑
我们习惯了用int x来定义一个整形变量,而不是利用malloc来堆里面分配内存定义变量
那为什么要这么写?
可不可以不用mallloc创建?
带着问题我们来实验
可不可以不使用malloc来编写链表
1.cpp
#include <iostream>
struct ListNode
{
int value;
ListNode *Next;
};
int main()
{
ListNode header;
header.value = 1;
ListNode next;
next.value = 2;
header.Next = &next;
printf("%d", header.Next->value);
return 0;
}
这里我们选择用c++单纯是不需要像c一样需要typedef
我们运行一下
可以看到程序正常返回了,说明我们这样确实形成了只有一个元素的链表,那如果需要扩增的话需要如下类似代码
ListNode list_name;
list_name.value=value;
tmp->Next=&list_name;
tmp=tmp->Next;
#include <iostream>
struct ListNode
{
int value;
ListNode *Next;
};
int main()
{
ListNode header;
ListNode *tmp = &header;
ListNode next;
next.value = 1;
tmp->Next = &next;
tmp = tmp->Next;
ListNode next2;
next2.value = 2;
tmp->Next = &next2;
tmp = tmp->Next;
ListNode next3;
next3.value = 3;
tmp->Next = &next3;
tmp = tmp->Next;
tmp->Next = NULL;
tmp = header.Next;
while (tmp)
{
printf("%d", tmp->value);
tmp = tmp->Next;
}
return 0;
}
可以看到,我们创建了三个节点,但写了这么多代码
这样写很麻烦,可不可以利用for循环创建呢
#include <iostream>
struct ListNode
{
int value;
ListNode *Next;
};
int main()
{
ListNode header;
int n = 10;
ListNode *tmp;
tmp = &header;
for (int i = 0; i < n; i++)
{
ListNode next;
next.value = i;
tmp->Next = &next;
tmp = &next;
}
tmp->Next = NULL;
tmp = header.Next;
while (tmp)
{
printf("%d", tmp->value);
tmp = tmp->Next;
}
return 0;
}
我们本来希望这个代码执行循环的时候可以不断创建新的对象,但发现他始终只有一个节点,我们可以通过看for循环里的链表的地址来证明
这里涉及了for循环内部定义变量内存分配问题,下面我们来调试一下
很基本的代码,下面开始调试
可以看见我们在定义这个变量的时候开辟了一个栈的内存区域
然后每一轮赋值都是把eax的值赋值到[rbp-4]里
这就解释了上面实际上只开辟了一个链表空间
这也就是不适用malloc的第一个缺点,不容易批量化
那么还有什么问题呢?
熟悉链表的同学一般会编写一个addNode的函数进行添加节点,如果这个地方不用malloc会有什么问题呢?
#include <iostream>
struct ListNode
{
int value;
ListNode *Next;
};
void AddNode(ListNode *head, int value)
{
ListNode next;
next.value = value;
head->Next = &next;
}
int main()
{
ListNode head;
AddNode(&head, 2);
printf("%d", head.Next->value);
return 0;
}
可以看见我们好像确实也可以这样操作,但这样操作会引入一个十分危险的漏洞
#include <iostream>
struct ListNode
{
ListNode *Next;
int value;
};
void AddNode(ListNode *head, int value)
{
ListNode next;
//printf("%x\n", &next);
next.value = value;
head->Next = &next;
}
void add()
{
int a = 3;
int b = 4;
int c = 5;
}
int main()
{
ListNode head;
AddNode(&head, 2);
add();
printf("%d", (&head)->Next->value);
return 0;
}
这里为了方便复现我修改了一下Struct的结构
我们看一下运行结果
这里变成4了
细心的同学会发现,好像这个地方和b的值一样了
这就要涉及到栈的结构
我们在函数里面定义的变量会放在栈内,随着函数调用的结束,栈会清空(这个清空只是表面上的,数据还在里面,只是移动了指针)
当我们在主函数调用了函数创建节点,我们获得了一个指针,这个指针其实指向的是AddNode()函数栈里面的一个内存区域,这个区域在我们运行完这个函数之后可能会交给其他调用的函数使用,那么其他调用的函数可能就会修改我们的数据,同时由于我们这个指针指向的是栈,也可能发生内存泄漏
(可能在特定的情况导致用户直接操作栈结构getshell)
如上,我们需要使用malloc
malloc他是在堆里面开辟空间,而堆可以保证不会被重复利用,但唯一缺点是需要我们手动回收