【第 2 周】深入理解计算机系统共读心得体会

请大家以回帖的方式将你在共读第一周的心得体会用你自己的话表达出来。

样例:

所在小组

第一组

组内昵称

张三

你的心得体会

可以基于不同知识点进行,有更新请在原回贴更新,每人每周只发一个帖子

一段自己的阐述

第二段自己的阐述

所在小组

第七组

组内昵称

杨文

你的心得体会

  1. 汇编语言与编程语言面向的所有者是不一样的,一个是机器,一个是人,那么他的可解释性也会不同。
  • 汇编是机器代码文本
  • 编程语言是人类语言文本
  1. 我们在使用第三方库的时候遇到性能问题一般都会尝试去看看源代码实现,类似的编译源代码后也应该去看最终执行的汇编代码,所以在一定范围内能阅读它就尤为重要了。
  • 库 与 汇编
  1. 学习知识只是了解一般规则或者浅层次上的,那只能是扩展知识面,不叫学习。
  • 一味的追求量,而不是质,那只是自欺欺人
  • 花时间研究示例、完成课后练习题,并对照答案来检验和巩固你的作答,非常有必要。

所在小组

第3组

组内昵称

h0n9xu

心得体会

本周任务是完成第二章;其中2.1信息存储本应该上周完成,其他内容包括2.2整数表达形式,2.3整数运算,2.4浮点数。这章蛮难的,以后还要多看。

  • 冷知识:大端小端原来来自于《格列佛游记》。Intel和兼容Intel的机器大多数采用小端。最近出现的微处理器芯片是双端的(bi-endian),但当操作系统确定之后大端小端也就确定了;Android和iOS都用小端。大端小端可以用这幅漫画说明。一般大端小端对应用程序(员)是不可见的,除非遇到不同机器的网络传输、查看整型表现形式(如读反汇编地址)、程序语言暴露了非正常类型系统使用(如c语言中union或cast的使用)的情形。文本数据比二进制数据更为平台无关。
  • 香农很牛逼,硕士论文里建立了布尔代数和数字逻辑之间的关系,另一个我知道的硕士学位的大神是 J. Roger Hindley,就是那个Hindley-Milner类型系统的Hindley。
  • 机器码层次上不存在有符号、无符号之说,当然更不存在结构体了——除了指令支持的类型,其他一概都是高层语言带来的抽象。
  • c语言规范很多都没有指明,太多undefined behavior,造成了难以挽回的损失。光是integer-overflow就能够程序员吃一壶了,更不用谈NPE这种大杀器。为了照顾各种不同的底层设备或驱动的vendor,我个人觉得当时制定规范的时候有点欠考虑,c真不是一个现代化的高级语言。比如,int需要保证的范围是[−32767,+32,767],居然没法保证它是4字节的;在我看来,int/long这类类型就不应该再被使用了,现代编程语言Rust和Go这类使用i32/int32这种才是王道。
  • 补码是最适合计算机的数的编码。对补码的加减乘除都很有巧妙,比较适合逻辑门电路计算;不过不太好写出来。我觉得重点是牢记取模运算,以及减数、乘法基于加法,除法基于乘法。
  • integer-overflow一般是CWE-190。关于integer-overflow的检测,2.3细细讲了不少rules;c/c++语言中比较常用的是LLVM提供的ubsan来进行检测,这个是由utah大学的ICSE2012的Understanding Integer Overflow in C/C++提到的方法得来的(值得骄傲的是,其中二作李鹏是中国人,目前在字节跳动当安全研究员)。当然c/c++中比较安全的办法是用GCC/LLVM相关可以检测overflow的扩展API,gccLLVM。其他:Rust在debug模式下(debug_assert )检查integer-overflow,Go不检查。文中讲了两个integer-overflow带来的安全问题(FreeBSD-SA-02:38.signed-error和CA-2002-25),CVE中由它导致的问题还蛮多的。
  • IEEE 754规定了浮点数的表现形式:32位1+8+23,64位1+11+52,V=(-1)^s x M x 2^E。下面:point_down:是32位的几种浮点形式。
3 个赞

所在小组

第一组

组内昵称

sadame

心得体会

1.逻辑运算与移位运算的区别
2.C语言中int在32/64位程序中都是32位,区别只是long的长度
3.java只支持有符号整数,并且区分逻辑右移和算术右移。因为C中的有符号到无符号的隐式转换会导致错误
4补码的除法中可以利用偏置量来决定答案是向上舍入还是向下舍入
5.阶码在32位数中为8位,在64位数中为11位,阶码中偏置值在32位中是127,64位数中是1023,
E=(e_ke_k-1…e_0)阶码表示的无符号数-(2^k-1)偏移量
只能表示有理数V=符号位×2^E×M
E和M的值在E为全0时会发生变化 ,E全1时为无穷或NaN

