打打基础 | 从翻转链表到寄存器、汇编与内存

news2025/6/4 1:39:36

我作为软件工程师在美国工作了三年,期间接触和浸泡过不少的技术栈,罗列一番的话有 AWS cloud, frontend (React, TypeScript), backend (Django, Springboot, ECS, GraphQL), JVM (Java, Scala, Kotlin), data pipelines (Spark, Snowflake, Prefect, DBT), database (Clickhouse, MySQL, Postgres, DuckDB), devOps (Gitlab CI, Jenkins, Bazel, linter), Observability (Grafana, Splunk, Prometheus), Docker, Kubernetes等一大串内容,做做准备的话可以把自己营造成多面手的人设。但是我明白,绝大多数时候里我都只是在使用各类application罢了,虽然自诩代码品味不错,追求让人一目了然而简洁易懂的程序,但总归还是在浅水里扑腾。

趁着辞职后的充电期,我开始系统性地学习计算机的各类基础内容。虽然缺乏这些知识并没有怎么影响我的日常工作,但我仍然发自内心地对计算机这个事物感到好奇,希望能够对它有更多的了解。我决定写些文章来记录分享我在阅读大名鼎鼎的教材 Computer Systems: A Programmer’s Perspective 期间的学习感悟,并尽可能地用高质量的例子来巩固所学。

这篇文章会用程序员们喜闻乐见的翻转链表作为例子,介绍寄存器register, 汇编assembly, 以及栈帧stack frame等内容,希望能够给大家带来一种「串联起来了」的愉悦。由于我比较偏好阅读英文的学习材料(我没有读过计算机领域的中文教材),对于许多技术名词的中文翻译感到别扭(比如把pipeline叫做流水线,stack frame叫做栈帧),所以干脆用英文进行写作。


Reversing a linked-list is like the “Hello World” from programmers. In this article I’m gonna use it as an example (written below in C) to showcase how C code gets translated into assembly, with a specific focus on stack frames, register usage, memory layout, and the power of the popular debugging tool gdb.

#include <stdio.h>

typedef struct Node {
    int val;
    int id;
    struct Node* next;
} Node;

// Reverses a singly linked list and returns the new head
Node* reverse(Node* head) {
    Node* prev = NULL;
    Node* curr = head;
    Node* next;

    while (curr != NULL) {
        next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }

    return prev;
}

// Prints the list
void print_list(Node* head) {
    while (head != NULL) {
        printf("Node ID: %d, Value: %d\n", head->id, head->val);
        head = head->next;
    }
}

int main() {
    // Declare four nodes on the stack and manually connect them
    Node node_4 = {40, 4, NULL};
    Node node_3 = {30, 3, &node_4};
    Node node_2 = {20, 2, &node_3};
    Node node_1 = {10, 1, &node_2};

    printf("Original list:\n");
    print_list(&node_1);

    // Reverse the list
    Node* new_head = reverse(&node_1);

    printf("\nReversed list:\n");
    print_list(new_head);
    return 0;
}

The code above should be pretty straightforward. Before we dig into this piece of code, let’s refresh our mind with some basic knowledge on CPU registers, memory and assembly.

Registers and Memory in a 64-bit World

When writing low-level programs like C, the compiler transforms our code into assembly instructions that run on the CPU. Each CPU has a small set of registers, which are fast-access storage units. On a 64-bit machine, these registers are 64 bits wide — i.e., they can hold a number as large as 2⁶⁴.

Computer memory is essentially a giant array of bytes. Every byte in memory has a unique address (think of it like array index), which is also a 64-bit number on modern architectures. So, each register can natually point to a particular memory address.

Notice that the values in the registers don’t always have to be memory address. They can also simply be numbers like -5, 42, etc. The CPU doesn’t care about the meaning—it just sees binary values. It’s up to us (and the compiler) to interpret them correctly. This dual interpretation — value or address — is what makes assembly programming flexible yet confusing to beginners.

Essentially all data inside a machine is just bits. When we see two bytes 0x48 0x69, we can either intepret them as string “Hi”, or as decimal number 18537, and the computer itself wouldn’t know the difference. Note that the prefix ‘0x’ means this a hex number (0x10 is 16 in decimal). You’ll run into hex numbers all the time in the low-level world, because memory addresses are 64-bit and decimal is just too clumsy - you would definitely prefer using 0x401256to 010 000 000 001 001 001 010 110 to denote an address. Hex is compact and maps neatly to binary.

