news 2026/10/11 13:12:51

CSAPP实验1全攻略:工具链、链接加载与进程漫游避坑详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CSAPP实验1全攻略:工具链、链接加载与进程漫游避坑详解

简介:面向哈工大计算机专业学生的《计算机系统漫游》实验1配套资料包,聚焦课程入门实践,帮助初学者打通从二进制到系统调用的完整知识链。压缩包大小约969MB,内含实验指导文档、可运行代码样例及配套数据文件,目录按知识点组织,便于逐项查阅。资源已吸引630人学习,适合正在完成实验1或复习底层原理的学生。内容覆盖实验涉及的九个关键模块:二进制与位运算、虚拟地址与页表、汇编指令、函数调用与栈帧、指针与内存分配、编译链接过程、性能分析工具、系统调用接口,并给出实验报告撰写框架。通过对照示例代码与文档解读,读者可快速理解每个实验环节,提升调试能力,为后续实验打下扎实基础。无论是要完成实验报告,还是准备后续实验,都能从中获得明确指引。

1. 计算机系统漫游实验:不是“写个 hello 截几张图”,而是给你一次看完整条工具链的机会

很多人拿到计算机系统漫游实验的第一反应是“这也能叫实验”——编译一个 hello.c、跑一下、截图、写报告,完事。但如果只是这么走过场,你就错过了整门课里唯一一次能把“预处理→编译→汇编→链接→加载→进程运行”整条链路亲手拆开的机会。CSAPP 实验 1 的真实目的不是验证“程序能跑”,而是让你被迫去回答那些平时被 IDE 和一行 gcc 命令遮住的问题:编译器的每一步到底产出了什么?符号表里为什么有未定义符号?fork 之后 printf 为什么会输出两遍?这些答案直接影响你后面 bomb、buffer 那几个硬核实验能不能顺利起步。这份实验资源适合两类人:一是正在上这门课、想拿高分报告的新手;二是想回头把工具链补扎实、但没时间自己从零整理的老手。

2. 环境准备与工具链选型:先让机器和实验文档对齐,再谈复现

2.1 三个最容易让环境“翻车”的版本差异

实验文档通常是在某个特定版本的工具链下写的,你在自己的机器上照抄命令,大概率会遇到三类差异,而且每类都足以让你的报告截图对不上号。

第一是 gcc 版本差异。老的实验文档基于 gcc 4.x 或 5.x,而现在主流发行版默认装的是 gcc 11 或 12。新版 gcc 默认开启 PIE(位置无关可执行文件),编译出来的可执行文件里各个段的加载地址是随机的,你 readelf 看到的地址和 gdb 里看到的地址会“飘”,这会给实验 4 那种需要固定地址分析的环节添乱。第二是发行版默认工具链的差异。Ubuntu 系的 binutils 版本偏新,readelf 和 objdump 的输出格式和一些老文档截图不一致,比如 .comment 段、GNU-stack 段的存在与否。第三是 32 位与 64 位的差异。实验文档里如果贴的是 32 位系统的输出,你拿 64 位系统去做,栈对齐、指针大小、结构体布局全都不一样,最直观的就是寄存器和地址长度。做实验 1 虽然没有那么深的汇编,但 gdb 里看到的地址范围差异就已经能让你手里文档的“标准答案”失效。

我一般拿到新环境会先跑一遍下面这段检查,确认工具链对齐后再开始实验。

# 确认系统架构、内核版本和发行版 uname -a cat /etc/os-release | head -2 # 确认编译工具链版本 gcc --version | head -1 make --version | head -1 ld --version | head -1 # 确认二进制分析工具齐全 which readelf objdump gdb ldd strace file

这里每条命令对应一个坑:uname -a看内核位数,如果是 aarch64 或 arm64 架构,实验 1 里的很多内存映射行为会和 x86_64 有出入,建议直接换 x86_64 环境;gcc --version用来判断默认编译参数,新版本默认 PIE 这件事必须心里有数,后面写 Makefile 时可以主动-no-pie关掉;which那一条是确认工具在 PATH 里,实验文档常假设你装了 binutils 全家桶,但最小化安装的机器上经常缺 readelf 或 objdump,缺哪个就装哪个。

2.2 必备工具清单与各自用途

实验 1 需要的最少工具集是编译套件(gcc、ld、make)和二进制分析套件(readelf、objdump、gdb、ldd、strace、file)。下面这张表是每个工具在这份实验里具体干哪一摊活的对照。

