csapp实验一:datalab

一、实验目的

本实验目的是加强学生对位级运算的理解及熟练使用的能力。

二、报告要求

本报告要求学生把实验中实现的所有函数逐一进行分析说明,写出实现的依据,也就是推理过程,可以是一个简单的数学证明,也可以是代码分析,根据实现中你的想法不同而异。

三、函数分析

符号只能使用位运算符 ! ~ & ^ | + << >> ,不能使用 if, else, ?:, while

bitXor函数

函数要求:

函数名 bitXor
参数 int , int
功能实现 x^y

实现分析:

a^b = ~( ~(a&~b)&(~(~a&b)))

异或的逻辑是 x ^ y = (x AND ~y) OR (~x AND y)

根据de morgan律:A | B = (A & ~B)

函数实现:

复制代码
int bitXor(int x, int y) {
   return ~(~(x&~y)&(~(~x&y)));
 }

getByte函数

函数要求:

函数名 getByte
参数 int , int
功能实现 在word x中提取第n个字节

实现分析:

getByte(x,n) = (x>>(n<<3))&0xff

从一个 32 位整数中取出第 n 个字节(0 表示最低字节)。

  • 每个字节是 8 位,所以要移动 n * 8 位,即 n << 3
  • 再用掩码 0xFF(11111111)保留最后 8 位。

函数实现:

复制代码
int getByte(int x, int n) {
   return (x>>(n<<3))&0xff;
 }

logicalShift函数

函数要求:

函数名 logicalShift
参数 int , int
功能实现 将x向右移n位

实现分析:

在函数中新定义了两个变量y和z,其中:

普通的 >> 是算术右移,会补符号位(即1或0)。

逻辑右移则始终补 0。

解决方法:

  1. 先算术右移 x >> n
  2. 再用掩码清除被符号位填充的部分。

掩码思路:
(1 << 31) 是最高位为 1 的数。
((1 << 31) >> n) << 1 构造出 n 位的 1,再取反即为低 32-n 位为1的掩码。

函数实现:

复制代码
int logicalShift(int x, int n) {
  int mark = 0x01 << 31;
  int y = (x & mark);
  int z = ~((y>>n)<<0x1);
  return (x>>n) & z;
}

bitCount函数

函数要求:

函数名 bitCount
参数 int
功能实现 计算输入字节中为1的bit数

实现分析:

  • 原理:一个2bit的二进制数,其所有的组合有00, 01, 10, 11。若要计算这个2bit的数的二进制有多少个1,则可以用这个数减去其二进制第二个位上的数字,得到的便是这个2bit数字的二进制中的1个个数。
  • 核心思想(并行计数 / 分治):
    把 32 位分成很多小块,先在最小粒度上对相邻位成对计数,然后把结果在更大粒度上合并,逐层累加,最终得到总和。这样做避免了循环和条件判断,全部用位运算和加法完成,速度快且合法操作数有限。
  • 掩码(便于分组):
  • mask1 = 0x55555555 = 二进制每 2 位为 01 01 01 ...
    用于把每对位分别取出(位0与位1配对)。
  • mask2 = 0x33333333 = 每 4 位为 0011 0011 ...
    用于把每 4 位当作两组 2 位的和来合并。
  • mask4 = 0x0F0F0F0F = 每 8 位为 00001111 ...
    用于把每 8 位当作两组 4 位的和来合并。

函数实现:

复制代码
int bitCount(int x) {
  int m8 = 0x55; 
  int mask1 = m8 | (m8 << 8) | (m8 << 16) | (m8 << 24);
  int m3 = 0x33;
  int mask2 = m3 | (m3 << 8) | (m3 << 16) | (m3 << 24);
  int m0f = 0x0f;
  int mask4 = m0f | (m0f << 8) | (m0f << 16) | (m0f << 24);

  x = x + (~(((x >> 1) & mask1)) + 1);
  x = (x & mask2) + ((x >> 2) & mask2);
  x = (x + (x >> 4)) & mask4;
  x = x + (x >> 8);
  x = x + (x >> 16);
  return x & 0x3f;
}

conditional函数

函数要求:

函数名 conditional
参数 int , int , int
功能实现 实现x ? y : z

实现分析:

核心思想是:

我们想构造一个掩码(mask),它是全 1 或全 0

  • x ≠ 0 时 → mask = 0xFFFFFFFF
  • x == 0 时 → mask = 0x00000000

制造掩码:

mask = !!x; // 把 x 变成 0 或 1

mask = ~mask + 1; // 扩展成全 0 或全 1

  • !!x:两次逻辑非,确保结果只有 0 或 1
    • 比如:x = 5 → !!x = 1
      x = 0 → !!x = 0
  • ~mask + 1
    • 当 mask = 0 → (~0 + 1) = 0
    • 当 mask = 1 → (~1 + 1) = 0xFFFFFFFF

函数实现:

复制代码
int conditional(int x, int y, int z) {
  int mask = (!!x << 31) >> 31;
  return (mask & y) | (~mask & z);
 }

tmin函数

函数要求:

