news 2026/6/11 11:43:25

北大ICS位运算实验包:bits.c源码+实验指南PDF(含约束说明)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
北大ICS位运算实验包:bits.c源码+实验指南PDF(含约束说明)

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

简介:北京大学计算机系统导论(ICS)课程Lab1实验材料,主打C语言位级编程训练。核心是bits.c文件,实现13个纯位运算函数,全部面向32位有符号整数,禁用if/else、循环、数组下标、函数调用和大于32位的立即数。每个函数仅允许使用! ~ & ^ | + << >>等基本操作,要求严格符合标准C语义。配套datalab.pdf详细说明实验目标、编码规则、测试方法及常见陷阱,比如如何不用条件语句实现逻辑判断、如何用位掩码提取符号位、如何安全实现加法溢出检测等。资源包含两个版本的bits.c:一个标注‘未使用大于32位imm’便于对照理解约束,另一个为常规实现;另有main.c用于本地编译运行验证,.gitignore和.inscode为开发环境配置文件。不提供自动评测脚本或答案,适合学生自主调试、教师布置作业或复习位操作底层逻辑。所有内容基于真实教学场景整理,可直接用于实验预习、代码分析和考试前强化训练。

1. 这不是编程练习,是给C语言“做CT”的第一课

你打开bits.c,看到第一个函数bitXor(int x, int y),注释写着“仅用~和&实现异或”,手一抖差点敲出return x ^ y;——停!这不是考你会不会写代码,而是考你知不知道^这个符号背后,CPU里真实发生的那几根晶体管是怎么开关的。北大ICS这门课从Lab1就撕掉所有抽象层,把学生直接摁在32位有符号整数的二进制地表上爬行。我带过三届ICS助教,每年都有学生卡在isPositive(int x)函数上:明明逻辑是“x > 0”,可禁用if、禁用比较运算符、连>都不能用,怎么表达?答案不是查百度,而是盯着0x80000000这个数发呆半小时——因为符号位就在那儿,它不说话,但它的值就是真相。

关键词里的“位运算实验”“C语言位操作”听着像老生常谈,但ICS Lab1的狠劲在于:它把“位”从工具变成唯一生存法则。你不能调用abs(),不能写for(i=0;i<32;i++),甚至不能写int mask = 0xffffffff;——因为0xffffffff是32位全1,但作为立即数,在某些编译器下会被解释为无符号长整型,违反“不使用大于32位立即数”的硬约束。所以你得写(1 << 31) | ((1 << 31) - 1)来构造它,而这就逼你立刻算清楚:1 << 310x80000000,减1是0x7fffffff,或起来才是0xffffffff。这不是炫技,是让你亲手把C标准里“整型提升”“符号扩展”这些黑话,搓成能看见、能摸着、能调试的比特流。

这份材料真正珍贵的,不是bits.c里那13个函数的答案,而是datalab.pdf里那些没明说却处处透着火药味的细节。比如它反复强调“行为必须符合标准C语义”,这意味着你写的tmin()不能只是返回0x80000000,还得确保它在所有符合C99的平台上都代表最小的int;你实现的addOK(int x, int y)检测溢出,不能只靠x>0 && y>0 && x+y<0这种直觉——因为x+y本身已经溢出了,结果未定义。你得用(x ^ y) < 0 ? 0 : ((x ^ (x + y)) & (y ^ (x + y))) < 0这类绕口令式表达,因为只有这样,每一步运算都在未定义行为发生前就掐住了脖子。这哪是写代码?这是用位运算给C语言做一次精准外科手术。

如果你正对着bits.c发懵,别急着搜答案。先打开datalab.pdf第4页的“编码规则”小节,把那七条禁令抄一遍:禁用条件语句、禁用循环、禁用数组下标、禁用函数调用、禁用大于32位的立即数、禁用类型转换(如(unsigned))、禁用sizeof。然后合上文档,拿张纸画一个32位int的格子,从右往左标上0到31位,再在最左边写上“符号位(S)”。接下来,你所有的思考,都得在这个格子里完成。ICS Lab1要训练的,从来不是“怎么写出正确代码”,而是“怎么让思维彻底降维,只用0和1的物理事实去推理”。

2. 实验整体设计与底层逻辑拆解

2.1 为什么非得“自废武功”?——约束背后的系统级意图

