CSapp-DataLab——深入理解函数实现原理

这篇具有很好参考价值的文章主要介绍了CSapp-DataLab——深入理解函数实现原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

文章一
文章二
文章三

相比于上面这些参考文章,我增加了更多的个人理解在其中,可以互为参考。

函数分析

bitXor函数

函数要求:

函数名 bitXor
参数 int , int
功能实现 x^y
要求 只能使用 ~ 和 | 运算符,将结果返回。

实现分析:

异或的最初形态是 x ⊕ y = ( x ∧ ! y ) ∨ ( ! x ∧ y ) x\oplus y=(x\land !y)\lor(!x\land y) xy=(x!y)(!xy),因为只允许使用或,所以使用Demorgan定律将与转化为非和或。

函数实现:文章来源地址https://www.toymoban.com/news/detail-723567.html

int bitXor(int x, int y) {
  // 代码实现
	return ~(~(~x & y) & ~(x & ~y));
}

getByte函数

实现分析:

因为要将x逐字节位移,所以要通过(n<<3)实现将一次位移放大到一字节的宽度。最后用0xff做低8位(1Byte)掩码。

函数实现:

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

logicalShift函数

实现分析:

对于int型这种有符号数,右移是算术右移。
要实现逻辑右移,可以在算数右移的基础上,增加一个掩码,比如右移n位,那就是n个0以及32-n个1

具体的构造方法,是先把1左移31位,变成0x80000000,然后取反变成0x7fffffff,对这个数右移n位,前面总共会有n+1个0,此时再左移1位,前面变成n个0,最后一位变为0,所以使用或运算将最后一位变成1.

其实我还用过另一个思路:mask=((~(1<<31)<<1)|1)>>n;
这个思路失败了,由此可以得出,C语言中常数是以补码形式存在的,进行算术右移。

函数实现:

int logicalShift(int x, int n) {
	int mask;
	x>>=n;
	mask=(((~(1<<31))>>n)<<1)|1;
	return x&mask;
}

bitCount函数

实现分析:

本题采取二分法进行加和,具体思路如下:

从最开始,构造32位的0x55555555掩码:

mask_1=0x55|(0x55<<8);
mask_1=mask_1|(mask_1<<16);//构造32位mask

此掩码经过x=(x&mask_1)+((x>>1)&mask_1);的操作,可以将2bit中的1的个数变成2bit形式的二进制数字。

上一步是从1到2,同理,还可以从2到4,使用0011为一个单位,构造0x33333333掩码,同样经过 x=(x&mask_2)+((x>>2)&mask_2);的操作,就可以实现将4bit中的1的个数转化为4bit的二进制数字。

由此递推,4到8,8到16,16到32,都是如此。

需要指出的是,从4到8开始,就不需要分开进行掩码操作了,因为随着二进制位的增加,其可以表达的数字已经远超出二进制位的总和,即高位已经不可能是1了,所以掩码不会影响到数字。

函数实现:

int bitCount(int x) {
  int mask_1,mask_2,mask_4,mask_8,mask_16;
  mask_1=0x55|(0x55<<8);
  mask_1=mask_1|(mask_1<<16);//构造32位mask
  mask_2=0x33|(0x33<<8);
  mask_2=mask_2|(mask_2<<16);
  mask_4=0x0f|(0x0f<<8);
  mask_4=mask_4|(mask_4<<16);
  mask_8=0xff|(0xff<<16);
  mask_16=0xff|(0xff<<8);//不需要进行额外的移位了

  x=(x&mask_1)+((x>>1)&mask_1);
  x=(x&mask_2)+((x>>2)&mask_2);
  //到这里不可能超出了,所以直接相加就好
  x=(x+(x>>4))&mask_4;
  x=(x+(x>>8))&mask_8;
  x=(x+(x>>16))&mask_16;

  return x;
}

conditional函数

实现分析:

实现选择,需要构造一个全是0或者全是1的掩码,然后通过按位异或操作来进行选取。

构造时,先使用二连!!,将一个数字转换成32位的bool格式,左移31位右移31位,如果是非零,那就全是1,0就全是0.

函数实现:

int conditional(int x, int y, int z) {
	int mask=((!!x)<<31)>>31;//~(!!x)+1;
	return (mask&y)|(~mask&z);
}