1.所在小组:第七组
2.组内昵称:吴奇驴
3.心得体会

  • 整数位右移分逻辑右移和算术右移。
  • IEEE浮点数表示分三种,第三类又分两个变种: 规格化的、非规格化的、无穷大、NaN, 通过s、exp、frac进行组合表示。
  • 浮点数舍入使用“向偶数舍入”的方式,在大多数情况下可以避免了一组数据的平均值统计偏差。
  • 编译-汇编-链接
  • C语言-汇编代码-机器码
  • AT&T和Intel汇编区别:
    o Intel省略了指示大小的后缀;
    o Intel省略了寄存器名字前面的“%”符号;
    o Intel用不同的方式来描述内存中的位置;
    o 两者源操作数和目的操作数位置相反;

1.所在小组:第六组
2.组内昵称:黄永平
3.心得体会:

  • 整数运算
    采用补码表示有符号数的优点,除了零值是唯一的,更重要的是,有符号数与无符号数的算术执行可以采用相同的位级实现。也就是说,位运算时可以不必区分有符号数或无符号数,执行相同的位级操作。只是运算操作之后,对同样的位模式分别采用不同的位级解析成有符号或无符号数值。有符号数的表示方式,除了补码外,还有反码、原码。但最终都选择补码是有原因的。

  • 浮点数
    浮点数的表示,分别由符号S、尾数M、阶码E三部分组成,其中符号S是首个bit,通过该bit标示有符号位,也就是说浮动数是采用原码表示。尾数M是二进制小数,随阶码E的取值,决定是否隐式包含1。而阶码E的取值,将浮点数分为三类:规格化、非规格化、无穷大或NaN。浮点数是经过精细设计的,从非规格化值能够平滑过渡到规格化值。

  1. 所在小组:第三组

  2. 组内昵称:hy

  3. 心得体会

如何使用二进制 0 和 1 表示整数?

从低位到高位,每一位表示 2^x 是否取值。

无符号

无符号(unsigned)整数都是正数,其表示比较简单,如果有 w 个位,每一位代表 2^x 是否取值,可以表示 0 ~ 2^w - 1 范围。

有符号:补码编码

负数如何表示呢?

最常见的是补码形式,将字的最高有效为定义为负权(negative weight)

对于 w 位的整数,最高位称为符号位,它的权重为 -2^(w-1),当符号位为 1 时,表示值为负(剩下的加起来也没这个大),例如:

如果 w 等于 4,此时:

[1011] = -1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 1 * 2^0 = -8 + 0 + 2 + 1 = -5 表示 -5。

最小值为:[10…0] -2^(w-1) 也就是 -8

最大值为:[01…1] 等于 w-1 的无符号表示,等于 4 + 2 + 1 = 7

-1 在计算机中如何表示?

根据上面的例子 -8 + 7 = -1 因此,-1 的二进制表示全是 1,对应 16 进制:0xFFFFFF

为什么负数的最大值比正数的最大值多 1?

因为正数还包括一个 0,而 0 是非负数。

什么是原码?

数字按照绝对值转换成二进制,如果是负数在最高位补 1。

例如:

  • 5 的原码是 00000000 00000000 00000000 00000101

  • -5 的原码是 10000000 00000000 00000000 00000101

也就是用最高位表示符号:0 为正,1 为负。

如实使用 8 个 bit 表示无符号整数,就是 0 ~ 255;如果是有符号,最高位表示符号,剩下的 7 个 bit 表示数字,就是 -127 ~ +127

但是原码有个弱点,有两个 0,对应的 1000000000000000 表示都是 0,即 +0、-0,非常诡异。

进行异号相加或同号相减时,比较笨蛋,先要判断 2 个数的绝对值大小,然后进行加减操作,最后运算结果的符号还要与大的符号相同;

于是,反码产生了。

反码是什么?

  • 正数的反码:与原码相同;

  • 负数的反码:除符号位以外全部取反。

-5 对应 10000101 的反码为 11111010

还是有 +0、-0 的问题,反码作为过渡产物,补码出现了。

为什么发明反码?

我认为就是为了方便做加法,5 - 5 因为有了反码,直接就在二进制下得到结果 0000 0000

补码的另一种视角

补码表示法:

  • 正数的补码与原码相同;

  • 负数的补码为原码的反码然后加 1。

例如 -5:

原码:10000101

反码:11111010

加 1 得到最终的编码:11111011

为什么要加 1?

这样就没有 +0、-0 的问题,因为:

+0(正数): 补码与原码相同,就是 00000000

-0(负数): 原码:10000000,反码:11111111,加 1 得到补码 00000000 和上面的 0 表示一样,统一了。

鉴于不需要两个 0,对于 -0 会特殊处理。

如果在 -0 取反码时会连符号位一起取反得到 01111111 补码得到 10000000(与原码一样) 表示 -128最高位的 1 即表示符号位又表示数字位)。

这样两个 0 就只剩一个了,而且还多了一个坑位表示 -128


运算时,减法可以转加法,将被减的数取负数即可,转为加负数。

5 - 5 = 5 + (-5) = 00000000

-1 如何表示

-1 在计算机中如何表示:

  • 原码:10000001

  • 反码:11111110

  • 补码(负数 +1):11111111

全是 1,对应 16 进制:0xFFFFFF

-128

在补码表示下,负数会多一个,就是从 -0(10000000) 抠过来的一个位置,表示最小的负数。

负数最大值是特殊情况,转换成原码时最高位的 1 有两层含义,符号位和数值位。

