从内存与汇编的角度理解C语言指针第04篇:实现单链表时为什么不能指针 *p 而要用指针的指针 **pp?【视频解析】

!!!看视频解析请点击这里!!!

一、错误的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
}

内存布局如下:

行数函数变量变量地址内存地址内存值
1main()library&library0x61fe180x0
2main()book1&book10x61fe00title1…
3main()book2&book20x61fdf0title2…
4addBook()library&library0x61fdd00x0
5addBook()newBook&newBook0x61fdd80x61fe00

可以看出,进入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

四、正确写法的内存布局与解释

行数函数变量变量地址内存地址内存值
1main()library&library0x61fe180x0
2main()book1&book10x61fe00title1…
3main()book2&book20x61fdf0title2…
4addBook()library&library0x61fdd00x61fe18
5addBook()newBook&newBook0x61fdd80x61fe00

跟错误写法的内存布局仅有一处区别,就是第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>的功能不确定,应该是方便调试用的
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值