There are 16 general-purpose registers in x86-64 systems:

%rax, %rbx, %rcx, %rdx,
%rsi, %rdi, %rbp, %rsp,
%r8 through %r15

Some registers have conventional purposes:

  • %rsp: stack pointer, always points to the top of the stack
  • %rax: return value from a function
  • %rdi, %rsi, %rdx, %rcx, %r8, %r9: used to pass the first 6 function arguments

Common Assembly Instructions

Assembly instructions manipulate the data between registers and memory. These instructions are where we begin to see how higher-level constructs like linked lists are implemented under the hood.

Here are a few of the most common instructions you’ll run into all the time. These might seem overwhelming for now, but don’t worry, we will see them work in action later.

  • mov: Copy value from source to destination (register-to-register, memory-to-register, etc.). For example, say register %rax is now 0x402000
    • mov $0x28, %rax updates register %rax to value 0x28.
    • mov $0x28, (%rax) puts integer 0x28 into memory starting at address 0x402000
  • lea: Load effective address. Usually used to perform simple arthmetic operations.
    • Say %rax is 0x402000, and %rbx is 0x2. Instruction lea 0x1(%rax, %rbx, 4), %rdx sets %rdx = 0x402000 + 2 * 4 + 0x1 = 0x402009.
  • call: Push return address onto stack and jump to the function
  • ret: Pop return address and jump back to where the function call was made
  • push: Decrease %rsp and store a value at the new top of the stack
  • pop: Opposite of push: Load value from top of stack and increase %rsp
  • sub, add: Perform arithmetic (often used to grow/shrink stack frame)
  • cmp: Compare two values
  • jmp, je, jne, jg, etc.: Control flow via unconditional or conditional jumps. Usually used together with cmp instruction, for example, cmp $0x3, %rax
    • je equal: %rax == 3
    • jne not equak: %rax != 3
    • jg greater: %rax > 3

Dissecting the main Function

I use the following code to compile the C file and then disassemble it:

gcc -g -no-pie -Og -o linked_list linked_list.c
objdump -d --no-show-raw-insn linked_list > linked_list.s

Let’s look at the disassembled version of our C program’s main function. I’ve added lots of annotations to help reading through. At each line, the leftmost hex number is the address of the instruction.

00000000004011c5 <main>:
  4011c5:	endbr64                    # Control-flow enforcement. Let's ignore it
  4011c9:	push   %rbx              	 # Register rbx will be modified in the process. We push it onto the stack so that we can retrieve later 
  4011ca:	sub    $0x50,%rsp          # Move the top of the stack to allocate 80 bytes on stack(0x50 is 80 in decimal) 
  4011ce:	mov    %fs:0x28,%rax       
  4011d7:	mov    %rax,0x48(%rsp)     # Put special value in the stack to detect stack buffer overflows. We can ignore for now.
  4011dc:	xor    %eax,%eax           # Clear %eax

# === Initialize 4 linked list nodes ===
  4011de:	movl   $0x28,(%rsp)        # node4.val = 40
  4011e5:	movl   $0x4,0x4(%rsp)      # node4.id = 4
  4011ed:	movq   $0x0,0x8(%rsp)      # node4.next = NULL

  4011f6:	movl   $0x1e,0x10(%rsp)    # node3.val = 30
  4011fe:	movl   $0x3,0x14(%rsp)     # node3.id = 3
  401206:	mov    %rsp,%rax           # address of node4
  401209:	mov    %rax,0x18(%rsp)     # node3.next = &node4

  40120e:	movl   $0x14,0x20(%rsp)    # node2.val = 20
  401216:	movl   $0x2,0x24(%rsp)     # node2.id = 2
  40121e:	lea    0x10(%rsp),%rax     # address of node3
  401223:	mov    %rax,0x28(%rsp)     # node2.next = &node3

  401228:	movl   $0xa,0x30(%rsp)     # node1.val = 10
  401230:	movl   $0x1,0x34(%rsp)     # node1.id = 1
  401238:	lea    0x20(%rsp),%rax     # address of node2
  40123d:	mov    %rax,0x38(%rsp)     # node1.next = &node2

# === Print Original List ===
  401242:	lea    0xdd3(%rip),%rdi    # Load "Original list:" format string
  401249:	call   401060 <puts@plt>   # Print string to our screen

  40124e:	lea    0x30(%rsp),%rbx     # Set rbp to point to node1
  401253:	mov    %rbx,%rdi           # Set pointer to node1 as the first argument to function call `print_list` so that we're ready for the next line
  401256:	call   401195 <print_list>