对其取反码会将符号位也一起取反,得到反码 01111111,加一得到补码 10000000

参考资料:

-128 + 1

10000000 + 00000001 = 10000001

-127 就是 10000001,对于补码的加法,直接二进制加就行了。

-127 的原码表示 11111111 反码 10000000 补码 10000001

整数运算

  • 正数除以 2 就相当于右移一位

无论是无符号数还是有符号数,一旦用来表示数值的最高位发生了进位,超出了表达形式或者改变了符号位,就会发生溢出。

对于无符号数加法,如果两个 w 位的数字相加,结果是 w+1 位的话,那么就会丢弃掉最高位,实际上是做了一个 mod 操作。

假设 w=3,那么能够表达的数字范围是 000~111(0~7)(括号内为二进制对应的十进制数值,后同),那么如果一个表达式是 110+111(6+7),原本应该等于 1101(13),但是由于 w=3,所以最终的结果是 101(5),也就是发生了溢出,两个无符号数相加,有可能反而变『小』。

对于有符号的加法(Two’s Complement Addition),操作过程和无符号加法一样,只是解释的时候会有不同,因此会得到正溢出(positive overflow)和负溢出(negative overflow)两种。正溢出就是数值太大把原来为 0 的符号位修改成了 1,反而成了负数;负溢出是数值太小,把原来为 1 的符号位修改成了 0,反而成了正数。

还是用刚才 w=3 作为例子,能够表达的数字范围是 100~011(-4~3),如果一个表达式是 011+010(3+2),理论上应该等于 5,但是相加之后变成了 101(-3),也就是发生了正溢出。如果一个表达式是 100+101(-4+(-3)),理论上应该等于 -7,但是相加后进位截取变成了 001(1),也就是发生了负溢出。

对于乘法来说,值的范围会大很多,这里分情况讨论一下,假设两个乘数是 x,y 并且都是 w 位的:

  • 无符号数:至多 2w 位

  • 有符号数,最小的负数:至多 2w - 1 位

  • 有符号数,最大的正数:至多 2w 位

如果需要保证精度,就需要用软件来实现了。

浮点数是对 V = x * 2^y 形式的有理数进行编码,计算机不像数学有各种符号可以表示任意的实数,只能表示有限精度的范围和精度,因此浮点数只能近似的表示实数运算。

二进制表示法

浮点数的二进制表示法等于整数位 + 小数位,小数位的权重是 2^-1、2^-2... 依次相加,例如:

5.75 = 101.11 也就是整数位 5 + 小数位 1/2 + 1/4

这种方法的问题在于,只能表示能够被写成 x * 2^y 的数,其他值只能近似地表示,比特位越多精度越高。

例如数字 0.2 能用十进制准备的表示,但确不能准备地表示为二进制,只能通过增加二进制的长度来提高表示的精度,试试如何通过增加小数位的长度接近 0.2

| 二进制 | 值 | 十进制 |

| — | — | — |

| 0.0 | 0/2 | 0.0 |

| 0.01 | 1/4 | 0.25 |

| 0.010 | 2/8 | 0.250 |

| 0.0011 | 3/16 | 0.1875 |

| 0.00110 | 6/32 | 0.1875 |

| 0.001101 | 13/64 | 0.203125 |

| 0.0011010 | 26/128 | 0.203125 |

| 0.00110011 | 51/256 | 0.19921875 |

有 4 个小数位,浮点数就有 4 位小数的精度 1/16 = 0.0625

这种方法表示不了 1,只能无限接近 0.111111…

而且,二进制表示法不能有效地表示很大的数字,例如 5 * 2^100 使用 101.100个0 的位模式来表示,0 的数量太多了,如果能通过给定 x 和 y 的值来表示 x * 2^y 的数,就能提升到更高的精度。

因此,实际采用的是 IEEE 754 规范。

IEEE 754

IEEE(eye-triple-ee)是电气和电子工程师协会的缩写,IEEE 标准 754 是 1985 年发布的浮点数国际标准,定义了浮点数的算术格式、交换格式、舍入规则、操作和异常处理。

IEEE 浮点标准使用 V = (-1)^s * M * 2^E 的形式来表示一个数:

  • s 符号 sign:表示正负;

  • M 尾数 significand:是一个二进制小数,取值范围大于 1 无限逼近 2;

  • E 阶码 exponent:对浮点数加权,权重是 2 的 E 次幂。

32 位的浮点数编码形式为:


0|01111100|01000000000000000000000

s exp frac(小数域)

计算 E 的值:

E = Exp - Bias

Exp:是 exp 编码区域的无符号整数值。

Bias 值为 2^(k-1) - 1,k 是 E 编码的位数,之所以需要 Bias 这个偏移量,是为了保证 E 编码只需要以无符号数来处理:

  • 单精度:127(Exp:1…254 E:-126…127)

  • 双精度:1023(Exp:1…2046 E:-1022…1023)

计算 M 的值:

frac 编码尾数(0 <= f < 1),采用二进制表示,此时 M = 1 + f 是一个永远大于 1 的数,能增加额外的精度(更接近不能代表的值)。

非规格化的值