函数名 tmin
参数 void
功能实现 返回最小补码

实现分析:

最小的补码就是0x10000000,直接返回

函数实现:

复制代码
int tmin(void) {
   return 0x1<<31;
 }

fitsBits函数

函数要求:

函数名 fitsBits
参数 int , int
功能实现 若x能用n个bit表示则返回1,否则返回0

实现分析:

整体思路:

  • 用 n 位能表示的数,其符号扩展后应与原值相同;
  • 左移丢弃高位再右移回来,看是否变了。

函数实现:

复制代码
int fitsBits(int x, int n) {
  int shift = 32 + (~n + 1); // 32 - n
  return !(((x << shift) >> shift) ^ x);
 }

dividePower2函数

函数要求:

函数名 dividePower2
参数 int , int
功能实现 计算x/(2^n),其中0<=n<=30

实现分析:

正数右移n位即可;

负数需要补偿偏差使结果"向零取整"(而非向下取整)。

函数实现:

复制代码
int dividePower2(int x, int n) {
  int sign = x >> 31;
  int bias = (1 << n) + ~0;  // 2^n - 1
  int add = sign & bias;
  return (x + add) >> n;
 }

negate函数

函数要求:

函数名 negate
参数 int
功能实现 取相反数-x

实现分析:

直接按位取反加1即可

函数实现:

复制代码
int negate(int x) {
    return ~x+1;
}

howManyBits函数

函数要求:

函数名 howManyBits
参数 int
功能实现 计算一个整数 x 用补码表示所需的最少二进制位数。返回表示x所要用的最小bit数

实现分析:

x 二进制补码 最少位数
12 01100 5
298 0100101010 10
-5 1011 4
0 0 1
-1 1 1
0x80000000 1000...000 32

要知道要多少位表示 x,实际上是找:

  • 对于正数:最高的 1 在第几位
  • 对于负数:最高的 0 在第几位(因为负数补码前面是 1)

所以要对数处理:

  • 对正数:不变;
  • 对负数:按位取反,相当于找"最高的 1"。

然后用位级的二分来进行查找最高的1

最终 b16 + b8 + b4 + b2 + b1 + b0 + 1 就是所需的位数。

+1 是因为还要包含符号位

函数实现:

复制代码
int howManyBits(int x) {
  int b16, b8, b4, b2, b1, b0;
  int sign = x >> 31;    /* sign = 0 (x >= 0) or -1 (x < 0) */
  x = (sign & ~x) | (~sign & x);  /* if x negative, set x = ~x, else keep x */

  b16 = !!(x >> 16) << 4; /* if top 16 bits nonzero, add 16 */
  x >>= b16;
  b8 = !!(x >> 8) << 3;   /* if top 8 bits nonzero, add 8 */
  x >>= b8;
  b4 = !!(x >> 4) << 2;   /* if top 4 bits nonzero, add 4 */
  x >>= b4;
  b2 = !!(x >> 2) << 1;   /* if top 2 bits nonzero, add 2 */
  x >>= b2;
  b1 = !!(x >> 1);        /* if top 1 bit nonzero, add 1 */
  x >>= b1;
  b0 = x;
  return b16 + b8 + b4 + b2 + b1 + b0 + 1;
}

isLessOrEqual函数

函数要求:

函数名 isLessOrEqual
参数 int , int
功能实现 若x<=y则返回1,否则返回0

实现分析:

判断大小转换成看两数之差 y - x >= 0,但是两数异号直接计算可能会溢出

所以我们必须先判断 x 与 y 的符号是否相同

情况1:x 与 y 符号不同

  • 如果 x 是负的,y 是正的 → 一定成立 (x ≤ y)
  • 如果 x 是正的,y 是负的 → 一定不成立 (x ≤ y)

情况2:x 与 y 符号相同

  • 用正常的差值判断:y - x >= 0

函数实现:

复制代码
int isLessOrEqual(int x, int y) {
  int sx = x >> 31;          /* sign bit of x: 0 or -1 */
  int sy = y >> 31;          /* sign bit of y */
  int diff = sx ^ sy;        /* 1 if signs differ */
  int sub = y + (~x + 1);    /* y - x */
  int ssub = sub >> 31;      /* sign of (y - x) */

  /* If signs differ and x negative, x <= y. Else if signs same, check y-x >= 0 */
  int res = (diff & sx) | ((!diff) & (!ssub));
  return !!res;
}

intLog2函数

函数要求:

函数名 intLog2
参数 int
功能实现 计算log2(x)并向下取整

实现分析:

这个问题的实质是找最高位的1在哪一位

二分法查找最高位的方式来实现:

设一个变量 ans = 0,表示当前找到了最高位的下标。

从高位向低位检测是否有 1:

  • 如果 x >> 16 不为 0 → 说明最高位至少在 [16, 31],那么 ans += 16
  • 再检测 x >> 8 不为 0 → ans += 8
  • 再检测 x >> 4 不为 0 → ans += 4
  • 再检测 x >> 2 不为 0 → ans += 2
  • 再检测 x >> 1 不为 0 → ans += 1