工具你在实验 1 里拿它干什么关键参数
gcc分别执行预处理、编译、汇编、链接四步-E / -S / -c / -v / -no-pie
readelf查看 ELF 文件头、段表、符号表-h / -S / -s / -l / -d
objdump反汇编 .text 段,看机器码与汇编的对应-d / -s / -h
ldd查看可执行文件依赖的动态库无参数,直接跟文件名
gdb观察进程加载、断点、寄存器、栈info proc mappings / b / r / x
strace跟踪系统调用序列,看 execve、open 的调用轨迹-f / -o 输出文件
file快速判断文件类型、架构、动态/静态链接无参数,直接跟文件名

这里有一个容易被忽略的边界:file和readelf看起来都是“看文件”,但file只是读文件头的几个字节做启发式判断,readelf才是真正解析段表。实验报告里如果让你“分析 ELF 格式”,你得用 readelf 的输出,而不是把 file 的一句话贴上去了事。

2.3 环境自检:用一个最小 hello 验证整条链路

在正式跑实验之前,我建议先编译一个最小的 hello.c 并完整走一遍加载过程,确认环境没有隐藏问题。最小验证的好处是,一旦后面出了问题,你能快速把锅甩给环境而不是自己的代码。

/* hello_env_check.c —— 环境自检用最小程序 */ #include <stdio.h> int main(int argc, char *argv[]) { printf("hello, csapp env check\n"); return 0; }
# 编译时显式关闭 PIE,避免地址“飘”影响后续 gdb 观察 gcc -no-pie -g -O0 hello_env_check.c -o hello_env_check # 用 file 确认产物是 64 位动态链接可执行文件 file hello_env_check # 用 ldd 查看动态依赖,确认加载器路径 ldd hello_env_check # 用 strace 跟踪从 execve 到 printf 的系统调用轨迹 strace -f -o trace.log ./hello_env_check

-no-pie是关键参数,它把可执行文件拉回传统的固定加载模型,让后续 gdb 观察地址时不会因为地址随机化而看得一头雾水;-g生成调试信息,实验 1 里你需要在 gdb 里看 main 的栈帧,这个必须有;-O0关闭优化,避免编译器把 printf 调用直接优化成 puts,导致汇编输出和源码对不上。strace -f -o trace.log是把包括子进程在内的所有系统调用记录到文件,实验报告里分析“hello 从加载到输出经历了哪些系统调用”时,这份 trace.log 就是你的素材。如果这一套跑下来 file、ldd、strace 输出都正常,环境基本就没问题了。

3. 拆解 hello 的一生:预处理、编译、汇编、链接、加载全链路

3.1 预处理:先搞清楚 #include 到底干了什么,而不是背概念

实验 1 的第一个动手点是观察预处理。你平时写#include <stdio.h>只知道“这是引入标准库”,但预处理真正做的事是:把 stdio.h 的内容原封不动地插入到源文件里,同时展开所有#define宏,处理所有条件编译指令。这个过程在编译器真正开始语法分析之前就完成了。

# 只做预处理,不编译、不汇编、不链接 # -E 让 gcc 在预处理后停下来,输出仍然是可以阅读的 C 源码 gcc -E hello.c -o hello.i # 看预处理产物的大小和行数 wc -l hello.i # 检查 stdio.h 里的 printf 声明是否被插入进来了 grep -n "printf" hello.i | head -5 # 看头文件的真实搜索路径 gcc -E hello.c -v 2>&1 | grep -A5 "search path"

-E参数让编译器停在预处理阶段,产物hello.i通常比源文件大几十倍,因为 stdio.h 里往往有上千行声明。wc -l看行数是从量的角度建立体感。grep "printf"是从质的角度验证“头文件内容真的进来了”——你在 hello.i 里看到的 printf 声明,就是编译器接下来要拿来检查你调用是否合法的依据。-v会打印编译过程的详细日志,其中search path后面列出的就是头文件搜索路径,这也是为什么要区分#include <stdio.h>(在系统默认路径里搜)和#include "myheader.h"(先搜当前目录再搜系统路径)的原因。

3.2 编译与汇编:从 C 到汇编再到机器码,观察编译器翻译