如果 Exp 全是 0,即阶码域都为 0 时,表示的数是非规格化的值,这样可以表示 +0 和 -0。

特殊值

当阶码域都为 1 时,能表示:

  • 正无穷 s = 0

  • 负无穷 s = 1

还要一个特殊值 NaN(Not a Number),表示一些运算的结果不是实数或无穷。

舍入(Round)

对于不能精确表示的数 x,只能尽量接近,找到最接近的匹配值(能用二进制浮点数表示)来代表 x,而这个匹配值肯定有两个,一个比 x 大一点,一个比 x 小一点,我们选择其中一个的过程就是舍入

问题是,在两个值之间,我们如何确定舍入方向?

目前有 4 种舍入的方法:

  • 向偶数舍入(Rount to Even)(默认的方式):将数字向上或向下舍入,保证结果的最低有效数字是偶数(也就是 0);

  • 向零舍入:往 0 靠近的舍入方式,在两个数中选择接近 0 的那一个;

  • 向下舍入:选择更小的值;

  • 向上舍入:选择更大的值。

其他的比较简单,我们着重讨论向偶数舍入:

也被称为向最接近的值舍入(round to nearest),会寻找这两个数中最接近的一个,例如 1.40 接近 1 而 1.60 接近 2,唯一的特殊情况是 1.50,接近的距离是相等的,此时也是采用向偶数舍入的时机,

1.5、2.5 最近的偶数是 2 所以都舍入到 2。

向偶数舍入这个名字有歧义,其实应该是先向最接近的值舍入,如果接近的值一样,再向偶数舍入,是一个处理分歧的规则。

向偶数舍入的好处是:如果数字很多,得到的平均数更均匀,因为一半是向上一半是向下舍入的

WHY 0.1 + 0.2 = 0.30000000000000004

因为不能精确表示 0.1 和 0.2。

Decimal 定点数

我们无法使用有限的二进制位数准确地表示十进制中的 0.1 和 0.2,这就造成了精度的损失,这些精度损失不断累加在最后就可能累积成较大的错误。

但是 0.25 和 0.5 两个十进制的小数都可以用二进制的浮点数准确表示,所以使用浮点数计算 0.25 + 0.5 的结果也一定是准确的。

为了解决浮点数的精度问题,一些编程语言引入了十进制的小数 Decimal

Decimal 在不同社区中都十分常见,如果编程语言没有原生支持 Decimal,我们在开源社区也一定能够找到使用特定语言实现的 Decimal 库。Java 通过 BigDecimal 提供了无限精度的小数,该类中包含三个关键的成员变量:

表示 1234.56 时:

  • intVal 中存储的是去掉小数点后的全部数字,即 123456;

  • scale 中存储的是小数的位数,即 2;

  • precision 中存储的是全部的有效位数,小数点前 4 位,小数点后 2 位,即 6;

intVal * 10^-scale

BigDecimal 这种使用多个整数的方法避开了二进制无法准确表示部分十进制小数的问题,因为 BigInteger 可以使用数组表示任意长度的整数,所以如果机器的内存资源是无限的,BigDecimal 在理论上也可以表示无限精度的小数。

虽然部分编程语言实现了理论上无限精度的 BigDecimal,但是在实际应用中我们大多不需要无限的精度保证,C# 等编程语言通过 16 字节的 Decimal 提供的 28 ~ 29 位的精度,而在金融系统中使用 16 字节的 Decimal 一般就可以保证数据计算的准确性了。

任何需要用到浮点数的地方,使用 Decimal 就错不了

2 个赞

所在小组

第一组

组内昵称

nigel

心得

整数部分(下)
1.为什么会溢出?
举例,50000 * 50000=-1794967296
为了弄清楚这个问题,必须了解整数在计算机中是如何存储的。
整数占4个字节,4个字节也就是32位byte,最高位表示符号位,1表示负数,0表示正数。
剩下的31位可以为1或者为0,所以整数的取值范围是-2^ 31+1~2^ 31,再看50000*50000=2500000000,这个数字超出了整数的取值范围
在计算机中,数据的运算是补码,而不是源码,那么数据进行计算的时候,其实是补码在计算,计算出结果后,在由补码转换为原码,在转换为10进制数,下面就是过程:

源码与补码的转换:
1)0和正数的补码与源码相同
2)负数,补码的符号位不变,其余位取反,最后+1
计算:50000*50000=2500000000
二进制补码计算结果:1001 0101 0000 0010 11111 0010 0000 0000
正数加符号位后:0|1001 0101 0000 0010 11111 0010 0000 0000(33位了)
超出32位后,去掉最高位0:1 0010 1010 0000 0101 1111 0010 0000 0000
符号位不变,其余取反后+1获得源码: 1101 0101 1111 1010 0000 1101 1111 1111+1=1101 0101 1111 1010 0000 0111 0000 0000
结果转化为10进制:-1794967296

提示:一般遇到这种情况,可以把变量定义成长整型或者浮点型。

浮点数:
1.同样占4个byte,为什么50000*50000=2500000000.000000,转化为浮点数,就能正确计算呢?
答:因为浮点数的存储形式与整数不同,取值范围比整数要大,包含了上面的结果值。