ICS Lab1所有看似反人类的约束,都不是为了刁难学生,而是精准对应计算机系统底层的真实限制。我们逐条拆解:

  • 禁用if/else和条件运算符?::这不是讨厌分支,而是模拟CPU的ALU(算术逻辑单元)本质。ALU本身没有“判断”能力,它只做运算;所谓“条件跳转”,是靠比较指令(如cmp)把结果写入标志寄存器(如ZF零标志、SF符号标志),再由后续的je(jump if equal)指令读取标志位决定是否跳转。Lab1要求你用!(逻辑非)和&(按位与)组合出等效逻辑,本质上是在教你:所有高级语言的“判断”,最终都归结为对某个比特位(通常是符号位或零标志)的提取与测试。比如isNegative(int x)函数,核心就是(x >> 31) & 1——把符号位右移到最低位,再用& 1屏蔽其他位,得到0或1。这行代码,就是test %eax, %eax; js negative_label这条汇编指令在C语言里的灵魂复刻。

  • 禁用循环(for/while/do-while:现代CPU的循环执行,依赖于程序计数器(PC)的自动递增和条件跳转的配合。但Lab1要你处理的是固定宽度(32位)的数据,所有操作都可在常数步内完成。禁用循环,逼你放弃“遍历每一位”的惯性思维,转而拥抱并行位操作。例如bitCount(int x)统计1的个数,标准解法是while(x) { count += x & 1; x >>= 1; },但Lab1要求你用类似“分治”的思路:先两两相加(x = (x & 0x55555555) + ((x >> 1) & 0x55555555)),再四四相加……最终一步到位。这正是GPU和SIMD指令集加速位操作的底层思想——不是靠更快的单核,而是靠同一时刻处理多个数据位。

  • 禁用大于32位的立即数:这直指x86-64架构的指令编码现实。在x86中,大多数ALU指令(如add,and,xor)的立即数字段只有32位宽。如果你写and eax, 0xffffffffffffffff,汇编器会报错或静默截断。Lab1强制你用(1<<31)-1~0U(注意U后缀保证无符号)来构造掩码,就是在训练你对指令集架构(ISA)的肌肉记忆。当你未来调试一段性能瓶颈在movabs指令的汇编时,你会瞬间明白:哦,这里用了64位立即数,触发了更长的指令编码,多占了一个字节缓存行。

  • 禁用函数调用:C语言函数调用涉及栈帧建立、参数压栈、返回地址保存等开销。Lab1所有函数都设计为纯计算、无状态、无副作用,其目的就是让你看清:最底层的计算,就是输入几个寄存器,输出一个寄存器,中间不碰内存、不改栈、不依赖任何运行时环境negate(int x)实现为~x + 1,这不仅是补码定义,更是neg指令的硬件实现——ALU里一个取反器加一个加法器,就这么简单。

这些约束合起来,构成了一张“系统级编程许可证”。拿到它的人,才能真正看懂gdbdisassemble出来的每一行汇编,才能理解为什么volatile关键字会影响编译器优化,才能明白memcpy为什么比手写循环快——因为Lab1训练的,是把C代码当作硬件操作手册来读的能力。

2.2 13个函数的内在谱系:从原子操作到复合逻辑

这13个函数绝非随机堆砌,它们构成一个严密的、由简入繁的能力金字塔。我按认知难度和功能依赖关系重新梳理:

层级函数名核心能力依赖前置系统级映射
L1:比特级原子操作bitXor,tmax,tmin,negate掌握基本位运算组合、理解补码边界、实现负数ALU基础门电路(异或门、非门)
L2:符号与范围探测isTmax,isTmin,isNegative,isPositive,fitsBits提取符号位、构造全1掩码、判断数值范围L1CPU标志寄存器(SF, OF)生成逻辑
L3:安全算术与溢出控制addOK,subOK,logicalShift,arithShift溢出检测、无符号/有符号移位区别L1+L2ALU溢出检测电路、移位器硬件设计
L4:位模式变换与统计bitCount,bang并行位统计、零值检测(不用==0L1+L2SIMD位操作指令(如popcnt)原理

关键洞察在于:L2和L3层函数,是连接C语义与硬件行为的翻译器。比如logicalShift(int x, int n)要求实现逻辑右移(高位补0),而C语言中x >> n对有符号数是算术右移(高位补符号位)。你要么用((unsigned)x) >> n(但禁用类型转换),要么手动构造:(x >> n) & ~(((1 << n) - 1) << (32 - n))。这个公式背后,是清晰的硬件动作分解:先算术右移,再用掩码把高位的符号位“擦掉”,换成0。你写的不是C代码,而是一份给硬件工程师看的微操作说明书。

再看bang(int x)——实现!x(逻辑非),但禁用!运算符。标准解法是(~x & (x + ~0)) >> 31 & 1?不对,太绕。最优解是(x | (~x + 1)) >> 31 & 1。为什么?因为x == 0时,x | (~x + 1)0 | 1 = 1,右移31位得0;x != 0时,x至少有一个1,~x + 1-x,二者或运算必然产生高位1,右移31位得1。再& 1确保结果是0或1。这个解法的精妙,在于它把“零值检测”这个抽象概念,完全锚定在补码加法器的物理输出上——你不是在写逻辑,你是在指挥加法器吐出你需要的那个比特。

3. 核心细节解析与实操要点

3.1bits.c双版本对照:理解约束的物理意义

资源包里特意提供了两个bits.c:一个标注“未使用大于32位imm”,另一个是常规实现。这不是多此一举,而是给你一把解剖刀。我们以tmax()函数为例(返回最大正int,即0x7fffffff):

常规版(可能违规):

int tmax(void) { return 0x7fffffff; // 直接写32位十六进制常量 }

约束合规版(推荐):

int tmax(void) { return (1 << 31) - 1; // 1<<31 是 0x80000000,减1得 0x7fffffff }

表面看只是写法不同,但背后是两套思维体系:
- 常规版是“结果导向”:我知道答案是0x7fffffff,直接填进去。
- 合规版是“过程导向”:我理解int是32位补码,最大正数是符号位0、其余31位全1;而1 << 31把1移到符号位(0x80000000),减1就自然得到31位全1(0x7fffffff)。

提示:1 << 31在C标准中是未定义行为(UB),因为int是32位,左移31位导致溢出。但ICS Lab1明确允许此操作,并视其为“构造掩码的合法手段”。这是教学上的刻意妥协——它告诉你:在底层,我们有时需要越过C标准的安全网,直接和硬件对话。真正的生产代码中,你应该用INT_MAX宏,但Lab1要你亲手造出INT_MAX

再看fitsBits(int x, int n)(判断x能否用n位补码表示):

int fitsBits(int x, int n) { int shift = 32 + (~n + 1); // 计算 32-n,用补码减法 int masked = x << shift >> shift; // 算术右移回原位,高位补符号位 return !(x ^ masked); // 如果原始x和“截断再扩展”后的x相同,则fit }

这里~n + 1-n的补码表示,32 + (~n + 1)就是32 - nx << shift把低位n位移到最高位,>> shift再算术右移回来,如果x原本就能用n位表示,那么高位补的符号位和原始高位一致,x ^ masked为0。这个函数完美展示了:位移不是移动数字,而是移动比特在内存中的物理位置;而“截断”和“符号扩展”,不过是同一组比特被不同方式解读的结果

3.2datalab.pdf里的隐藏考点:那些没写进函数名的陷阱

datalab.pdf的“常见陷阱”章节,字字千金。我结合多年debug经验,提炼三个最易踩坑的点:

陷阱1:>>在C中是有符号右移,但你的目标可能是逻辑右移
arithShift(int x, int n)要求实现算术右移(高位补符号位),这和C的x >> n一致。但logicalShift(int x, int n)要求高位补0,这就不一样了。错误做法:

// 错!直接强转,违反禁用类型转换规则 return ((unsigned)x) >> n;

正确做法是手动清除符号位影响:

int logicalShift(int x, int n) { int mask = ~((1 << (32 + (~n))) - 1); // 构造高位掩码,如n=2时mask=0xc0000000 return (x >> n) & mask; // 先算术右移,再用mask清高位 }

这里的32 + (~n)就是32 - n1 << (32 - n)得到高位起始位置,减1得低位掩码,取反得高位掩码。这个计算过程,就是你在纸上画32个格子,亲手标记哪些位该保留、哪些该清零的过程。

陷阱2:addOK的溢出检测,必须避免自身溢出
检测x + y是否溢出,最直觉的想法是(x > 0 && y > 0 && x + y < 0),但x + y这一步本身就可能溢出,导致未定义行为。datalab.pdf提示:“检测必须在溢出发生前完成”。正确思路是:溢出只发生在同号数相加时,且结果符号与加数相反。所以:

int addOK(int x, int y) { int sum = x + y; int posOverflow = (x > 0) && (y > 0) && (sum < 0); // 但x>0和sum<0也用了比较符! // 正确解法:用符号位计算 int sx = x >> 31; // 符号位,0或-1(因算术右移) int sy = y >> 31; int ss = sum >> 31; // 同号:sx == sy;溢出:ss != sx return !(sx == sy && ss != sx); }

sx == sy又用了==!所以最终要写成:

int addOK(int x, int y) { int sx = x >> 31; int sy = y >> 31; int sum = x + y; int ss = sum >> 31; // sx == sy 等价于 !(sx ^ sy) 的符号位为0,即 ((sx ^ sy) >> 31) == 0 // ss != sx 等价于 (ss ^ sx) 的符号位为1,即 ((ss ^ sx) >> 31) == -1 return !(((sx ^ sy) >> 31) & ((ss ^ sx) >> 31)); }

看到这里,你应该明白了:Lab1的每个函数,都是在训练你把高级语言的抽象概念(相等、不等、大于),翻译成底层硬件能理解的比特操作。这不是编程题,这是编译原理的实践课。

陷阱3:bang函数的终极解法,揭示补码的哲学
bang(int x)实现!x,即x==0时返回1,否则返回0。最简洁解法:

int bang(int x) { return ((x | (~x + 1)) >> 31) & 1; }

为什么?因为:
- 若x == 0,则~x + 1 = ~0 + 1 = 0x | (~x + 1) = 0 | 0 = 00 >> 31 = 0& 1 = 0
- 若x != 0,则x至少有一个1,~x + 1-x(补码定义),二者或运算,由于x-x的比特模式必然在某一位不同(除非x=0),结果必不为0,且最高位很可能是1,>> 31得-1(全1),& 1得1。

这个解法的震撼之处在于:它用补码的数学定义(x + (-x) = 0)和位或运算的性质(a | b只要a或b有一个1,结果就有1),构建了一个完美的零值探测器。你写的不是代码,而是补码系统的一条公理。

4. 实操过程与核心环节实现

4.1 本地验证环境搭建:main.c的正确用法

资源包里的main.c是你的第一道防线,但它不是用来“跑通就行”的玩具。正确用法是把它当作一个微型调试台。典型main.c结构如下:

#include <stdio.h> #include "bits.h" // 假设头文件 int main() { printf("tmax: 0x%x\n", tmax()); // 验证常量函数 printf("isNegative(-5): %d\n", isNegative(-5)); // 验证逻辑函数 printf("addOK(0x7fffffff, 1): %d\n", addOK(0x7fffffff, 1)); // 验证溢出 return 0; }

关键操作步骤:
1.编译时开启严格警告gcc -Wall -Wextra -std=c99 -o main main.c bits.c-Wall会揪出隐式类型转换、符号扩展警告;-std=c99确保符合实验要求的C标准。
2.gdb单步跟踪:编译时加-g,然后gdb ./main,在关键函数设断点。例如:
bash (gdb) break addOK (gdb) run (gdb) display/x $rax # 查看返回值寄存器 (gdb) stepi # 单步执行汇编指令
你会亲眼看到x >> 31如何把%eax的内容算术右移,&指令如何对两个寄存器做按位与。这才是Lab1的终极目标:让C代码和汇编指令之间,不再有黑箱。

  1. 构造边界测试用例:不要只测addOK(1,2),要测addOK(0x7fffffff, 1)(正溢出)、addOK(0x80000000, -1)(负溢出)、addOK(0, 0)(不溢出)。datalab.pdf里提到的“极端情况”,就是这些值。

注意:main.c不包含自动测试框架,这意味着你必须自己设计测试矩阵。建议建一个test_cases.txt
```

func_name, arg1, arg2, expected_result

isNegative, -1, , 1
isNegative, 0, , 0
addOK, 0x7fffffff, 1, 0
```
然后写个Python脚本读取并调用,这本身就是一次小型工程实践。

4.2 13个函数的通关路径:从bitXorbitCount

我们按实际调试难度,给出一条渐进式通关路线,并附关键代码片段(仅展示核心逻辑,省略完整函数签名):

Step 1:bitXor—— 异或的原子分解
目标:用~&实现x ^ y
原理:布尔代数恒等式x ^ y = ~(x & y) & ~(~x & ~y)(德·摩根律)。
实现:

int bitXor(int x, int y) { return ~(x & y) & ~((~x) & (~y)); }

验证:x=1(0x1), y=2(0x2)x&y=0,~(x&y)=0xffffffff;~x=0xfffffffe,~y=0xfffffffd,(~x)&(~y)=0xfffffffc,~((~x)&(~y))=0x3; 最终0xffffffff & 0x3 = 0x3 = 3,正确。

Step 2:isPositive—— 符号位的暴力提取
目标:x > 0返回1,否则0。
原理:正数符号位为0,且x != 0
实现:

int isPositive(int x) { int sign = x >> 31; // 符号位,0或-1 int zero = !x; // 用!运算符(允许),或用bang函数 return !(sign | zero); // sign为0且zero为0时,结果为1 }

这里!x是允许的,因为约束禁用的是if/else等控制流,!是合法的一元运算符。

Step 3:addOK—— 溢出检测的黄金公式
这是最难的函数之一,也是理解补码的核心。
原理:溢出当且仅当(x > 0 && y > 0 && x+y < 0)(x < 0 && y < 0 && x+y > 0)。用符号位表达:

int addOK(int x, int y) { int sx = x >> 31; int sy = y >> 31; int sum = x + y; int ss = sum >> 31; // 同号且结果异号:溢出 // sx == sy 等价于 (sx ^ sy) == 0,即 ((sx ^ sy) >> 31) == 0 // ss != sx 等价于 (ss ^ sx) != 0,即 ((ss ^ sx) >> 31) != 0 // 所以溢出条件:!((sx ^ sy) >> 31) && ((ss ^ sx) >> 31) // 返回值:!overflow int same_sign = !((sx ^ sy) >> 31); // 1 if same, 0 if diff int overflow = same_sign & ((ss ^ sx) >> 31); // 1 if overflow return !overflow; }

Step 4:bitCount—— 并行位统计的艺术
统计1的个数,最优解是分治:

int bitCount(int x) { // Step 1: 每2位一组,统计每组1的个数 x = (x & 0x55555555) + ((x >> 1) & 0x55555555); // 0x55555555 = 01010101... // Step 2: 每4位一组 x = (x & 0x33333333) + ((x >> 2) & 0x33333333); // 0x33333333 = 00110011... // Step 3: 每8位一组 x = (x & 0x0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f); // 0x0f0f0f0f = 00001111... // Step 4: 每16位一组 x = (x & 0x00ff00ff) + ((x >> 8) & 0x00ff00ff); // Step 5: 全32位 x = (x & 0x0000ffff) + ((x >> 16) & 0x0000ffff); return x; }

这个算法的美在于:它不依赖循环,每一步都是对整个32位字的并行操作,时间复杂度O(1),完美匹配硬件的并行处理能力。

5. 常见问题与排查技巧实录

5.1 编译与链接阶段的“幽灵错误”

问题现象gcc main.c bits.c报错undefined reference to 'tmax'
原因分析bits.c里函数声明为int tmax(void),但main.c里可能写了extern int tmax();(旧式声明),或头文件缺失。更隐蔽的原因是:bits.c里函数定义前缺少static,而main.c里又有同名函数,导致链接冲突。
排查技巧
- 用nm bits.o | grep tmax检查目标文件中tmax符号是否存在且为T(text段,即已定义)。
- 用gcc -E main.c查看预处理后的代码,确认头文件是否被正确包含。
-终极方案:在bits.c顶部加#include "bits.h",并在bits.h中规范声明所有函数,main.c只包含bits.h

问题现象:程序运行结果正确,但./btest(假设你后来拿到了评测脚本)报错
原因分析:Lab1虽不提供btest,但很多学生会从网上找旧版。旧版btest可能对立即数检查更宽松,或测试用例不同。你的代码在main.c里通过,但在btest的严苛环境下失败,往往是因为:
- 使用了未定义行为(UB),如1 << 32(左移位数等于位宽);
- 对INT_MIN-x-INT_MIN溢出);
- 忽略了datalab.pdf里“所有函数必须处理INT_MIN”的要求。

解决方案:在main.c里显式测试边界值:

printf("tmin test: %x\n", tmin()); // 应为0x80000000 printf("negate(INT_MIN): %x\n", negate(0x80000000)); // 应为0x80000000(-INT_MIN == INT_MIN)

5.2 逻辑错误的“比特级”调试法

当函数行为诡异(如isPositive(-1)返回1),不要急于改代码,按以下步骤比特级排查:

步骤1:打印中间变量的二进制
写一个辅助函数:

void print_bits(int x) { for (int i = 31; i >= 0; i--) { printf("%d", (x >> i) & 1); if (i % 4 == 0) printf(" "); // 每4位空格分隔 } printf("\n"); }

然后在isPositive里插入:

printf("x = "); print_bits(x); printf("x>>31 = "); print_bits(x >> 31);

你会立刻看到:x = -10xffffffff),x>>310xffffffff(全1),这就是符号位提取的真相。

步骤2:用gdb观察寄存器
isPositive函数入口设断点:

(gdb) break isPositive (gdb) run (gdb) info registers rax rdx rcx # 查看参数寄存器 (gdb) x/4xb $rbp-4 # 查看局部变量内存

你会发现,x >> 31这条C语句,对应汇编的sar $0x1f, %eax(算术右移31位),而%eax里的值,就是你正在调试的比特流。

步骤3:构造最小反例
如果addOK0x7fffffff, 1返回1(应为0),那就只测这一组:

int main() { int x = 0x7fffffff, y = 1; printf("x=%x, y=%x\n", x, y); printf("x>>31=%x, y>>31=%x\n", x>>31, y>>31); int sum = x + y; printf("sum=%x, sum>>31=%x\n", sum, sum>>31); return 0; }

输出会显示sum=0x80000000sum>>31=0xffffffff,从而定位到ss != sx判断逻辑。

5.3 经验总结:那些只在深夜debug时才懂的道理

  • “立即数”不是数字,是比特模板0xff255在源码里一样,但在汇编层面,0xff是8位立即数,255可能被编码为32位。Lab1要求你写0xff而非255,就是在训练你像汇编器一样思考:这个常量,最终会占用多少个字节的机器码?

  • >>是算术右移,>>>不存在:Java有无符号右移>>>,C没有。所以logicalShift必须手动构造掩码。这个限制不是缺陷,而是提醒你:硬件没有“无符号”概念,只有比特;所谓的“无符号”,是你对同一组比特的解读方式不同

  • !是唯一的“逻辑”运算符:约束禁用if/else,但允许!。这是因为!是C标准定义的“逻辑非”,它把任意非零值映射为1,零映射为0,且不改变原值。它是连接数值世界和布尔世界的唯一桥梁。bang函数的本质,就是把这个桥梁造出来。

  • 最短的代码,往往最危险tmax()写成(1<<31)-1很短,但1<<31是UB;写成~0U>>1~0U0xffffffff,右移1位得0x7fffffff)更安全,因为unsigned右移是定义良好的。Lab1的“安全”,不是不犯错,而是让错误暴露在编译期或运行期,而不是在某个特定CPU上偶然成功。

最后分享一个小技巧:把bits.c打印出来,用红笔在每个函数旁边画32个格子,亲手填上测试用例的比特模式。当你的手指划过纸面,感受0和1的排列组合时,你才真正进入了ICS的世界——那里没有魔法,只有确定的比特,和确定的物理定律。

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

简介:北京大学计算机系统导论(ICS)课程Lab1实验材料,主打C语言位级编程训练。核心是bits.c文件,实现13个纯位运算函数,全部面向32位有符号整数,禁用if/else、循环、数组下标、函数调用和大于32位的立即数。每个函数仅允许使用! ~ & ^ | + << >>等基本操作,要求严格符合标准C语义。配套datalab.pdf详细说明实验目标、编码规则、测试方法及常见陷阱,比如如何不用条件语句实现逻辑判断、如何用位掩码提取符号位、如何安全实现加法溢出检测等。资源包含两个版本的bits.c:一个标注‘未使用大于32位imm’便于对照理解约束,另一个为常规实现;另有main.c用于本地编译运行验证,.gitignore和.inscode为开发环境配置文件。不提供自动评测脚本或答案,适合学生自主调试、教师布置作业或复习位操作底层逻辑。所有内容基于真实教学场景整理,可直接用于实验预习、代码分析和考试前强化训练。


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

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

从零设计一个能跑排序的CPU:用Logisim仿真单总线结构与微程序控制

从零设计一个能跑排序的CPU&#xff1a;用Logisim仿真单总线结构与微程序控制在计算机体系结构的学习中&#xff0c;真正理解CPU工作原理的最佳方式莫过于亲手设计一个。本文将带你从零开始&#xff0c;使用Logisim仿真工具构建一个能够执行排序算法的单总线结构CPU&#xff0c…

作者头像 李华
网站建设 2026/6/11 11:35:36

MCU电气特性与热设计实战:从数据手册到可靠嵌入式硬件

1. 项目概述&#xff1a;从数据手册到可靠设计在嵌入式系统&#xff0c;尤其是汽车电子和工业控制这类对可靠性要求严苛的领域&#xff0c;选型一颗微控制器&#xff08;MCU&#xff09;仅仅是万里长征的第一步。真正决定产品能否在高温、振动、复杂电磁环境下稳定工作十年甚至…

作者头像 李华