CSAPP读书笔记--信息的表示和使用(一)

本文是CSAPP读书笔记的第一部分,主要探讨信息的表示和使用,特别是整型数据的表示。内容涵盖字节、字、数据长度、字节序、运算(位级、逻辑、移位)以及整型数据(无符号数和有符号数的编码、转换)。文章详细解释了补码、无符号数的转换规则,并讨论了不同字长类型转换的零扩展和截断位操作,以及整数运算如加法和乘法的实现。
摘要由CSDN通过智能技术生成

CSAPP读书笔记–信息的表示和使用(一)

  CSAPP的part 1主要介绍程序的结构和执行,而第二章作为PART 1的第一个章节,主要讲述的是信息的表示和使用,这部分的主要内容包括:
在这里插入图片描述

  本文主要讲一下整型数据的部分,下一篇文档继续介绍浮点型数据。

信息存储

字节(Byte)

  信息在内存中的存储并不是以单个bit为单位的,而是以8bit的块为单位,这个8bit的块,就称为字节(Byte)。因此,字节也是内存寻址的最小单位。通常我们在表示byte数据的时候使用16进制, 也就是我们用16进制来写bit模式。
  操作系统给每个进程提供了虚拟内存的抽象,让进程都能访问从相同地址开始的、连续的虚拟内存空间,每个内存单位都有唯一的编码进行标识,这个编码称为地址(Address),而所有地址的集合就构成了虚拟内存空间。那虚拟内存空间究竟有多大是由什么来决定的呢?这主要取决于计算机的一个参数——字长(Word Size)。什么是字长?什么又是字呢?如下。

字(word)

  我们可以将若干个字节当做一个块,称为字(Word),而这里的字节的数目就是字长。字长指明了指针数据的标称大小(Nominal Size),而指针指向虚拟内存空间,它的位数就决定了它能索引多大的空间,由此也就规定了虚拟内存空间的最大大小。所以虚拟空间的最大大小由字长决定。
  当前大部分计算机的字长都是32bit或64bit,而程序可以通过不同的编译指令将其编译成32位程序或者64位程序(程序的字长是由编译决定的),其中32位机器可以运行32位程序,但是不能运行64位程序,而64位机器可以运行32位程序和64位程序,即64位机器是兼容32位程序的。

数据长度

  对于不同的数据类型,它实际占用的字节数,也跟编译器将程序编译成32位还是64位有关。C语言中常见数据类型所占字节数在32位程序和64位程序中的对比如下:

在这里插入图片描述

字节序

  当存储多字节的目标程序或数据时,通常需要考虑两点:① 目标的地址是什么?② 应该怎样在内存中去排列各字节的数据。目标的地址指的就是字节序列的最低地址,而对于字节排列顺序,通常有两种方式:大端模式(big endian)和小端模式(little endian)。其中,大端模式指的是将较高位的有效字节存储在较低地址的内存中,而小端模式则是将较高位的有效字节存储在较高的内存地址中。举个例子,若现在需要将数据0x01234567,存储在内存地址0x100-0x103中,采用大端模式和小端模式的存储方式如下:

在这里插入图片描述

  不同的机器支持不同的字节序,如多数Intel的机器只支持小端模式,相反,IBM、Oracle的多数机器则只支持大端模式。此外,也有如ARM的微处理器支持两种字节序,可通过配置决定采用哪种字节序。通常来讲,一旦选定了操作系统,字节顺序也就固定了。如arm的处理器,虽然支持双端模式,但在上面使用安卓或IOS系统,就只能支持小端模式。
  一般而言,机器所使用的的字节顺序是不可见的,但是在有些情况下需要注意字节顺序带来的影响:

  • 在两个不同类型的机器之间通过网络传输数据时,如果这两个机器使用了不同的字节顺序,就会造成问题。所以网络应用程序的代码编写必须遵守已建立的关于字节顺序的规则, 确保发送方机器将它的内部表示转换成网络标准,而接收方机器将网络标准转换为它自己的内部表示。
  • 当我们通过反汇编器得到可执行程序的指令序列时,字节顺序也很重要。