2.如何计算浮点数的取值范围呢?
根据IEEE浮点数的表示规则,可以表示为公式:
[image:B12E24E6-6A12-4B99-B341-52D60B65999D-56964-000C017D4A44836F/42E0B43A-B97E-4DD4-8E24-75DDF15AE515.png]

S:符号位,0正,1负
exp:阶码位,以移码形式存储,位数决定取值范围,用e表示,其阶码值为E
frac:尾数位,以原码形式存储,小数部分,位数决定小数的精度,用M表示

图中例子,
尾数位:1001.10012^ 0,存入计算机需要规格化后,=1.00110012^ 3

那么浮点数是如何转换用计算机表示的呢?看图讲解:
图1:
[image:987B155D-3D8A-48B3-83FD-56702276AE5A-56964-000C023D5E648967/155DE558-B1FC-4E0C-B5FF-54B68A5DEA1E.png]
[image:64839006-6B23-49E8-8151-96649BC9E917-56964-000C022CF1A83A53/2C1D7D75-3C5A-4BFC-8FB6-AFB723FB4921.png]

计算机中,浮点数表示共分三类,以单精度浮点数为例:

1.规格化:当阶码位在1和254之间时,阶码位不是0,也不是255
2.非规格化:阶码位全为0
3.无穷大:阶码位全为1,尾数位全为0
提示:阶码位全为1,尾数位不为0,认为不是一个数,c语言用NaN表示

图1,显示了计算Bias(偏置值)的计算规则,k表示阶码位的位数,单精度的阶码位是8位,所以Bias=2^ 7-1=127,又因为阶码位是移码的形式存储的,所以要得到阶码值,就必须要经过转换,而Bias就是转换的关键,所以这里要先求Bias

图2,求出Bias后,就得到图2下半部分的转换过程。

根据以上,得出,规格化的最大表示形式为:
S=0,e=1111110,M=1111111111111111111111
E=e-127=254-127=127
带入公式后:
[image:25154C8E-E056-4AA7-AC72-7FD9DE89944C-56964-000C030EC9C86C39/1EE1C3DA-6EB0-4046-859F-90EFC8E7C2F2.png]
所以,最大取值范围是: -3.410^ 38~3.410^ 38

这个范围,包含了50000*50000,所以,在转换为浮点数后,可以得到正确的结果。

3.那么计算机如何存储这个结果呢?
[image:9A1FF819-13F7-401A-AA07-91D2289956FB-56964-000C0351C82AD0A5/44804D18-4D26-4938-965C-CEB6F6E4B0BF.png]

第一步将结果转换成二进制并进行规格化处理,将小数点移到高两位之间得到
1.0010101000000101111100100000000*10^ 31(31就是阶码值E)
E = e - Bais 得到 e = E + Bais
S=0, e = (31+127)10进制= (10011110)2进制 ,M=001010100000001011111001
最后结果: 0 1001110 00101010000001011111001

可以打印其汇编值进行验证。

1 个赞

所在小组

第五组

组内昵称

王传义

你的心得体会

汇编指令回顾,下面都是具体例子,先了解,在验证(目前没有验证)

  1. call c++ 普通函数调用 call 地址,虚函数 call ptr[i] 是个偏移量。可见代码编译时候不确定对应地址
  2. lock volatile 解决缓存内存可可见性, 不解决
  3. gcc golang 设置cflag开启内联,print 遍历查看 x 十六进制内容。 写demo打印函数堆栈调用和整数数值。
  4. 二进制操作一定看懂 & ^操作。

所在小组

静默组

组内昵称

清风环佩

心得

  • 原码、补码和反码都是在基本课程所学,首通第二章,加深了印象。在计算机中,无符号整数用原码表示,有符号整数用补码表示,那反码的作用是什么?为了做减法运算时,先变成反码然后再运算用以提高效率?
  • 浮点数的表示比较精妙和麻烦,以32位举例,首位1bit为符号位S,其后8bit为指数部分E,最后23bit为尾数部分M,此外还有偏移值,32位浮点型的偏移值是127,这个数怎么来的就不说了。给个例子,7.25的浮点数表示:
    1. 7.25的以二进制表示,整数部分7以原码表示111,小数部分0.25,按照小数点后的1/2 1/4 1/8 …等等来凑这个数,刚好0.25等于1/4,于是小数部分为 01,拼凑起来为111.01
    2. 111.01用科学计数法表示为1.1101*2^2,尾数M的范围是1<=M<2,此时1.1101是在这个区间内的,刚才的计数法是2的2次幂,E就是2+偏移值127等于129
    3. S为0,E为129,M为1101,拼接起来就是 0 10000001 11010000000000000000000
    4. 计算过程就是这样,书里这一段的推导很完善,但是没给出一个具体例子。
  • 有符号和无符号数的转换,这两种类型转二进制再转回来,这段内容除了移位操作十分简单,其他的原理和推导太过繁多,第一次读本章节细节现在已经记不清了,没有个二读三读实在不能像大佬们一样透彻。就不献丑了。

所在小组

静默组

组内昵称

Han

心得体会

无符号数的位表示就是二进制的值,很好理解。有符号数位表示也是二进制表示,只是最高位取负值,其它为取正值。
下面的程序打印出符号数的位表示。