tmin函数

实现分析:

在补码的世界中,最小的补码是-1:0xffffffff,随着绝对值逐渐增大,后面代表数字的部分逐渐递减,绝对值最大的负数(最小的负数)就是除符号位以外全是0的数。

给出最低补码,即构造0x80000000;

函数实现:

int tmin(void) {
	return 1<<31;
}
  1. fitBits函数

实现分析:

代码很直观,就是先确定移位数量,然后左移再右移,比较前后是否发生变化。关键在于,为什么左移32-n右移32-n后如果两个32位数不相等就说明不可以呢?

原因在于,超出以后会发生两种异常:符号位是否能保留,以及高位有效位是否能保留。

先说符号位保留问题:正数符号位为0,比如n=3,正数3在补码中的低3位就是011,2就是010,1就是001,0就是000,有没有符号位一定是0,一旦超出上限,比如变成4,就会变成0100的形式,符号位被破坏,那么移位32-n后就会导致高位补1,完全就不一样了。

那继续增加呢?符号位是会回来,但是你的高位有效位没了。

5就是0101,6就是0110,7就是0111,8就是01000,对于8来说,左移32-n就会变成0,右移回来第三位虽然是0,但是但是已经丢失了高位信息了。又比如10是01010,最后低4位会变成0010,不再是原来的32位数了。

总的来说,从32位角度来看,如果想表示的数超出了低n位表示的能力,左移32-n位后再右移回来,无非两个结果:高位全变成1或者丢失超出的部分。

函数实现:

int fitsBits(int x, int n) {
	int shift_number=32+(~n+1);//32-n
	int x_shifted=x<<shift_number>>shift_number;
	return !(x_shifted^x);
}

dividePower2函数

实现分析:

补码 2 k 2^k 2k除法,如果是正数,直接移k位,但是负数会出现向下取整的情况,为了实现趋零截断,需要对负数加一个特定的偏置,即 + 2 w − 1 +2^w-1 +2w1

判断正数和负数,可以通过类似于前面conditional的全0/全1mask实现,本次直接用符号位构造mask。

如果是正数,mask就全是0,bias不生效,如果是复数,mask都是1,bias生效。

函数实现:

int dividePower2(int x, int n) {
  int mask=x>>31;/*00000000 or 1111111*/
  int bias=mask&((1<<n)+(~0));
  return (x+bias)>>n;
}

negate函数

实现分析:

如代码,不仅是正转负是按位取反+1,负转正也是这样,很神奇,实际上有离散数学作为理论支持,这是必然结果。

函数实现:

int negate(int x) {
	return ~x+1;
}

howManyBits函数

实现分析:

补码的最高表示位前面的所有数字应该都是全0(正数)或是全1(负数)。
将原数与左移一位后的数字异或则最高的不同的那个位置为1,我们只需要找到这个最高的不同位即可。这个信息储存在temp中。

之后就是用二分法去测试最高位在哪里,比如bit_16=!!(temp>>16)<<4;结果不为0,说明最高非零位至少在16位以后,我们进一步将高16位留下观察。反之,如果为0,说明最高位介于0和16之间,于是不做处理,等的后面8位处理进行判断。

由此,通过二分法得到若干shift值,有的是16,8这类数,有的就是0,最后加起来,给一个+1的偏置就可以得到需要的位数。

函数实现:

int howManyBits(int x) {
	int temp=x^(x<<1);
	int bit_16,bit_8,bit_4,bit_2,bit_1;
	bit_16=!!(temp>>16)<<4;
	temp=temp>>bit_16;
	bit_8=!!(temp>>8)<<3;
	temp=temp>>bit_8;
	bit_4=!!(temp>>4)<<2;
	temp=temp>>bit_4;
	bit_2=!!(temp>>2)<<1;
	temp=temp>>bit_2;
	bit_1=!!(temp>>1);//no need to shift back

	return bit_1+bit_2+bit_4+bit_8+bit_16+1;//1 offset
}

isLessOrEqual函数

实现分析:

比较大小可以转换为作差。但是如果是异号,就可能出现溢出,所以异号要单独拿出来分析,同号就可以直接作差。