预处理之后是真正的编译,把 C 源码翻译成汇编指令;再经过汇编器,把汇编指令翻译成机器码并打包成目标文件。这两步分开做的好处是,你能看到中间产物.s和.o,而不是只看到最终可执行文件。

# -S 生成汇编文件,观察编译器如何翻译 printf 调用 gcc -S hello.i -o hello.s # 查看 main 函数的汇编,重点看 call 指令前面的参数传递 grep -A15 "<main>:" hello.s # -c 只汇编不链接,生成可重定位目标文件 gcc -c hello.s -o hello.o # 查看目标文件的符号表,注意 printf 是未定义符号 readelf -s hello.o | grep -E "printf|main"

-S生成的 hello.s 是纯汇编文本,mov 指令把字符串地址放到寄存器,然后 call printf 调用库函数。实验报告里写“汇编阶段”时,这个文件就是证据。-c生成的是.o目标文件,它和可执行文件的关键区别是:里面的地址大多是相对偏移,还没有被重定位,因为链接器还没决定 printf 在最终进程地址空间里的位置。readelf -s看符号表时,你会看到printf一行的 Ndx 列是UND(undefined),这就是“未定义符号”——它等着链接器从 libc 里把这个符号补上。

这一步很容易踩的坑是:新版本 gcc 默认会开启优化,哪怕你没写-O,编译器也可能把printf("hello\n")优化成puts("hello")。这会导致你抓破脑袋看汇编却找不到 printf 调用。解决办法很简单:编译时显式-O0,并且在实验报告里写明编译参数,不然导师只会看到“汇编和源码不一致”而不理解你做了什么。

3.3 链接:动态链接和静态链接的代价不是三言两语

实验 1 的文档会要求你区分动态链接与静态链接,但很多人只是背了一句“动态链接体积小、静态链接体积大”就交差。真正动手做一次就有体感了。

# 动态链接:默认方式,链接器只记录依赖,不复制代码 gcc -no-pie -O0 hello.c -o hello_dyn # 查看动态依赖,你会看到 libc.so.6 和 ld-linux 加载器 ldd hello_dyn # 静态链接:把 libc 的代码直接复制进可执行文件 gcc -static -no-pie -O0 hello.c -o hello_static # 对比大小 ls -lh hello_dyn hello_static # 查看两个文件的段表差异,注意静态链接多了哪些段 readelf -S hello_dyn | grep -E "\.text|\.dynamic|\.got" readelf -S hello_static | grep -E "\.text|\.dynamic|\.got"

动态链接的可执行文件里只有GOT(全局偏移表)和.plt(过程链接表)这类间接跳转结构,真正的 printf 代码在 libc.so.6 里,运行时由加载器解析;静态链接的可执行文件里.text段会大一个数量级,因为 libc 里的所有用到的函数代码都被复制进来了。ls -lh对比大小是最直观的体感数据,通常差二十倍以上。这里多说一句:静态链接的 hello 在实验报告里分析 ELF 段表时更有“素材感”,因为你能在符号表里看到一长串 libc 内部符号,这在动态链接版本里是看不到的。

3.4 加载:execve 之后发生了什么

这一步把 hello 从磁盘上的文件变成进程地址空间里的映像。你平时双击运行一个程序,背后是内核做了大量工作,而实验 1 让你用工具把内核的“黑匣子”撬开一条缝。

# 运行程序,获取它的 PID ./hello_dyn & PID=$! # 查看进程的内存映射:每一行代表一个已映射的段 cat /proc/$PID/maps | head -20 # 用 gdb 观察加载后的入口地址和 main 地址 gdb -q ./hello_dyn -ex "start" -ex "info proc mappings" -ex "x/i $pc" -ex "quit"

/proc/PID/maps里每一行格式是“地址范围 权限 偏移量 设备 挂载点 映射对象”。前几行里你会看到名字带hello_dyn的几个映射,权限分别是r-x(代码段)、r--(只读数据)、rw-(数据段),而后面一堆libc.so.6的映射就是动态链接库被加载进来的结果。gdb ... -ex "start"是让程序停在 main 的第一条指令,用info proc mappings看整个进程地址空间,再用x/i $pc查看即将执行的那一条指令。这一步的痛苦来源往往是 PIE:默认开启 PIE 的可执行文件,它的基地址每次运行都变,maps里的地址和readelf里的段地址对不上,用-no-pie就能让两者严格对应。

4. 从 fork 到 execve:进程视角下的“系统漫游”