#include <stdio.h>

void print_bits(char c) {
    int l = sizeof(c) * 8;
    printf("%d = ", c);
    for(int i = l-1; i >= 0; i--) {
        printf("%d", (c >> i) & 0x01);
    }
    printf("\n");
}

int main() {
    char c = -1;
    print_bits(c);

    c = -2;
    print_bits(c);

    c = -128;
    print_bits(c);
    
    c = 127;
    print_bits(c);
    return 0;
}

Output:
-1 = 11111111
-2 = 11111110
-128 = 10000000
127 = 01111111

可以看到,-1 的位表示为 11111111
-1 = (10000000=-2^7) + (01111111=2^7-1)

有符号数和无符号数之间转换时,不同字长的整数之间转换,实际工作中常见遇到的情况是隐式的强制转化导致的bug,很难发现。

  • 直接变量赋值
  • 算术运算,加减等

对于需要考虑跨平台的代码,最好使用明确了大小的数据类型来声明,如uint32_t,int16_t等,而不是使用int,short,代码的可读性更高,避免了产生不明显的错误的可能性。

整数运算中越界其实还挺容易遇到的,尤其是在使用unsigned char或者short时,发生之后,问题产生的现象会很奇怪,很难定位。尤其是随着项目的持续维护,实现之初,即使是有经验的开发,都会有预设这样写肯定没问题,运算的输入绝对不会导致越界,但随着时间推移,开发人员变更、上下文输入变化,甚至有人直接拷贝代码片段复用到其它场景。

浮点数的存储细节,开发工作中很少会涉及到,了解下原理性就好了。重点要关注运算过程中可溢出的可能性、各个类数据类型间的转换,很多安全漏洞,都是由这种事情导致的。

是否存在静态检查工具?帮助找到这种潜在的问题,找了几个工具,对于书中的示例扫描结果都没有警告提示。

所在小组

第一组

组内昵称

郝立鹏

依旧和第一章一样,关于公式的部分我没有细看,就看了下结论,总体来说对第二章感觉没那么深刻,

起码第三章的开始做实验的章节。到时边看书边做实验,也许会好点?

学习第二章之前,我对整数的表示方式比较理解,但是对浮点数一直很困惑,这个如何来表示呢?

1、浮点数

浮点数的表示,分别由符号S、尾数M、阶码E三部分组成,其中符号S是首个bit,通过该bit标示有符号位,也就是说浮动数是采用原码表示。尾数M是二进制小数,随阶码E的取值,决定是否隐式包含1。而阶码E的取值,将浮点数分为三类:规格化、非规格化、无穷大或NaN。

2、溢出

32位的数值转为16位时可能会发生溢出;32位数值乘以32位数值的结果存储在32位数值中,可能无法存下

3、浮点数之间相等如何来判断

4、补码的运算

源码与补码的转换:

1)0和正数的补码与源码相同

2)负数,补码的符号位不变,其余位取反,最后+1

计算:50000*50000=2500000000

二进制补码计算结果:1001 0101 0000 0010 11111 0010 0000 0000

正数加符号位后:0|1001 0101 0000 0010 11111 0010 0000 0000(33位了)

超出32位后,去掉最高位0:1 0010 1010 0000 0101 1111 0010 0000 0000

符号位不变,其余取反后+1获得源码: 1101 0101 1111 1010 0000 1101 1111 1111+1=1101 0101 1111 1010 0000 0111 0000 0000

结果转化为10进制:-1794967296

由于有一个组员是写在其他笔记软件上的,帮他贴一下

组内昵称

张仁杰
week2:心得体会:
从使用二进制进行表示再到为了更好的位模式而使用16进制,这些都是信息展现在我们眼前最出的样子,但是在寻址与字节顺序时需要注意机器的大小端,这些都是信息的表示上面的坑。不管是浮点数还是整数,当初设计时便是如此的巧妙;最常见的负数的表示方式就是以补码的方式,而且Java中要求采用补码进行标识;但是对于程序中信息的处理时需要注意的,虽然是很微妙同时也容易被忽略,从考虑数据的溢出,再到数据隐士强制类型转换,这些都是可能造成漏洞,导致一些不可想象的后果,那么从开发上来讲,需要正确的考虑极限的种场景下,这种信息表示是否合理。浮点数的运算这个可以算是比较难理解的,规格化的值与非规格化值为什么要如此设计?它就是想要浮点数按照整数排序那样进行排序。浮点数计算中同样存在溢出、精度损失等问题,这些都是在使用浮点数计算需要注意的,它也不是遵守我们所认识的算数属性。

所在小组

第六组

组内昵称

之昂

心得体会

  • 机器表示法

    1. 无符号编码基于传统的二进制表示法:表示大于或者等于零的数字;
    2. 补码编码表示有符号证书最常见的方式,有符号证书就是可以为正或为负;
    3. 浮点数编码是表示实数的科学技术法的以2为技术的版本
    
  • 溢出

    计算机的表示法使用有限数量的位来对一个数字编码,结果太大不能表示就会溢出。

