一、错误的C程序
注:重点是addBook
方法,下面的写法是错误的,你可以先想一想为什么是错的。
#include <stdio.h>
/** 书的结构体 */
struct Book {
char title[8]; // 书名
struct Book *next; // 下一本书
};
/**
* 在原书链表的最前面插入新书(可理解为书店里新出的书都放在最上面)。【错误写法】
* @param library 原书链表
* @param newBook 新书
*/
void addBook(struct Book *library, struct Book *newBook) {
if (library != NULL) { // 如果当前链表不为空
newBook->next = library; // 则把当前链表挂在newBook的后面
}
library = newBook; // 当前链表指向newBook
}
/**
* 打印链表
* @param library
*/
void printLibrary(struct Book *library) {
while (library != NULL) {
printf("书名: %s\n", library->title);
library = library->next;
}
}
int main(void) {
struct Book *library = NULL; // 定义指针 library,目前未指向实际的Book对象
struct Book book1 = {"title1"}; // 定义第1本书,标题是title1
struct Book book2 = {"title2"}; // 定义第2本书,标题是title2
addBook(library, &book1); // 把第1本书放到链表的最前面
addBook(library, &book2); // 把第2本书放到链表的最前面
printLibrary(library); // 打印链表
return 0;
}
二、为什么addBook
代码是错的
addBook
的代码单独拎出来如下:
void addBook(struct Book *library, struct Book *newBook) {
if (library != NULL) { // 如果当前链表不为空
newBook->next = library; // 则把当前链表挂在newBook的后面
}
library = newBook; // 当前链表指向newBook
}
内存布局如下:
行数 | 函数 | 变量 | 变量地址 | 内存地址 | 内存值 |
---|---|---|---|---|---|
1 | main() | library | &library | 0x61fe18 | 0x0 |
2 | main() | book1 | &book1 | 0x61fe00 | title1… |
3 | main() | book2 | &book2 | 0x61fdf0 | title2… |
4 | addBook() | library | &library | 0x61fdd0 | 0x0 |
5 | addBook() | newBook | &newBook | 0x61fdd8 | 0x61fe00 |
可以看出,进入addBook
方法时,为library
变量重新分配了一个内存地址,与外面的main
方法里的library
虽然名称一样,但是是不同的内存地址。
在addBook
方法里面对library
变量做的操作,都只影响了第4行中0x61fdd0
的内存地址处的值,而没有影响第1行中0x61fe18
内存地址处的值,因此当返回到main
函数时,main
函数中的library
变量没有任何变化。
三、正确写法
addBook
正确写法如下(main
方法也有小有改动,代码里未列出,后面图片里有):
void addBook(struct Book **library, struct Book *newBook) {
if (*library != NULL) { // 如果当前链表不为空
newBook->next = *library; // 则把当前链表挂在newBook的后面
}
*library = newBook; // 当前链表指向newBook
}
先上一个对比图,左侧是错误的代码,右侧是正确的代码:
在addBook
方法中,声明中的*library
变成了**library
,方法里面的library
变成了*library
。
在main
方法中,之前传的是library
,现在传的是&library
。
四、正确写法的内存布局与解释
行数 | 函数 | 变量 | 变量地址 | 内存地址 | 内存值 |
---|---|---|---|---|---|
1 | main() | library | &library | 0x61fe18 | 0x0 |
2 | main() | book1 | &book1 | 0x61fe00 | title1… |
3 | main() | book2 | &book2 | 0x61fdf0 | title2… |
4 | addBook() | library | &library | 0x61fdd0 | 0x61fe18 |
5 | addBook() | newBook | &newBook | 0x61fdd8 | 0x61fe00 |
跟错误写法的内存布局仅有一处区别,就是第4行的内存值:错误写法的值是0x0
,而正确写法的内存值是0x61fe18
,而这个0x61fe18
又正好是第1行中的library
变量的内存地址。
于是在addBook
方法里,就可以直接去操作第1行中的library
的内存值了,这样返回main
方法后,就能得到修改后的内存值。
五、正确的C程序
正确的C程序的完整代码如下,方便复制:
#include <stdio.h>
/** 书的结构体 */
struct Book {
char title[8]; // 书名
struct Book *next; // 下一本书
};
/**
* 在原书链表的最前面插入新书(可理解为书店里新出的书都放在最上面)。【正确写法】
* @param library 原书链表
* @param newBook 新书
*/
void addBook(struct Book **library, struct Book *newBook) {
if (*library != NULL) { // 如果当前链表不为空
newBook->next = *library; // 则把当前链表挂在newBook的后面
}
*library = newBook; // 当前链表指向newBook
}
/**
* 打印链表
* @param library
*/
void printLibrary(struct Book *library) {
while (library != NULL) {
printf("书名: %s\n", library->title);
library = library->next;
}
}
int main(void) {
struct Book *library = NULL; // 定义指针 library,目前未指向实际的Book对象
struct Book book1 = {"title1"}; // 定义第1本书,标题是title1
struct Book book2 = {"title2"}; // 定义第2本书,标题是title2
addBook(&library, &book1); // 把第1本书放到链表的最前面
addBook(&library, &book2); // 把第2本书放到链表的最前面
printLibrary(library); // 打印链表
return 0;
}
六、C代码方法调用时CPU干了些什么?
基础汇编相关的指令在前几篇中已有介绍,这里不重复了。只介绍一下addBook(&library, &book1)
对应的汇编指令:
lea rdx,[rbp-0x30] // 把[rbp-0x30]的内存地址存入rdx寄存器
lea rax,[rbp-0x38] // 把[rbp-0x38]的内存地址存入rax寄存器
mov rsi,rdx // rdx寄存器里面的值存入rsi寄存器
mov rdi,rax // rax寄存器里面的值存入rdi寄存器
call 0x555555555169 <addBook> // 调用0x555555555169地址处的方法
// <addBook>的功能不确定,应该是方便调试用的