4.1 进程的诞生:fork 和 execve 各干一半的活

实验 1 的文档通常会让你写一个小程序,观察 fork 之后子进程和父进程的行为差异。这里的关键认知是:fork 负责“复制进程”,execve 负责“替换进程映像”。一个完整的新进程是先 fork 复制出一个几乎一样的子进程,再在子进程里调用 execve 把 hello 加载进来。

/* proc_demo.c —— 观察 fork 后父子进程与 execve 的分工 */ #include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main(void) { pid_t pid = fork(); /* fork: 复制当前进程 */ if (pid == 0) { /* 子进程: 用 execve 替换自己的映像,运行 hello */ char *argv[] = {"hello_dyn", NULL}; char *envp[] = {NULL}; execve("./hello_dyn", argv, envp); perror("execve"); } else { /* 父进程: 等待子进程结束 */ wait(NULL); printf("parent: child finished\n"); } return 0; }
# 编译并运行,观察输出顺序 gcc -no-pie -O0 proc_demo.c -o proc_demo ./proc_demo # 用 strace 观察 fork/execve/wait4 的系统调用轨迹 strace -f -o proc_trace.log ./proc_demo grep -E "fork|execve|wait4" proc_trace.log

fork 返回两次:在父进程里返回子进程的 PID,在子进程里返回 0。所以if (pid == 0)是标准写法,用来区分父子。execve 是“一去不回”的系统调用,如果失败它不会 return 一个错误码,而是直接给子进程设置错误信息,所以perror("execve")写在它后面是兜底。strace 的-f参数让 strace 也跟踪 fork 出来的子进程,否则你只能看到父进程的调用,看不到子进程的 execve。

4.2 虚拟内存视角:hello 的地址空间不是一整块

你用 readelf 看的段表是“文件视角”,进程运行时的地址空间是“运行时视角”。两者有对应关系但不是一个东西。进程调度器给 hello 分配了从低到高若干段:代码段、数据段、堆、内存映射区(mmap)、栈。实验 1 要求你能把/proc/PID/maps里的每一行解释清楚。

# 运行 hello 并获取 PID,然后查看该进程的完整地址空间布局 ./hello_dyn & PID=$! cat /proc/$PID/maps

观察这个输出时,注意三个细节。第一,栈区的权限是rw-但没有x,栈只是数据区域,你不应该把代码丢上去执行,这在后面缓冲区溢出实验里是绕不开的知识点。第二,堆和栈之间隔着巨大的一段空洞,这是虚拟内存的经典特征——这段区域没有被任何映射占用,访问它会触发段错误。第三,地址从0x400000(传统 x86_64 代码段装载点)到0x7ffffffff000(用户栈顶)跨度几十 TB,但实际占用的物理内存只有几 KB,虚拟内存和物理内存的差别在这一步看得清清楚楚。

4.3 缓冲区:一个能让实验报告“翻车”的隐藏知识点

这个知识点经常被实验手册一笔带过,但考试和面答时却高频出现——printf 的缓冲区在 fork 之后会被复制。实验现象是:如果你的代码先 printf 再 fork,重定向输出到文件时,printf 的内容会打印两次。

/* buf_demo.c —— 演示 fork 复制缓冲区 */ #include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main(void) { printf("before fork\n"); /* 进入 stdout 缓冲区 */ fflush(stdout); /* 强制刷新,注释掉本行观察差异 */ pid_t pid = fork(); if (pid == 0) { exit(0); } else { wait(NULL); exit(0); } }
# 直接输出到终端:stdout 是行缓冲,fork 前遇到 \n 已刷出 ./buf_demo # 重定向到文件:stdout 变成全缓冲,缓冲区内容被复制 ./buf_demo > out.txt cat out.txt

如果代码里没有fflush(stdout),第一次跑(输出到终端)只看到一个before fork,因为终端是行缓冲模式,遇到换行符就自动刷新了;但重定向到文件后,stdout 变成全缓冲模式,printf的内容还躺在缓冲区里,fork 时缓冲区被完整复制给了子进程,父进程退出时刷一次、子进程退出时又刷一次,于是out.txt里有两行before fork。这是实验 1 报告中特别能体现深度的素材,也是你提前给后面 shell 实验打预防针的地方。

5. 避坑指南:实验 1 里四个真实踩过的坑