最后将两种情况合并到return式子里,用或综合返回。

函数实现:

int isLessOrEqual(int x, int y) {
	int sign_x=(x>>31)&1;
	int sign_y=(y>>31)&1;
	int sign_y_x=((y+(~x+1))>>31)&1;
	return (!(sign_x^sign_y)&!sign_y_x)|((sign_x^sign_y)&sign_x);//
}

intLog2函数

实现分析:

x是大于0的,所以是原码形式,是简化版的howManyBits。

函数实现:

int intLog2(int x) {
  int temp=x;
  int bit_16,bit_8,bit_4,bit_2,bit_1;
  bit_16=!!(temp>>16)<<4;
  temp=temp>>bit_16;
  bit_8=!!(temp>>8)<<3;
  temp=temp>>bit_8;
  bit_4=!!(temp>>4)<<2;
  temp=temp>>bit_4;
  bit_2=!!(temp>>2)<<1;
  temp=temp>>bit_2;
  bit_1=!!(temp>>1);//no need to shift ba
  return bit_1+bit_2+bit_4+bit_8+bit_16;
}

floatAbsVal函数

实现分析:

浮点数看这篇:
浮点数基础

E=0xffff且M=0为Inf,E=0xffff且M!=0为Nan:Nan,Inf判断

首先通过掩码,抓取8位阶码,如果是特殊值,就直接返回,否则把符号位置0就可以。

函数实现:

unsigned floatAbsVal(unsigned uf) {
	if((uf&0x7f800000)>>23==255 && uf<<9)
		return uf;//uf<<9!=0
	return uf&0x7fffffff;
}

floatScale1d2函数

实现分析:

首先取出符号位和阶码。先判断是否是特殊值,如果不是就进行下一步规范化判断。
规范化的指数至少为2,否则就是exp==1和0的时候,这个时候会考虑舍入。

如果E>1,说明是规范化值,直接用掩码把x的阶码屏蔽,使用移位后的阶码覆盖即可。
反之,说明是不规范的极小值,考虑舍入。

floatAbsVal会考虑是否规范化。关于非规范化:规范化参考

函数实现:

unsigned floatScale1d2(unsigned uf) {
	int E = (uf&0x7f800000) >> 23;
	int S = uf&0x80000000;//sign
	//int S = uf&0x80000000;
	if((uf&0x7fffffff) >= 0x7f800000) //Nan Inf
		return uf;
	if(E>1) //regular
		return (uf&0x807fffff)|(--E)<<23;
	else //irregular E==1 E==0
	{	
		if((uf&0x3)==0x3)
			uf+=0x2;
		return ((uf>>1)&0x007fffff)|S;
	}	
}

floatFloat2Int函数

实现分析:

把浮点数转int,首先取出符号位和阶码(将移码转换为真实数),以及尾数。

如果E太大,就会溢出,如果E小于0,说明真实值介于-1和1之间,趋零截断就会变成0.

如果正常,就要令尾数与科学计数法的宽度统一,如果E更大,就把尾数左移,E小就把尾数右移。

最后根据符号位返回正负值。

函数实现:

int floatFloat2Int(unsigned uf) {
	int sign=uf>>31;//unsigned logical shift
	int E=((uf&0x7f800000)>>23)-127;//8-bits exp
	int frac=(uf&0x007fffff)|0x00800000;//frac and add the 1(regular)
	if(E>=32)
	{
		return 0x80000000;//overflow up
	}
	else if(E<0)
	{
		return 0;//between -1,1
	}
	else
	{
		if(E>=23)//more than frac, pow 2
		{
			frac<<=(E-23);
		}
		else//less than frac,pow
		{
			frac>>=(23-E);
		}
		return sign?-frac:frac;
	}
}