运算

位级运算

  C中的整型数据类型是使用二进制数进行编码的,二进制数可以对应为布尔型的位向量,所以它可以支持按位的布尔运算,比如|表示OR、&表示AND、~表示NOT、^表示XOR。一个常见的应用举例:

//如flags代表某个feature支持的所有属性,它的每个bit对应某个特定的flag,每个bit对应flag定义如下:
#define flag_XX1 0x1
#define flag_XX2 0x2
#define flag_XX3 0x4
...
#define flag_XX4 0x80

//若想判断某个flag是否被置上,比如flag_XX3
if (flags & flag_XX3){
    //content
}

//若要置上某个标志位,比如flag_XX3
flags = flags | flag_XX3;

//若要清除某个flag, 如flag_XX3  
flags = flags & (~flag_XX3);

  此外,位级运算的另一个常见用法是进行严密运算,比如对int型数据a,提取最低有效字节为a&0xFF,保留除了最低有效字节以外的字节为a&~0xFF。

逻辑运算

  C提供了一组逻辑运算符||、&&和!分别对应于命题逻辑中的OR、AND和NOT运算。要注意逻辑运算和位级运算的区别,在逻辑运算中,只要是非零的数据就表示为TRUE,全零的数据就表示为FALSE,所以计算时候先将其转换为TRUE和FALSE,然后计算出来的结果只会是0x00或0x01,分别对应FALSE和TRUE。
  逻辑运算中一个值得关注的点,叫做early termination。它主要针对逻辑与运算和或运算。对于与运算,“一假则假”,如a&&b, 其中a、b分别为两个需要计算的表达式,若计算出a为flase, 则a&&b必为flase,b表达式将不会被执行和计算;同理,对于或运算,“一真则真”,如a||b, 若a计算结果为true,则a||b必为true,b将不会被执行。

移位运算

  C语言支持移位运算,具体分为左移和右移。

  • 左移:整型数x执行左移k位,即x << k,执行过程是,x按bit左移k位,最高的k位会被丢弃,然后在最右边补上k个0。
  • 右移:右移包括逻辑右移和算术右移。对x >> k,两种右移的共同点是,将整型数x按bit右移k位,并丢弃最低的k位,但是高位补齐方式不同。逻辑右移是直接用0进行高位补齐,而算术右移则是用符号位对高位进行补齐,具体而言,对有符号数,最高位为0表示正数,则右移高位用0补齐,若最高位为1,则为负数,高位用1补齐.

整型数据的表示

  整型数据包括无符号型和有符号型,无符号型只能表示非负数,而有符号型则既可以表示非负数,也可以表示负数。

无符号数的编码

  假设一个无符号整数x为w bit, 我们可以将它表示为一个向量 [ x w − 1 , x w − 2 , … , x 0 ] \left[x_{w-1}, x_{w-2}, \ldots, x_{0}\right] [xw1,xw2,,x0],作为x的二进制表示。定义一个函数 B 2 U w B 2 U_{\mathrm{w} } B2Uw(binary to unsigned)表示将二进制转换为无符号数,其函数定义为:
B 2 U w ( x ) = ∑ i = 0 w − 1 x i 2 i (1) B2{U_{\rm{w} } }(x) = \sum\limits_{i = 0}^{w - 1} { {x_i}{2^i} } \tag{1} B2Uw(x)=i=0w1xi2i(1)
  根据公式可知,相当于二进制转为十进制:

  • 最小值 U M i n w UMi{n_w} UMinw:[0,0,…,0],即0;
  • 最大值 U M a x w UMa{x_w} UMaxw:[1,1,…,1],即 ∑ i = 0 w − 1 2 i = 2 w − 1 \sum_{i=0}^{w-1} 2^{i}=2^{w}-1 i=0w12i=2w1