写这个章节不是纸上谈兵,以下每一条都是反复出现在学生报告里、甚至能让实验报告被打回重写的真实问题。

5.1 现象:gdb 里看到的地址和 readelf 对不上

用默认 gcc 编译后,readelf 显示.text段起始地址是0x1040或类似的小地址,但 gdb 里info proc mappings看到的可执行段基址是0x555555554000这种大地址,报告里两张图互为矛盾。

原因:发行版默认开启了 PIE,可执行文件被编译成位置无关的,加载时基地址由内核随机决定。readelf 显示的是“没有重定位前的段偏移”,gdb 里显示的是“加载后的运行地址”,两者隔着一个随机基址。解决:编译时显式加-no-pie,命令行gcc -no-pie -g -O0 hello.c -o hello,并且在报告的“环境说明”里写明这个参数。从那以后,我每次拿到新环境第一件事就是 Makefile 里写好-no-pie,否则后面所有地址类的实验都会带着这个隐患。

5.2 现象:elf 文件不能用 readelf 打开

实验里拷贝了一个文件到虚拟机上,readelf 报错说 “Not an ELF file”,file 命令显示它是current ar archive或者data。

原因:八成是从 Windows 下解压出来的文件带了奇怪的换行符,或者是用zip直接解压源码包时符号链接被破坏了。file命令的结果是判断文件真实格式的依据,不要用扩展名猜。解决:用git clone或tar -xf重新获取资源,如果是单个文件传输,用scp保持二进制模式,确认传输后xxd 文件名 | head看文件头,ELF 的第一个字节必须是7f 45 4c 46。

5.3 现象:重定向输出时 printf 内容重复

实验里先 printf 再 fork,结果重定向到文件后输出出现了两遍,学生以为是程序 bug。

原因:stdout 在全缓冲模式下(重定向到文件时)缓冲区被 fork 复制,父子进程退出时各刷新一次。这个问题不解决的话,你在实验报告里解释不清楚,而且会影响后续 shell 实验中对内置命令和外部命令输出顺序的理解。解决:在 fork 之前显式fflush(stdout),并且在报告中用这个原理去解释输出差异,而不是只在代码里补一行了事。

5.4 现象:静态链接的 hello 跑不起来或文件异常庞大

为了“看 ELF 结构”而用-static编译,结果生成的文件体积达到几百 MB,甚至在某些受限环境里报 “out of memory”。

原因:静态链接会把 libc 里被引用的所有相关代码复制进可执行文件,一个 hello 也包含完整的 I/O 和启动代码。新版 glibc 的静态链接体积膨胀更严重。解决:静态链接不要用 glibc,实验 1 里也没必要追求纯静态;如果报告里只是想展示“静态链接和动态链接的体积差异”,动态链接版本和静态链接版本对比存在即可,不需要跑起来。真需要小体积静态文件时用 musl 工具链交叉编译。

5.5 现象:WSL 环境下 /proc/PID/maps 输出异常或缺行

部分同学用 WSL 跑实验,发现cat /proc/PID/maps看不到预期的可执行文件映射,或者映射关系和老文档完全不一样。

原因:WSL 1 用的是兼容层而非原生 Linux 内核,/proc的很多实现是模拟的,进程内存映射的展示不完整;WSL 2 的虚拟机内核相对正常,但某些发行版版本下仍有差异。解决:实验 1 对进程观测有强依赖,建议直接在原生 Linux 虚拟机上做。这不是“哪个系统更好”的问题,是工具链输出必须和实验文档对齐的问题,你在报告里写“我用 WSL 跑”意味着后面所有地址截图都不能被验证。

6. 进阶:把一次性实验变成后续所有 lab 都能用的环境自检脚本

实验 1 做完就放一边太可惜了。我习惯把这一套观测命令收拢成一个脚本,后面做任何和 ELF、进程地址有关的实验前先跑一遍,五分钟确认环境对齐,省掉后面两小时的无用功。

