学习计时:共8小时
读书:2h
代码:1h
作业:3h
博客:2h
---
使用C99特性时 gcc -std=c99 xxx.c 实验楼环境为64位,编译为32位机器码: gcc -m32 xxx.c。
1.十六进制(简写为“hex”)使用数字‘0’~‘9’,以及字符‘A’~‘F’来表示16 个可能的值。
2.二进制与十六进制的转换
假设数字0x173A4C,可以通过展开每个十六进制数字,将它转换为二进制格式,如下所示:
十六进制 1 7 3 A 4 C
二进制 0001 0111 0011 1010 0100 1100
这样就得到了二进制表示000101110011101001001100。
反过来,如果给定一个二进制数字1111001010110110110011,你可以首先把它分为每4 位一组,再把它转换为十六进制。不过要注意,如果位的总数不是4的倍数,最左边的一组可以少于4 位,前面用0 补足,然后将每个4 位组转换为相应的十六进制数字:
二进制 11 1100 1010 1101 1011 0011
十六进制 3 C A D B 3
3.十进制与十六进制的转换
每台计算机都有一个字长(word size),
对于一个字长为w 位的机器而言,虚拟地址的范围为0 ~ 2w-1,程序最多
访问2w 个字节。
1. C 语言中数字数据类型的字节数
C 声明 32 位机器 64 位机器
char 1 1
short int 2 2
int 4 4
long int 4 8
long long int 8 8
char * 4 8
float 4 4
double 8 8
2.C 语言标准对不同数据类型的数字范围设置了下界,但是却没有上界。
1.在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为使用字节中最小的地址。例如,假设一个类型为int的变量x的地址为0x100,也就是说,地址表达式&x 的值为0x100。那么,x的4个字节将被存储在存储器的0x100、0x101、0x102和0x13 位置。
2.排列表示一个对象的字节有两个通用的规则。考虑一个w位的整数,位表示为[xw-1,xw-2,…,x1,x0],其中xw-1 是最高有效位,而x0 是最低有效位。假设w是8 的倍数,这些位就能被分组成为字节,其中最高有效字节包含位[xw-1,xw-2,…,xw-8],而最低有效字节包含位[x7,x6,…,x0],其他字节包含中间的位。某些机器选择在存储器中按照从最低有效字节到最高有效字节的顺序存储对象,而另一些机器则按照从最高有效字节到最低有效字节的顺序存储。前一种规则—最低有效字节在最前面的方式,称为小端法(little endian)。大多数Intel 兼容机都采用这种规则。后一种规则—最高有效字节在最前面的方式,称为大端法(big endian)。大数IBM 和Sun Microsystems 的机器都采用这种规则。
3.C 语言中的typedef声明提供了一种给数据类型命名的方式。这能够极大地改善代码的可读性。
1.C 语言中字符串被编码为一个以null(其值为0)字符结尾的字符数组。每个字符都由某个标准编码来表示,最常见的是ASCII 字符码。
2.在使用ASCII 码作为字符码的
任何系统上都将得到相同的结果,与字节顺序和字大小规则无关。因而,文本数据比二进制数据具有更强的平台独立性。
1.不同的机器类型使用不同的且不兼容的指令和编码方式
2.即使是完全一样的进程运行在不同的操作系统上也会有不同的编码规则,因此二进制代码是不兼容的
我们可以将上述4 个布尔运算扩展到位向量的运算,位向量就是有固定长度为w、由0 和1组成的串。位向量的运算可以定义成参数的每个对应元素之间的运算。假设a 和b 分别表示位向量[aw-1,aw-2,…,a0]和[bw-1,bw-2,…,b0]。
1.C 语言的一个很有用的特性就是它支持按位布尔运算。事实上,我们在布尔运算中使用的那些符号就是C 语言所使用的:| 就是OR(或),& 就是AND(与),~ 就是NOT(取反),而^就是EXCLUSIVE-OR(异或)。这些运算能运用到任何“整型”的数据类型上,也就是那些声明为char或者int的数据类型,无论它们有没有像short、long、long long 或者unsigned这样的限定词。
2.位级运算的一个常见用法就是实现掩码运算,这里掩码是一个位模式,表示从一个字中选出的位的集合。让我们来看一个例子,掩码0xFF(最低的8位为1)表示一个字的低位字节。位级运算x&0xFF生成一个由x的最低有效字节组成的值,而其他的字节就被置为0。比如,对于x=0x89ABCDEF,其表达式将得到0x000000EF。表达式~0 将生成一个全1 的掩码,不管机器的字大小是多少。尽管对于一个32位机器来说,同样的掩码可以写成0xFFFFFFFF,但是这样的代码不是可移植的
C 语言还提供了一组逻辑运算符 ||、&& 和!,分别对应于命题逻辑中的OR、AND和NOT运算。逻辑运算很容易和位级运算相混淆,但是它们的功能是完全不同的。逻辑算认为所有非零的参数都表示TRUE,而参数0 表示FALSE。它们返回1 或者0,分别表示结果为TRUE 或者为FALSE。
下面的表给出了对某些实例8 位数据做不同的移位操作得到的结果。
操作值
参数x [01100011] [10010101]
x << 4 [00110000] [01010000]
x >> 4(逻辑右移) [00000110] [00001001]
x >> 4(算术右移) [00000110] [11111001]
C 语言标准并没有明确定义应该使用哪种类型的右移。对于无符号数据(也就是以限定词
unsigned 声明的整型对象),右移必须是逻辑的。而对于有符号数据(默认的声明的整型对象),算术的或者逻辑的右移都可以。不幸的是,这就意味着任何假设一种或者另一种右移形式的代码都潜在着可移植性问题。然而,实际上,几乎所有的编译器/ 机器组合都对有符号数据使用算术右移,且许多程序员也都假设机器会使用这种右移。另一方面,Java 对于如何进行右移有明确的定义。表达式x>>k 会将x 算术右移k 个位置,而x>>>k 会对x 做逻辑右移。
在C 表达式中搞错优先级是一种常见的程序错误,而且常常很难检查出来。所以当拿不准的时候,加上括号!
1.数据类型int 可以用2个字节的数字来实现,而这几乎退回到了16位机器的时代。还看到,long的大小可以用4 个字节的数字来实现,而实际上也常常是这样。数据类型long long是在ISO C99 中引入的,它需要至少8 个字节表示。
2.C、C++ 和Java 中的有符号和无符号数
C 和C++ 都支持有符号(默认)和无符号数。Java 只支持有符号数。
无符号数的二进制表示有一个很重要的属性,就是每个介于0 ~ 2w-1之间的数都有唯一一个w 位的值编码
对于许多应用,我们还希望表示负数值。最常见的有符号数的计算机表示方式就是补码(two’s-complement)形式。在这个定义中,将字的最高有效位解释为负权(negative weight)。我们用函数B2Tw来表示
我们可以看出B2Tw 是一个从长度为w的位模式到TMinw和TMax之间数字的映射,写做B2Tw:{0,1}w → {-2w-1,…,2w-1-1}。同无符号表示一样,在可表示的取值范围内的每个数字都有一个唯一的w位的补码编码。用数学语言来说就是B2Tw是一个双射—每个长度为w 的位向量都对应一个唯一的值;反过来,每个介于-2w-1和2w-1-1 之间的整数都有一个唯一的长度为w的位向量二进制表示。
![](http://images2015.cnblogs.com/blog/744507/201510/744507-20151004200904386-1271298798.png)
1.C 语言支持所有整型数据类型的有符号和无符号运算。尽管C 语言标准没有指定有符号数要采用某种表示,但是几乎所有的机器都使用补码。常,大多数数字都默认为是有符号的。例如,当声明一个像12345或者0x1A2B这样的常量时,这个值就被认为是有符号的。要创建一个无符号常量,必须加上后缀字符‘U’或者‘u’。例如,12345U 或者0x1A2Bu
2.C 语言允许无符号数和有符号数之间的转换。转换的原则是底层的位表示保持不变。因此,在一台采用补码的机器上,当从无符号数转换为有符号数时,效果就是应用函数U2Tw,而从有符号数转换为无符号数时,就是应用函数T2Uw,其中w表示数据类型的位数。
一种常见的运算是在不同字长的整数之间转换,同时又保持数值不变。当然,当标数据类型太小以至于不能表示想要的值时,这根本就是不可能的。然而,从一个较小的数据类型转换到一个较大的类型,这应该总是可能的。将一个无符号数转换为一个更大的数据类型,我们只需要简单地在表示的开头添加0,这种运算称为零扩展(zero extension)。将一个补码数字转换为一个更大的数据类型可以执行符号扩展(sign extension),规则是在表示中添加最高有效位的值的副本。由此可知,如果我们原始值的位表示为[xw-1,xw-2,…,x0],那么扩展后的表示就为[xw-1,…,xw-1,xw-1,xw-2,…,x0]。(我们用浅灰色标出符号位xw-1来突出它们在符号扩展中的角色。)
我们不用额外的位来扩展一个数值,而是减少表示一个数字的位数。
在一台典型的32 位机器上,当把x 强制类型转换为short 时,我们就将32 位的int 截断为16 位的shortint。就像前面所看到的,这个16位的位模式就是-12 345 的补码表示。当我们把它强制类型转换回int时,符号扩展把高16位设置为1,从而生成-12 345 的32 位补码表示
就像我们看到的那样,有符号数到无符号数的隐式强制类型转换导致了某些非直观的行为。而这些非直观的特性经常导致程序错误,并且这种包含隐式强制类型转换细微差别的错误很难被发现。因为这种强制类型转换是在代码中没有明确指示的情况下发生的,程序员经常忽视了它的影响。
我们已经看到了由于许多无符号运算的细微特性,尤其是有符号数到无符号数的隐式转换,会导致错误或者漏洞的方式。避免这类错误的一种方法就是绝不使用无符号数。实际上,除了C以外,很少有语言支持无符号整数。很明显,这些语言的设计者认为它们带来的麻烦要比益处多得多。例如,Java只支持有符号整数,并且要求用补码运算来实现。正常的右移运算符>>被定义为执行算术右移。特殊的运算符>>> 被指定为执行逻辑右移。当我们想要把字仅仅看做是位的集合,并且没有任何数字意义时,无符号数值是非常有用的。例如,往一个字中放入描述各种布尔条件的标记(flag)时,就是这样。地址自然地就是无符号的,所以系统程序员发现无符号类型是很有帮助的。当实现模运算和多精度运算的数学包时,数字是由字的数组来表示的,无符号值也会非常有用。
无符号运算可以被视为一种模运算形式。无符号加法等价于计算和模上2w。可以通过简单的丢弃x + y 的w + 1 位表示的最高位,来计算这个数值。
一般而言,我们可以看到,如果x+y<2w,则和的w+1位表示中的最高位会等于0,因此丢弃它不会改变这个数值。另一方面,如果2w≤x+y<2w+1,则和的w+1位表示中的最高位会等于1,因此丢弃它就相当于从和中减去了2w。
对于补码加法,我们必须确定当结果太大(为正)或者太小(为负)时,应该做些什么。给定在范围-2w-1 ≤ x, y ≤ 2w-1-1 之内的整数值x和y,它们的和就在范围-2w≤x+y≤2w-2之内,要想准确表示,可能需要w+1 位。
范围在0 ≤ x, y ≤ 2w-1 内的整数x 和y 可以表示为w 位的无符号数,但是它们的乘积x · y
的取值范围为0 到(2w-1)2 = 22w-2w+1+1 之间。这可能需要2w 位来表示。不过,C 语言中的无符
号乘法被定义为产生w 位的值,就是2w 位的整数乘积的低w 位表示的值。
范围在-2w-1 ≤ x, y ≤ 2w-1-1 内的整数x 和y 可以表示为w 位的补码数字,但是它们的乘积
x · y 的取值范围在-2w-1 · (2w-1-1) = -22w-2+2w-1 和-2w-1 · -2w-1 = -22w-2 之间。要用补码来表示这个乘积,可能需要2w 位—大多数情况下只需要2w-1位,但是特殊情况22w-2需要2w位(包括一个符号位0)。然而,C 语言中的有符号乘法是通过将2w 位的乘积截断为w位的方式实现的。
在大多数机器上,整数乘法指令相当慢,需要10个或者更多的时钟周期,然而其他整数运算(例如加法、减法、位级运算和移位)只需要1个时钟周期。因此,编译器使用了一项重要的优化,试着用移位和加法运算的组合来代替乘以常数因子的乘法。首先,我们会考虑乘以2 的幂的情况,然后再概括成乘以任意常数。
在大多数机器上,整数除法要比整数乘法更慢—需要30个或者更多的时钟周期。除以2的幂也可以用移位运算来实现,只不过我们用的是右移,而不是左移。无符号和补码数分别使用逻辑移位和算术移位来达到目的
正如我们看到的,计算机执行的“整数”运算实际上是一种模运算形式。表示数字的有限字长限制了可能的值的取值范围,结果运算可能溢出。我们还看到,补码表示提供了一种既能表示负数也能表示正数的灵活方法,同时使用了与执行无符号算术相同的位级实现,这些运算包括加法、减法、乘法,甚至除法,无论运算数是以无符号形式还是以补码形式表示的,都有完全一样或者非常类似的位级行为。
我们看到了C 语言中的某些规定可能会产生令人意想不到的结果,而这些可能是难以察觉和理解的缺陷的源头。我们特别看到了unsigned数据类型,虽然它概念上很简单,但可能导致即使是资深程序员都意想不到的行为
十进制表示法
IEEE 浮点标准用V = (-1)s × M × 2E 的形式来表示一个数:
• 符号(sign) s 决定这个数是负数(s=1)还是正数(s=0),而对于数值0 的符号位解释作
为特殊情况处理。
• 尾数(significand) M 是一个二进制小数,它的范围是1 ~ 2-ε,或者是0 ~ 1-ε。
• 阶码(exponent) E 的作用是对浮点数加权,这个权重是2 的E 次幂(可能是负数)。
将浮点数的位表示划分为三个字段,分别对这些值进行编码:
• 一个单独的符号位s 直接编码符号s。
• k 位的阶码字段exp = ek-1…e1e0 编码阶码E。
• n 位小数字段frac = fn-1…f1 f0 编码尾数M,但是编码出来的值也依赖于阶码字段的值是否
等于0。
当处理负数时,有一个小的难点,因为它们有开头的1,并且它们是按照降序出现的,但是不需要浮点运算来进行比较也能解决这个问题
有k 位阶码和n 位小数的浮点表示的一般属性。
• 值+0.0 总有一个全为0 的位表示。
• 最小的正非规格化值的位表示,是由最低有效位为1 而其他所有位为0构成的。它具有小数(和尾数)值M = f = 2-n 和阶码值E = -2k-1 + 2。因此它的数字值是V = 2-n-2k-1+2。
• 最大的非规格化值的位模式是由全为0的阶码字段和全为1的小数字段组成的。它有小数(和尾数)值M = f = 1-2-n(我们写成1-ε)和阶码值E = -2k-1 + 2。因此,数值V =
(1-2-n)× 2-n-2k-1+2,这仅比最小的规格化值小一点。
• 最小的正规格化值的位模式的阶码字段的最低有效位为1,其他位全为0。它的数值M =1,而阶码值E = -2k-1 + 2
因为表示方法限制了浮点数的范围和精度,浮点运算只能近似地表示实数运算。因此,对于值x,我们一般想用一种系统的方法,能够找到“最接近的”匹配值x',它可以用期望的浮点形式表示出来。这就是舍入(rounding)运算的任务。一个关键问题是在两个可能值的中间确定舍入方向。
IEEE 标准中指定浮点运算行为方法的一个优势在于,它可以独立于任何具体的硬件或者软件实现。因此,我们可以检查它的抽象数学属性,而不必考虑实际上它是如何实现的。
所有的C 语言版本提供了两种不同的浮点数据类型:float 和double。在支持IEEE 浮点格式的机器上,这些数据类型就对应于单精度和双精度浮点。另外,这类机器使用向偶数舍入的方式。不幸的是,因为C语言标准不要求机器使用IEEE浮点,所以没有标准的方法来改变舍入方式或者得到诸如-0、+∞、-∞或者NaN之类的特殊值。大多数系统提供include(‘.h’)文件和读取这些特征的过程库,但是细节因为系统不同而不同
1.计算机将信息按位编码,通常组织成字节序列。用不同的编码方式表示整数、实数和字符串。不同的计算机模型在编码数字和多字节数据中的字节排序时使用不同的约定。
2.C 语言的设计可以包容多种不同字长和数字编码的实现。虽然高端机器逐渐开始使用64 位字长,但是目前大多数机器仍使用32位字长。大多数机器对整数使用补码编码,而对浮点数使用IEEE浮点编码。在位级上理解这些编码,并且理解算术运算的数学特性,对于想使编写的程序能在全部数值范围上正确运算的程序员来说,是很重要的。
3。在相同长度的无符号和有符号整数之间进行强制类型转换时,大多数C语言实现遵循的原则是底层的位模式不变。在补码机器上,对于一个w位的值,这种行为是由函数T2Uw 和U2Tw来描述的浮点表示通过将数字编码为x×2y的形式来近似地表示实数。最常见的浮点表示方式是由IEEE标准754定义的。它提供了几种不同的精度,最常见的是单精度(32 位)和双精度(64位)。IEEE 浮点也能够表示特殊值+ ∞、- ∞和NaN。必须非常小心地使用浮点运算,因为浮点运算只有有限的范围和精度,而且不遵守普遍的算术属性,比如结合性。
公式可以不看,习题不能不做,考核题目和课后习题类似,重点题目:
2.4、2.6、2.8、2.11、2.13、2.14、2.18、2.19、2.21、2.23、
2.24、2.25、2.27、2.29、2.33、2.34、2.39、2.40、2.42、2.43、
2.44、2.45、2.47、2.50、2.52、2.54
p20: 三种数字:无符号数、有符号数(2进制补码)、浮点数,信息安全系同学从逆向角度考虑为什么会产生漏洞
p22: 进制转换,注意拿二进制作中间结果就好转了
p25: gcc -m32 可以在64位机上(比如实验楼的环境)生成32位的代码
p26: 字节顺序是网络编程的基础,记住小端是“高对高、低对低”,大端与之相反就可以了。
p28: 代码执行一下
p32: 能区分逻辑运算(结果是1或0)和位运算(结果是位向量),所有逻辑运算都可以用与、或、非表达(最大式、最小式),而与或非可以用“与非”或“或非”表达,所以,只要一个与非门,就可以完成所有的逻辑运算。
p33: 掩码是位运算的重要应用,对特定位可以置一,可以清零
p38: 要用C99中的“long long”类型,编译是要用 gcc -std=c99
p39: 补码的利用寄存器的长度是固定的特性简化数学运算。想想钟表,12-1 等价于 12 + 11,利用补码可以把数学运算统一成加法,只要一个加法器就可以实现所有的数学运算。
p44: 注意C语言中有符号数和无符号数的转换规则,位向量不变。想想第一章说的 信息就是“位+上下文”
p48: 怎么样让负数等于正数? 信息安全的逆向思维
p49: 0扩展和符号扩展
p52: 深入思考一下代码和结果
p54: 如何让整数运算溢出?如何避免? p62例子看看
p67: 关于整数运算的最后思考
p67: 浮点数有科学计数法的基础就不难理解,IEEE标准754
p68: 浮点数运算的不精确性与舍入
p70: IEEE浮点标准,float/double类型
p74: 整数与浮点数表示同一个数字的关系
p78: 整数与浮点数转换规则
p80:家庭作业可以选做,协调好每题最多两人一组做,一星题目一人加一分,二星加二分,三星加三分,四星加四分
p24 进制转换代码
p24 show_bytes,写个main函数测试一下,参考p30代码
p35 练习2.11, 可以用GDB单步跟踪一下,理解更深刻
p44 代码放到一个main函数中,可以用GDB单步跟踪一下,理解更深刻
p47/p49代码放到一个main函数中,可以用GDB单步跟踪一下,理解更深刻
p78,转换规则可以写几行代码测试一下。
作业 小伙伴:20135303
因为本题受12次操作的限制,故不能按位计算是否该位为1。考虑到本题只需要判断1的个数的奇偶性,而并不需要计算一共有多少个1。那么我们考虑到如果能去掉偶数个1对结果并不会产生影响,这需要快速的去掉偶数个1。因为异或运算恰好可以把同为1时变成0。然后在利用分治的方法,整体异或来减少操作次数。
操作:1
1.前16和后16位对齐后异或,那么这时候原来32位的奇偶性和目前异或出来的16位的结果一致。
2.同理前8位和后8位对齐异或。
3.同理前4位和后4位对齐异或。
4.同理前2位和后2位对齐异或。
5.同理前1位和后1位对齐异或。
最后只需要判断最后那一位上是1还是 0即可。
代码:
int even_ones(unsigned x)
{
unsigned y = x >> 16; x ^= y;
y = x >> 8; x ^= y;
y = x >> 4; x ^= y;
y = x >> 2; x ^= y;
y = x >> 1; x ^= y;
return !(x & 1);
}
问题:用了这个编辑器不能上传图片了。。。
[1]: ./images/1.png "1.png"