# === Reverse the List ===
  40125b:	mov    %rbx,%rdi           # first arg: head node
  40125e:	call   401176 <reverse>    # returns new head
  401263:	mov    %rax,%rbx           # save new head

# === Print Reversed List ===
  401266:	lea    0xdbe(%rip),%rdi    # Load "Reversed list:" string
  40126d:	call   401060 <puts@plt>

  401272:	mov    %rbx,%rdi
  401275:	call   401195 <print_list>

# === Stack Canary Check ===
  40127a:	mov    0x48(%rsp),%rax
  40127f:	sub    %fs:0x28,%rax
  401288:	jne    401295 <main+0xd0>   # if canary changed → stack overflow → crash

# === Return Normally ===
  40128a:	mov    $0x0,%eax           # 0 is the success return code
  40128f:	add    $0x50,%rsp          # Clean up stack frame
  401293:	pop    %rbx                # Retrieve the original value of register rbx
  401294:	ret                        # Return 0
  401295:	call   401070 <__stack_chk_fail@plt>  # stack corrupted → crash

A very important concept is stack frame - a block of memory to use whenever a function is called. It stores everything that the function needs while it runs, such as return address (where to jump back after the function finishes), local variables, function arguments (if passed via the stack) and sometimes, saved registers (like %rbp, %rbx) to restore later. It’s created at the start of a function and destroyed at the end.

The register %rsp is the top of the stack frame, and is extremely important since it marks the boundary of the stack frame. When we say the stack frame is destroyed, we don’t mean physically destroying the hardware. Instead, it means the previous chunk of memory is no longered considered part of the current stack frame, and other programs are now allowed to write new values to this memory space to overwrite the existing ones.

Now let’s run GDB (GNU Debugger) to execute the program. add a breakpoint to address 401256 (you can find this number in the disassembled code above), right before calling print_list function.

$ gdb linked_list
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
(gdb) break *0x401256
(gdb) run
Starting program: /home/ubuntu/linked_list 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Now let’s examine the stack frame. The GDB command to examine memory (a very powerful tool!) is x/<count><format><size> <address>.

  • codes for [format]: x = hex, d = decimal, u = unsigned, c = char, s = string
  • codes for [size]: b = byte (1 byte), h = halfword (2 bytes), w = word (4 bytes), g = giant (8 bytes)

For example, the following code examines 20 words (4 bytes each) at the current stack pointer:

(gdb) x/20wx $rsp
0x7fffffffe350: 0x00000028      0x00000004      0x00000000      0x00000000
0x7fffffffe360: 0x0000001e      0x00000003      0xffffe350      0x00007fff
0x7fffffffe370: 0x00000014      0x00000002      0xffffe360      0x00007fff
0x7fffffffe380: 0x0000000a      0x00000001      0xffffe370      0x00007fff
0x7fffffffe390: 0x00000000      0x00000000      0xfbeca800      0x9629f2d4

The first 4 lines correspond to the 4 linked-list nodes we just created, and we can clearly see each node’s value, id and the pointer to next node. Each node consumes 16bytes (two integers, 4 bytes for each, and a 8-byte pointer), and that’s why their memory addresses all differ by 0x10.

Here’s how the stack looks during execution.

|------------------------| <- at 0x50(%rsp), 0x7fffffffe3a0
| stack overflow check   | 
|------------------------| 
|                        | 
|------------------------| <- at 0x40(%rsp), 0x7fffffffe390
| node1 (next)           | 
|------------------------| <- at 0x38(%rsp), 0x7fffffffe368
| node1 (val, id)        | 
|------------------------| <- at 0x30(%rsp), 0x7fffffffe370
| node2 (next)           | 
|------------------------| <- at 0x28(%rsp), 0x7fffffffe368
| node2 (val, id)        | 
|------------------------| <- at 0x20(%rsp), 0x7fffffffe370
| node3 (next)           | 
|------------------------| <- at 0x18(%rsp), 0x7fffffffe368
| node3 (val, id)        | 
|------------------------| <- at 0x10(%rsp), 0x7fffffffe360
| node4 (next)           | 
|------------------------| <- at 0x8(%rsp), 0x7fffffffe358
| node4 (val, id)        | 
|------------------------| <- %rsp, 0x7fffffffe350