​ 整数表示虽只能编码一个相对较小的数值范围,但是这种是精确的,浮点数虽然可以编码,但是近似的

  • 字长

    1. 决定的最重要的系统参数就会说虚拟地址空间的最大大小。
    2. 对于一个字长为w位的机器而言,虚拟地址的范围为0~2^w-1,程序最多访问2^w个字节;
  • 32位/64位程序

    区别在于程序是如何编译,而不是其运行的机器类型。

    1. 32位字长限制虚拟地址空间4千兆字节(4GB),4*10^9,
    2. 64位则为16EB,1.84*10^9.
  • c语言

    ​ 1. 位级运算:

    ​ 与、或、非、异或

    ​ 2. 逻辑运算:

    ​ 认为所有的非零的参数都表示true,参数0表示false,返回0或1,分别表示true或false

    ​ ||、&& 和 !

    ​ 3. 移位运算

    ​ 和左移相应的右移运算x>>k,有点微妙。一般而言,机器支持两种形式的右移:逻辑右移算术右移

    逻辑右移在左端补k个0,

    算术右移是在左端补k个最高有效位的值

    它对有符号整数数据的运算非常有用。

  • 有符号数表示法

  • 无符号与补码直接转换

所在小组

第七组

组内昵称

高华

心得体会

  1. 计算机只能表示有限范围的数,这使得有些情况下会出现很诡异的现象,如两个正整数相加结果却为负数,了解整数在计算机中的表示方式,有助于在编程的过程中避免发生各种溢出,就算出现也能更快定位问题。
  2. 隐式类型转换很可能会带来意想不到的 bug,造成严重的后果,写代码的过程中尽可能避免隐式类型转换。
  3. 无论是原码,反码还是补码都是人为定义的编码方式,对于计算机来说,都是 0 和 1 组成的序列。
  4. IEEE 754 统一了浮点数的编码方式,用更小的空间存储更大范围的数

所在小组

静默组

组内昵称

Bean

心得体会

  • 书中所说的补码的计算视角和之前看到到不一致,非常容易理解
  • 整形,浮点的溢出在日常编写代码中其实也经常能够遇到,,需要提高警惕