有符号数的编码

  有符号数有很多不同的编码方式,比如补码(two’s complement)、反码(one’s complement)和原码(sign-magnitude)。其中最常见的是补码编码,C语言标准没有要求要用何种形式的编码来表示有符号整数,但是几乎所有机器都会使用补码编码。
  同样的,我们定义一个函数( B 2 T_{w} )表示二进制转化为补码编码的有符号数,定义如下:
B 2 T w ( x ) = − x w − 1 2 w − 1 + ∑ i = 0 w − 2 x i 2 i (2) B2{T_w}(x) = - {x_{w - 1} }{2^{w - 1} } + \sum\limits_{i = 0}^{w - 2} { {x_i}{2^i} } \tag{2} B2Tw(x)=xw12w1+i=0w2xi2i(2)
  如何理解这个公式呢?实际上,对于补码形式,最高位 x w − 1 x_{w-1} xw1为符号位,且赋予符号位一个权重 − 2 w − 1 -2^{w-1} 2w1

  • 最小值 T M i n w TMi{n_w} TMinw:[1,0,…,0],结果为 − 2 w − 1 -2^{w-1} 2w1
  • 最大值 T M a x w TMa{x_w} TMaxw:[0,1,…,1],结果为 ∑ i = 0 w − 2 2 i = 2 w − 1 − 1 \sum_{i=0}^{w-2} 2^{i}=2^{w-1}-1 i=0w22i=2w11
  • -1:[1,1,…,1],与 U M a x w UMa{x_w} UMaxw的二进制相同。

  我们可以看到补码编码的范围是不对称的,且 ∣ T M i n w ∣ = ∣ T M a x w ∣ + 1 |TMi{n_w}| = |TMa{x_w}| + 1 TMinw=TMaxw+1, 这是因为补码的编码的形式,通过设置符号位,使负数和非负数各占一半的编码范围,而非负数包含正数和0,因此负数比正数多一个。

补充:前面提到,有符号数也可以使用反码(one’s compliment)编码或原码(Sign magnitude)编码,关于反码和原码的定义如下:
反码:跟补码编码方式类似,只是符号位的权重变成了 − ( 2 w − 1 − 1 ) -\left(2^{w-1}-1\right) (2w11),之所以叫反码,是因为对负数而言,除符号位以外的部分其实就是将负数绝对值的二进制编码取反。反码值的计算公式为:
B 2 O w = − x w − 1 ( 2 w − 1 − 1 ) + ∑ i = 0 w − 2 x i 2 i (3) B 2 O_{w}=-x_{w-1}\left(2^{w-1}-1\right)+\sum_{i=0}^{w-2} x_{i} 2^{i}\tag{3} B2Ow=xw1(2w11)+i=0w2xi2i(3)
原码:最高位为符号位,其余位为绝对值的二进制编码。计算公式如下:
B 2 S w = ( − 1 ) x w − 1 ⋅ ∑ i = 0 w − 2 x i 2 i (4) B 2 S_{w}=(-1)^{x_{w-1} } \cdot \sum_{i=0}^{w-2} x_{i} 2^{i}\tag{4} B2Sw=(1)xw1i=0w2xi2i(4)
原码和反码对于数字0,均有两种编码方式,这也许就是几乎所有机器都用补码表示有符号数的原因。

有符号数和无符号数之间的转换

  C语言允许在各种不同的数据类型之间做强制转换,它的具体实现要从位级角度来看,它保持每bit的值不变,只是改变了解释这些位的方式。

补码转换为无符号数

  对于补码而言,当有符号数为正数时,其补码与其对应的无符号数相同,当有符号数为负数时,区别就在于符号位,只需要将符号位转换为无符号数的最高位数值即可。设补码为x,其二进制序列为 [ x w − 1 , x w − 2 , . . . , x 0 ] [{x_{w - 1} },{x_{w - 2} },...,{x_0}] [xw1,xw2,...,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值