The stack frame contains exactly 0x50 bytes of memory, as allocated by the instruction 4011ca: sub $0x50,%rsp above. You may notice that the 8 bytes at address 0x40(%rsp) are simply unused, and you may wonder why did we bother to allocate space for it. On x86-64, the stack must be aligned to 16 bytes before a call instruction, and we need those “0x00” bytes for padding.

Dissecting the print_list Function

Now let’s resume from we set the breakpoint, and dig into the print_list function.

0000000000401195 <print_list>:
  401195:	endbr64

# === Function Prologue ===
  401199:	push   %rbx            # Save %rbx
  40119a:	mov    %rdi,%rbx       # Move first arg (head pointer) into %rbx

# === Loop Condition ===
  40119d:	jmp    4011be          # Jump to address 4011be for loop condition check

# === Loop Body ===
  40119f:	mov    (%rbx),%ecx     # Load val into %ecx
  4011a1:	mov    0x4(%rbx),%edx  # Load id into %edx
  4011a4:	lea    0xe59(%rip),%rsi # Load the address of format string "Node ID: %d, Value: %d\n" into %rsi
  4011ab:	mov    $0x1,%edi       # First arg to printf (fd = stdout)
  4011b0:	mov    $0x0,%eax       # Clear %eax before variadic call
  4011b5:	call   401080 <__printf_chk@plt>  # secure version of function printf

  4011ba:	mov    0x8(%rbx),%rbx  # Move to next node (follow .next)

# === Loop Condition ===
  4011be:	test   %rbx,%rbx       # Check if current node is NULL
  4011c1:	jne    40119f          # If not NULL, jump to address 40119f to continue loop

# === Function Epilogue ===
  4011c3:	pop    %rbx            # Restore %rbx
  4011c4:	ret                    # Return

We can add a second breakpoint to the function to examine the stack

(gdb) break *0x4011ba
Breakpoint 2 at 0x4011ba: file linked_list.c, line 30.
(gdb) continue
Continuing.
Node ID: 1, Value: 10

Breakpoint 2, print_list (head=head@entry=0x7fffffffe380) at linked_list.c:30
30              head = head->next;
(gdb) x/20wx $rsp
0x7fffffffe340: 0xffffe380      0x00007fff      0x0040125b      0x00000000
0x7fffffffe350: 0x00000028      0x00000004      0x00000000      0x00000000
0x7fffffffe360: 0x0000001e      0x00000003      0xffffe350      0x00007fff

We notice that the stack pointer register %rsp has been changed from 0x7fffffffe350 to 0x7fffffffe340, even though the print_list function doesn’t allocate any space to the stack frame. So, why did the stack frame grow by 0x10 (16bytes)?

  • In the main function, when we execute instruction 401256: call 401195 <print_list>, we push the address of next instruction 40125b: mov %rbx,%rdi onto the stack, so that when function call print_list finishes, we can return to the correct location of main function and continue executing. This return addreses takes 8 bytes.
  • Inside the print_list function , we run 401199: push %rbx to push the value of register %rbx onto the stack. That takes another 8 bytes.

So, even though register %rsp isn’t directly called, it was modified by instructions call and push. Notice that instructions ret and pop are counterparts of them, and will do the opposite - pop data out of the stack.

Dissecting the reverse Function

Now let’s move on to the last function reverse.

0000000000401176 <reverse>:
  401176:	endbr64

# === Function Prologue ===
  40117a:	mov    $0x0,%eax       # Set return register %rax = NULL (this will track the new head)

# === Loop Condition (initial jump) ===
  40117f:	jmp    40118f          # Jump to loop condition before first iteration

# === Loop Body ===
  401181:	mov    0x8(%rdi),%rdx  # Load next node: %rdx = current->next
  401185:	mov    %rax,0x8(%rdi)  # Reverse the link: current->next = previous
  401189:	mov    %rdi,%rax       # Update previous: previous = current
  40118c:	mov    %rdx,%rdi       # Move to next: current = next

# === Loop Condition ===
  40118f:	test   %rdi,%rdi       # Check if current (now in %rdi) is NULL
  401192:	jne    401181          # If not, continue the loop

# === Function Epilogue ===
  401194:	ret                    # Return; %rax holds new head of reversed list

This piece of assembly should be rather straightforward now.

One thing interesting here is that in my C function reverse , I declare 3 local variables prev, curr and next, but in the assembly code there isn’t any stack space allocated for them. It’s because the compiler tries to optimize away unnecessary memory operations, and it’s smart enough to realize that we don’t actually need any additional memory - simply using registers to update pointers is enough. And this process is just beautiful.