ans 就是 ⌊log₂(x)⌋

函数实现:

复制代码
int intLog2(int x) {
  int ans = 0;
  int b16, b8, b4, b2, b1;

  /* check top 16 bits */
  b16 = !!(x >> 16) << 4; /* if high 16 bits set, add 16 */
  ans += b16;
  x >>= b16;
  /* check top 8 bits */
  b8 = !!(x >> 8) << 3;
  ans += b8;
  x >>= b8;
  /* check top 4 bits */
  b4 = !!(x >> 4) << 2;
  ans += b4;
  x >>= b4;
  /* check top 2 bits */
  b2 = !!(x >> 2) << 1;
  ans += b2;
  x >>= b2;
  /* check top 1 bit */
  b1 = !!(x >> 1);
  ans += b1;
  return ans;
}

floatAbsVal函数

函数要求:

函数名 floatAbsVal
参数 unsigned
功能实现 返回浮点数f的绝对值
要求 可以使用运算符,

实现分析:

  • 取绝对值:
    清除符号位即可(符号位在最高位)。
    uf & 0x7FFFFFFF
  • 检测 NaN:
    当指数全为 1(e = 0xFF000000)且尾数不为 0(f != 0)时,是 NaN。

函数实现:

复制代码
unsigned floatAbsVal(unsigned uf) {
    unsigned mask = 0x7FFFFFFF;
    unsigned abs = uf & mask;
    if (abs > 0x7F800000)
        return uf;
    return abs;
}

floatScale1d2函数

函数要求:

可以使用 integer/unsigned, operations, ||, &&. also if, while

函数名 floatScale1d2
参数 unsigned
功能实现 返回0.5*f

功能:

返回与表达式 0.5 * f位级等价的无符号整数。

  • 输入 uf 是一个单精度浮点数的位模式
  • 输出同样是一个位模式;
  • ufNaN,则返回原值。

实现分析:

  • 若输入为INF和NaN,那么直接返回
  • 若exp>1,保留小数位然后直接把exp-1,阶码也就减少1,达到了*0.5的目的
  • exp<=1,此时若直接把exp-1,那么exp将会小于等于0,实现不了f*0.5;
  • 所以将uf右移1位实现"exp-1"(若exp=1那么将会得exp=0,若exp=0那么exp的值不变)和小数部分的f*0.5。
  • 但是在这里要注意小数的舍入问题,若f的第0位和第1位都是1,那么需要给他加2。

函数实现:

复制代码
unsigned floatScale1d2(unsigned uf) {
    int exp = (uf & 0x7fffffff)>>23;
    int sign = uf & 0x80000000;
        if((uf&0x7fffffff) >= 0x7f800000) return uf;
    if(exp > 1)    return (uf&0x807fffff)|(--exp)<<23;
    if((uf&0x3) == 0x3) uf = uf + 0x2;
    return ((uf>>1)&0xbfffffff)|sign;
}

floatFloat2Int函数

函数要求:

可以使用 integer/unsigned, operations, ||, &&. also if, while

函数名 floatFloat2Int
参数 unsigned
功能实现 将f转换为int形式

实现分析:

  • 提取符号位 S、指数 E 和尾数 F
  • 计算真实指数 exp = E-127
  • 恢复尾数隐含 1 → M
  • 特殊情况:
    • E=255 → NaN/∞ → 返回 0x80000000
    • exp < 0 → |f|<1 → 返回 0
    • exp > 30 → 超出 int → 返回 0x80000000
  • 根据 exp 调整尾数得到整数部分:
    • exp > 23 → 左移
    • exp ≤ 23 → 右移
  • 根据符号返回正负整数

函数实现:

复制代码
int floatFloat2Int(unsigned uf) {
  unsigned sign = uf >> 31;
  int exp = ((uf >> 23) & 0xFF) - 127;
  unsigned frac = uf & 0x7FFFFF;
  unsigned M = frac | 0x800000;
  unsigned val;

    if (((uf >> 23) & 0xFF) == 0xFF) 
        return 0x80000000;

    if (exp < 0) 
        return 0;

    if (exp > 30)
        return 0x80000000;

  if (exp > 23) {
    val = M << (exp - 23);
  } else {
    val = M >> (23 - exp);
  }

  return sign ? -val : val;
}
相关推荐
ad钙奶长高高2 小时前
【C语言】初始C语言
c语言·开发语言·算法
侯小啾2 小时前
【17】C语言-gets() 与 fgets() 函数
c语言·开发语言
胡桃夹夹子3 小时前
存档111111111
java·开发语言
不会编程的小寒3 小时前
C++ 中string的用法
java·开发语言
想搞艺术的程序员3 小时前
Go Error 全方位解析:原理、实践、扩展与封装
开发语言·后端·golang
闲人编程3 小时前
Python游戏开发入门:Pygame实战
开发语言·python·游戏·pygame·毕设·codecapsule
是苏浙4 小时前
零基础入门C语言之枚举和联合体
c语言·开发语言
报错小能手4 小时前
C++笔记(面向对象)静态联编和动态联编
开发语言·c++·算法