#!/bin/bash # csapp_env_check.sh —— 实验 1 沉淀下来的环境自检脚本 # 用法: ./csapp_env_check.sh <可执行文件路径> # 作用: 一次性输出架构、编译参数、ELF 头、动态依赖、反汇编入口和运行痕迹 BIN="${1:-./hello}" echo "==== 1. 系统与工具链 ====" uname -m gcc --version | head -1 readelf --version | head -1 echo "==== 2. 文件类型与架构 ====" file "$BIN" echo "==== 3. ELF 关键段 ====" readelf -h "$BIN" | grep -E "Class|Machine|Type" readelf -S "$BIN" | grep -E "\.text|\.data|\.bss|\.dynamic" echo "==== 4. 动态依赖 ====" ldd "$BIN" || echo "(静态链接或无动态依赖)" echo "==== 5. 入口与 main 反汇编 ====" # -D 反汇编所有段,grep 拿到 _start 附近的前 10 条指令 objdump -D "$BIN" | grep -A10 "<_start>:" | head -12 echo "==== 6. 运行痕迹 ====" strace -f -e trace=execve,write "$BIN" 2>&1 | tail -5

脚本里值得留意的两个参数:readelf -h的Machine字段如果是Advanced Micro Devices X86-64说明是 x86_64 架构,如果显示ARM aarch64你需要停下来考虑是否换环境;objdump -D的-D和-d不同,-d只反汇编可执行段,-D会连数据段里的指令也尝试反汇编,实验 1 里用-D能看到更多“意外内容”,比如字符串被误当成指令的乱码,这在报告中可以作为“数据与指令交织”的素材。

这个脚本还有一个隐藏用途:服务器上做重现性实验时,别人给你发一个二进制文件,你不需要先信任它,先跑一遍这个脚本看它依赖什么库、入口在哪、反汇编前几条指令在干什么,基本能判断它是不是一个标准的入门实验产物。有一次某位同学在群里发了个“跑不出来”的二进制,我远程让他跑这个脚本,发现file输出显示是 Windows PE 格式,根本不是 Linux ELF——问题不在环境,在传输环节。从那以后,我每次在群里帮人排查实验问题,第一句话都是“先跑 env_check,把输出贴出来”。这份资源里的代码和脚本都是从这个角度沉淀的,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 13:11:48

Java 实现 HEIC 转 PNG/JPEG 全攻略:选型、性能与避坑

简介&#xff1a;这份资源面向需要在Java环境中处理HEIC图片的开发者&#xff0c;尤其是遇到苹果设备素材、旧系统或第三方库不支持该格式的兼容性场景。HEIC基于HEVC编码&#xff0c;压缩效率优于JPEG&#xff0c;但Java标准库并不原生支持解码&#xff0c;因此项目围绕借助Im…

作者头像 李华
网站建设 2026/10/11 13:11:01

野生动物目标检测数据集实战:从数据体检到YOLO调优避坑指南

简介&#xff1a;这份野生动物目标检测数据集面向从事计算机视觉、生态监测与智能安防的开发者与研究人员&#xff0c;提供可直接用于YOLO系列等主流检测框架训练的标准数据。数据集共1768张图片&#xff0c;按训练集1240张、验证集355张、测试集173张划分&#xff0c;覆盖熊、…

作者头像 李华
网站建设 2026/10/11 13:11:00

宫颈癌细胞YOLOv5检测:临床级数据集构建与落地实践

简介&#xff1a;本资源是一套专为医学图像目标检测任务设计的高质量宫颈癌YOLOv5训练数据集&#xff0c;面向计算机视觉初学者、AI医疗方向研究者及YOLO系列模型实践者&#xff0c;解决小目标病灶检测中数据稀缺、标注不规范等实际问题。数据集共2000个文件&#xff0c;含916张…

作者头像 李华
网站建设 2026/10/11 13:10:57

港科大 M-A-P 团队:开源音乐模型的华人力量图鉴

港科大 M-A-P 团队&#xff1a;开源音乐模型的华人力量图鉴 【免费下载链接】YuE2-3B 项目地址: https://ai.gitcode.com/hf_mirrors/m-a-p/YuE2-3B 当 Suno 凭闭源模型在 2025 年席卷全网时&#xff0c;很少有人注意到&#xff0c;在音乐生成这条赛道上&#xff0c;一…

作者头像 李华
网站建设 2026/10/11 13:06:06

运动想象脑电分类:CNN与Transformer混合架构实战解析

简介&#xff1a;这是一份基于Transformer的运动想象脑电信号分类Python源码&#xff0c;整合CNN与局部时空特征提取&#xff0c;对应个人毕设项目&#xff08;答辩98分&#xff09;&#xff0c;适合计算机、人工智能、自动化等专业学生用于课程设计、大作业或毕业设计。资源包…

作者头像 李华