Thanks

I just started learning fundamentals about machines recently, although I’ve been a developer for many years. This article is my personal attempt to internalize my learnings and share them with others. If you’re just getting into the low-level world, I hope you’re just as excited as I’m.

One last note: I’m using my M2 Macbook Air for daily task and it doesn’t work well with GDB and all those x86-64 stuff. I ended up getting an AWS ECS Ubuntu t2.micro machine.

$ uname -a
Linux ip-172-31-18-227 6.8.0-1024-aws #26~22.04.1-Ubuntu SMP Wed Feb 19 06:54:57 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2395552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

常见相机的ISP算法

常见的ISP算法 3A算法 去雾算法 图像增强算法 图像宽动态算法 图像的电子缩放算法&#xff0c;无极电子缩放 图像降噪算法 相机常见问题 1.相机启动速度问题&#xff0c;启动速度较慢 2.相机扛不住高低温问题 3.相机散热问题问题 4.相机高低温芯片保护掉电 5.相机的成像效果或者…

2024 CKA模拟系统制作 | Step-By-Step | 8、题目搭建-创建 Ingress

目录 ​​​​​​免费获取题库配套 CKA_v1.31_模拟系统 一、题目 二、核心考点 Ingress 资源定义 Ingress Controller 依赖 服务暴露验证 网络层次关系 三、搭建模拟环境 1.创建命名空间 2.安装ingress ingress-nginx-controller 3.创建hello.yaml并部署 四、总结 …

OldRoll复古胶片相机:穿越时光,定格经典

在数字摄影盛行的今天&#xff0c;复古胶片相机的独特魅力依然吸引着无数摄影爱好者。OldRoll复古胶片相机这款软件&#xff0c;以其独特的复古风格和丰富的胶片滤镜效果&#xff0c;让用户仿佛穿越回了那个胶片摄影的黄金时代。它不仅模拟了胶片相机的操作界面&#xff0c;还提…

通俗易懂的 JS DOM 操作指南:从创建到挂载

目录 &#x1f9e9; 1. 创建元素&#xff1a;document.createElement / createElementNS &#x1f4dd; 2. 创建文本&#xff1a;document.createTextNode ✏️ 3. 修改文本&#xff1a;node.nodeValue &#x1f5d1;️ 4. 移除元素&#xff1a;el.removeChild() &#x1…

CSS Day07

1.搭建项目目录 2.网页头部SEO三大标签 3.Favicon图标与版心 &#xff08;1&#xff09;Favicon图标 &#xff08;2&#xff09;版心 4.快捷导航 5.头部-布局 6.头部-logo 7.头部-导航 8.头部-搜索 9头部-购物车 10.底部-布局 11.底部-服务区域 12.底部-帮助中心 13.底部-版权…

RV1126-OPENCV 交叉编译

一.下载opencv-3.4.16.zip到自己想装的目录下 二.解压并且打开 opencv 目录 先用 unzip opencv-3.4.16.zip 来解压 opencv 的压缩包&#xff0c;并且进入 opencv 目录(cd opencv-3.4.16) 三. 修改 opencv 的 cmake 脚本的内容 先 cd platforms/linux 然后修改 arm-gnueabi.to…

【深度学习】 19. 生成模型:Diffusion Models

Diffusion Models Diffusion Models 简介 Diffusion 模型是一类通过逐步添加噪声并再逆向还原的方式进行图像生成的深度生成模型。其基本流程包括&#xff1a; 前向过程&#xff08;Forward Process&#xff09;&#xff1a;将真实图像逐步加噪&#xff0c;最终变为高斯噪声…

JMeter 直连数据库

1.直连数据库的使用场景 1.1 参数化&#xff0c;例如登录使用的账户名密码都可以从数据库中取得 1.2 断言&#xff0c;查看实际结果和数据库中的预期结果是否一致 1.3 清理垃圾数据&#xff0c;例如插入一个用户&#xff0c;它的ID不能相同&#xff0c;在测试插入功能后将数据删…

易路 iBuilder:解构企业 AI 落地困境,重构智能体时代生产力范式

一、从大模型到智能体的产业跃迁 2024 年堪称中国人工智能产业的 "战略拐点" 之年。当 DeepSeek R1 模型以 "技术 价格" 双重普惠模式掀起行业震荡时&#xff0c;各企业纷纷意识到&#xff0c;大模型的真正价值不在于技术炫技&#xff0c;而在于成为企业…