1.所在小组:第五组
2.组内昵称:肖思成
3.心得体会
因为自己一直做的PHP开发,对这方面了解的不多,所以这部分读起来的进度比较慢。
整个2.2阅读下来,我觉得最重要的一句话就是C语言在无符号数和有符号数之间的转换,其原则是底层的位表示保持不变。所有的知识点围绕这句话来进行讲解。

  • 64位机器使用8字节,32位机器使用4字节
  • 对于无符号数的二进制表示来说,每一个介于0~2^w-1之间的数,都有唯一一个w位的值编码
  • 无符号数值可以用 B2Uw 来表示
  • 只计算机里,希望表示负数值的时候,通常采用的方式是补码 (two`s-complement) ,即将字的最高有效位解释为负权,用函数 B2Tw 来表示,最高有效位也称为是符号位
  • 取绝对值来进行比较的话,补码的范围是不对称的,即 |TMin| = |TMax|+1,这是应为0表示的是非负数
  • 除了补码,有符号数还有两种标准的表示方式:反码原码
  • 在C语言中,支持所有整型数据的有符号和无符号运算。强制类型转换是显示的数值转换,而当一种类型的表达式被赋值给另外一种类型的变量的时候,转换是隐式发生的。C中的printf既可以用%u来输出int型数值,也可以用%d输出unsigned类型的数值
  • 当执行一个运算的时候,如果它的一个运算符是有符号的,另一个是无符号的,那么C语言会隐式的将有符号参数强制转换成无符号的,并且假设两者都是非负的来执行运算

1.所在小组:静默组
2.组内昵称:Tang_D
3.心得体会

  • 数字等在计算机中以二进制表示,各种溢出其实都是对于二进制的不同解读所导致。
  • 浮点数的规则较为简单,分为符号位,阶码,尾数
  • 浮点数对于某些数字无法准确表示,只能采用舍入的方式
  • 舍入采用偶数舍入,即最低有效位为偶数,这样的方式使得统计误差达到一个较小的水准。
  • 移位等价于乘以或除以2,同时移位分为算数和逻辑,算术在最高位添加符号位
  • 因为有溢出的情况可能会出现,所以需要在编码时仔细考虑数字的范围,在强转时需要思考是否会溢出产生不可预料的事情。
  • 有符号和无符号运算时,C会将有符号转为无符号的,需要注意这一点。

所在小组
静默组

组内昵称
franceshu

你的心得体会

本周主要学习第二章信息的表示和处理,数值处理部分相对比较枯燥.第一遍看到后面定理晕头转向的,后面看第二遍的时候将所有练习题都跟着动手完成,效果明显好了很多.
对于整数运算,表示数字的有限字长限制了可能值的取值范围,结果存在溢出可能.C语言中强制类型转换等一些规定可能会产生非直观的结果,需要注意.
浮点数计算只有有限的范围和精度,并且不遵守普遍的算数属性.

重新温顾的知识点:

  • 大端/小端法
    最低有效字节在前面的是小端法,最高有效字节在最前面的是大端法. (大多数Intel兼容机都只用小端法)
    假设变量x=0x01234567在地址0x100处
    |0x100 0x101 0x102 0x103|
    大端 |01 | 23 | 45 | 67 |
    小端 |67 | 45 | 23 | 01 |
    网络传输二进制数据时通过网络标准转换来避免不同机器字节顺序的影响.
    反汇编代码阅读时需要考虑字节顺序.
    C语言中强制类型转换/Union允许一种数据类型引用一个对象时需要考虑字节顺序.
  • 有符号数和无符号数之间的转换
    补码转换为无符号数时,T2U(x)=x (x>=0); T2U(x)=x+2^w(x<0,w位)
    无符号数转换为补码时,U2T(u)=u (u<=TMax); U2T(u)=u-2^w(u>=TMax)
    C语言中对同时包含有符号和无符号数的表达式处理时,会隐式的将有符号数强制转换为无符号数,对于标准运算符并无差异,但是遇到关系运算符时会导致问题.
    如 -1 < 0U, 会先将-1转换为4294967295U 导致非直观结果出现.
  • 扩充整数字长时, 补码数符号扩展,无符号数零位扩展.
  • 无符号整数加法, x+y=x+y(正常) x+y=x+y-2^w (溢出时)
  • 补码加法, x+y=x+y-2^w(正溢出), x+y=x+y(正常), x+y=x+y+2^w(负溢出)
  • 无符号数乘法 xy=(xy) mod 2^w
  • 补码乘法 xy=U2T((xy) mod 2^w)
  • 浮点数通过将数字编码为x*2^y的形式来近似地表示实数,最常见的浮点精度都float和double.
  • IEEE浮点通过符号s/尾数M/阶码E来表示,32位1+8+23,64位1+11+52,被编码的值有三种不同的情况:
    • 规格化的值, 阶码不全为0也不全为1
    • 非规格化的值,阶码域全为0
    • 特殊值, 阶码域全为1,小数域全为0时(正无穷,负无穷); 阶码域全为1,小数域非零(NaN)
  • 四种舍入方式中默认的是Round-to-even,将数字向上或向下舍入使得结果的最低有效数字是偶数.(可以避免统计偏差)

所在小组:静默组
组内昵称:黄小黄
你的心得体会:

  • 在对象的寻址上,几乎所有的计算机,把多字节对象都存储为连续的字节序列,对象的地址为所使用字节序列中最小的地址。
  • 在存储对象字节的顺序上,分为小端机和大端机。小端机,按照最低有效字节到最高有效字节顺序存储对象。大端机,按照最高有效字节到最低有效直接存储数据。
  • 整数运算:和大多数其他程序语言一样,C语言实现的有限整数运算和真实的整数运算相比,有一些特殊的属性。例如,由于溢出,表达式x x能够得出负数。但是,无符号数和补码的运算都满足整数运算的许多其他属性,包括结合律,交换律和分配律。这就允许编译器做很多的优化。例如,用(x<<3)-x取代表达式7 x时,我们就利用了结合律,交换律和分配律的属性,还利用了移位和乘以2的幂之间的关系。
  • 位运算,使用补码运算,~x+1等价于-x.另外一个例子,假设我们想要一个形如[0,…,0,1,·,1]的位模株式,由wーk个0后面紧跟着k个1组成.这些位模式有助于掩码运算.这种模式能够通过C表达式(1<<k)-1生成,利用的是这样一个属性,即我们想要的位模式的数值为2^k一1.例如,表达式(1<<8)-1将产生位模式OxFF。
  • 浮点数表示: 浮点表示通过将数字编码为x*2^y的形株式来近似地表示实数,最常见的浮点表示方式是由IEEE标准754定义的,它提供了几种不同的精度,最常见的是单精度(32位)和双精度(64位).IEEE浮点也能够表示特殊值正无穷,负无穷和NaN。浮点数运算必须非常小心地使用浮点运算,因为浮点运算只有有限的范围和精度,而且并不遵守普遍的算术属性,比如结合性。
  • 截断数字,假设我们不用额外的位来扩展一个数值,而是减少表示一个数字的位数.在一台典型32位机器上,当把intX强制类型转换为short时,我们就将32位的int截断为16位的short int.这个16位的位模式就是-12345的补码表示.当把它强制转换回int时,符号扩展把高16位设置为1,从而生成-12345的32位补码表示.

1.所在小组:静默组
2.组内昵称:维钢、
3.心得体会:

  • 整数无符号类直接用整数的二进制形式进行表示,所以一个储存单元来存储一个无符号的整数时,其存储范围为存储单元中各个位全0也就是0到各个位都为1所表示的十进制的整数(0~2^n-1);
  • 有符号的整数使用补码表示,补码的最高位称为符号位,符号位为1时,表示的数为负数,符号位为0时,表示的数为非负数,所以存储单元在存储有符号的整数时,其储存范围大小最小为符号位为1,剩下的全0,也就是-2^(n-1),最大的数为符号位为0剩下的全1,也就是2^(n-1)-1;
  • 强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。
  • 浮点数使用符号+尾数+阶码的格式来存储,单精度浮点数分别是1位、8位和23位组成32位大小的空间来存储,双精度浮点数分别是1位、11位和52位组成64位大小的空间来存储。