到了这里,关于CSapp-DataLab——深入理解函数实现原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《深入理解计算机系统(CSAPP)》第6章 存储器层次结构 - 学习笔记

    写在前面的话:此系列文章为笔者学习CSAPP时的个人笔记,分享出来与大家学习交流,目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记,在复习回看时发现部分内容存在一些小问题,因时间紧张来不及再次整理总结,希望读者理解。 《深入理解计算机

    2024年02月06日
    浏览(47)
  • 深入理解DES算法:原理、实现与应用

    title: 深入理解DES算法:原理、实现与应用 date: 2024/4/14 21:30:21 updated: 2024/4/14 21:30:21 tags: DES加密 对称加密 分组密码 密钥管理 S盒P盒 安全性分析 替代算法 历史 DES(Data Encryption Standard)算法是由IBM研发,并于1977年被美国国家标准局(NBS,现NIST)确定为数据加密标准。 设计目

    2024年04月14日
    浏览(78)
  • 函数探秘:深入理解C语言函数,实现高效模块化编程

    ✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C语言学习 贝蒂的主页:Betty‘s blog 在数学中我们就知道了函数这个概念,而C语言同样引入了函数这个概念,那C语言的函数到底是什么样的呢? 在C语言中, 函数也叫子程序,它是一段可以

    2024年03月09日
    浏览(65)
  • 深入理解 python 虚拟机:字节码教程(3)——深入剖析循环实现原理

    在本篇文章当中主要给大家介绍 cpython 当中跟循环相关的字节码,这部分字节码相比起其他字节码来说相对复杂一点,通过分析这部分字节码我们对程序的执行过程将会有更加深刻的理解。 我们使用各种例子来理解和循环相关的字节码: 上面的代码对应的字节码如下所示:

    2023年04月15日
    浏览(37)
  • 【排序算法】深入理解快速排序算法:从原理到实现

    目录 1. 引言 2. 快速排序算法原理 3. 快速排序的时间复杂度分析 4. 快速排序的应用场景 5. 快速排序的优缺点分析 5.1 优点: 5.2 缺点: 6. Java、JavaScript 和 Python 实现快速排序算法 6.1 Java 实现: 6.2 JavaScript 实现: 6.3 Python 7. 总结        快速排序是一种经典的排序算法,它的

    2024年03月20日
    浏览(45)
  • 深入理解回调函数qsort:从入门到模拟实现

    💓博客主页:江池俊的博客 ⏩收录专栏:C语言进阶之路 👉专栏推荐:✅C语言初阶之路 ✅数据结构探索 💻代码仓库:江池俊的代码仓库 ​🎪 社区:GeekHub社区 ​ 🎉欢迎大家点赞👍评论📝收藏⭐ 回调函数和 qsort 是 C语言编程中重要的概念,它们为我们提供了强大的工具

    2024年02月10日
    浏览(50)
  • 【并发编程】深入理解Java并发之synchronized实现原理

    分析: 通过 new MyThread() 创建了一个对象 myThread ,这时候堆中就存在了共享资源 myThread ,然后对 myThread 对象创建两个线程,那么thread1线程和thread2线程就会共享 myThread 。 thread1.start() 和 thead2.start() 开启了两个线程,CPU会随机调度这两个线程。假如 thread1 先获得 synchronized 锁,

    2024年02月04日
    浏览(59)
  • 【JUC系列-01】深入理解JMM内存模型的底层实现原理

    JUC系列整体栏目 内容 链接地址 【一】深入理解JMM内存模型的底层实现原理 https://zhenghuisheng.blog.csdn.net/article/details/132400429 【二】深入理解CAS底层原理和基本使用 https://blog.csdn.net/zhenghuishengq/article/details/132478786 【三】熟练掌握Atomic原子系列基本使用 https://blog.csdn.net/zhenghuis

    2024年02月12日
    浏览(44)
  • 深入理解 STM32 串口空闲中断的工作原理与实现方法

    STM32 微控制器的串口空闲中断是一种重要的通信机制,用于处理数据接收方面的任务。 本文深入解析了 STM32 串口空闲中断的工作原理,包括触发条件和中断服务函数的实现方法,并给出了相应的代码示例。 STM32 微控制器的串口通信是嵌入式系统中常见的通信方式之一。为了

    2024年02月01日
    浏览(45)
  • 深入理解python虚拟机:调试器实现原理与源码分析

    调试器是一个编程语言非常重要的部分,调试器是一种用于诊断和修复代码错误(或称为 bug)的工具,它允许开发者在程序执行时逐步查看和分析代码的状态和行为,它可以帮助开发者诊断和修复代码错误,理解程序的行为,优化性能。无论在哪种编程语言中,调试器都是一

    2023年04月26日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包