计算机网络之路由表更新

1.解题思路 对新接收到的路由表进行更新&#xff0c;全部"距离"1&#xff0c;且"下一跳路由器"都写成发送方路由器的名称。 开始对比新表和原来的路由表 1.看目的网络 如果是新的目的网络&#xff0c;则直接把对应的各项信息填入表中&#xff1b;如果是相同…

万兴PDF手机版

万兴PDF手机版(万兴PDF编辑器)是一款国产PDF编辑工具.万兴PDF安卓版提供PDF文档编辑,AI撰写摘要,文档签名,设置密码保护等功能,万兴PDF专家APP以简约风格及文档编辑功能为核心,支持多设备终端同步保存.全免 万兴 PDF 编辑器是一款功能强大的 PDF 编辑软件&#xff0c;它支持多种…

Qt -使用OpenCV得到SDF

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【暂无】 欢迎点赞&#x1f44d;收藏⭐关注❤️ 目录 cv::MatdistanceTransform获得SDF 本文的目标&#xff0c; 是简单学习并使用OpenCV的相关函数&#xff0c; 并获得QImage的SDF(Signed Distance Field 有向距离场) 至…

DDR5 ECC详细原理介绍与基于协议讲解

本文篇幅较长,涉及背景原理介绍方便大家理解其运作方式 以及 基于DDR5协议具体展开介绍。 背景原理介绍 上图参考:DDR 内存中的 ECC 写入操作时,On-die ECC的工作过程如下: SoC将需要写入到Memory中的数据发送给控制器控制器将需要写入的数据直接发送给DRAM芯片在DDR5 DR…

EC800X QuecDuino开发板介绍

支持的模组列表 EG800KEC800MEC800GEC800E 功能列表 基本概述 EC800X QuecDuino EVB 搭载移远 EC800 系列模组。支持模组型号为&#xff1a; EC800M 系列、EC800K 系列、EG800K 系列、EC800E 系列等。 渲染图 开发板的主要组件、接口布局见下图 资料下载 EC800X-QuecDui…

PHP轻量级聊天室源码(源码下载)

最新版本&#xff1a;v2.1.2 (2024.08更新) 运行环境&#xff1a;PHP5.6&#xff08;无需MySQL&#xff09; 核心特性&#xff1a;手机电脑自适应、TXT数据存储、50条历史消息 适用场景&#xff1a;小型社区/企业内网/教育培训即时通讯 一、核心功能亮点&#xff08;SEO关键词布…

leetcode hot100刷题日记——33.二叉树的层序遍历

解题总结二维vector的初始化方法 题目描述情况1&#xff1a;不确定行数和列数情况2&#xff1a;已知行数和列数情况3&#xff1a;已知行数但不知道列数情况4&#xff1a;已知列数但不知道行数 题目描述 解答&#xff1a;用队列 思路都差不多&#xff0c;我觉得对于我自己来说&a…

《数据结构初阶》【番外篇:快速排序的前世今生】

【番外篇&#xff1a;快速排序的前世今生】目录 前言&#xff1a;---------------起源---------------一、诞生&#xff1a;二、突破&#xff1a;三、核心&#xff1a; ---------------发展---------------1. 早期版本&#xff1a;简单但不稳定1960 年&#xff1a;初始版本 2. …

【笔记】基于 MSYS2(MINGW64)的 Poetry 虚拟环境创建指南

#工作记录 基于 MSYS2&#xff08;MINGW64&#xff09;的 Poetry 虚拟环境创建指南 一、背景说明 在基于 MSYS2&#xff08;MINGW64&#xff09;的环境中&#xff0c;使用 Poetry 创建虚拟环境是一种高效且灵活的方式来管理 Python 项目依赖。本指南将详细介绍如何在 PyChar…

PINNs案例——二维磁场计算

基于物理信息的神经网络是一种解决偏微分方程计算问题的全新方法… 有关PINN基础详见&#xff1a;PINNs案例——中心热源温度场预测问题的torch代码 今日分享代码案例&#xff1a;二维带电流源磁场计算 该案例参考学习论文&#xff1a;[1]张宇娇&#xff0c;孙宏达&#xff0…

算法打开13天

41.前 K 个高频元素 &#xff08;力扣347题&#xff09; 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 示例 1: 输入: nums [1,1,1,2,2,3], k 2 输出: [1,2]示例 2: 输入: nums [1], k 1 输出: …