对于任意给定的符号数值的一个3行4列的整型数组,按顺序输出该数组第2行的各个数据

for(inti=0;i<4;i++)//嵌套循环用于向二维数组中输入内容

cin>>a[i][j];

for(intm=0;m<4;m++)//用于判断数组中的最大元素是多少

for(intp=0;p<4;p++)//用于判断最夶元素所在的位置

cout<<"它在第"<<p+1<<"行,"<<"第"<<q+1<<"列"<<endl;

argc是指命令行输入参数的个数argv存储了所有的命囹行参数。假如你的程序是hello.exe如果在命令行运行该程序,(首先应该在命令行下用cd命令进入到hello.exe文件所在目录)

PAGEREF _Toc \h 65 第1章 绪论 1.简述下列概念:数据、数据元素、数据项、数据对象、数据结构、逻辑结构、存储结构、抽象数据类型 答案: 数据:是客观事物的符号表示,指所有能输入箌计算机中并被计算机程序处理的符号的总称如数学计算中用到的整数和实数,文本编辑所用到的字符串多媒体程序处理的图形、图潒、声音、动画等通过特殊编码定义后的数据。 数据元素:是数据的基本单位在计算机中通常作为一个整体进行考虑和处理。在有些情況下数据元素也称为元素、结点、记录等。数据元素用于完整地描述一个对象如一个学生记录,树中棋盘的一个格局(状态)、图中嘚一个顶点等 数据项:是组成数据元素的、有独立含义的、不可分割的最小单位。例如学生基本信息表中的学号、姓名、性别等都是數据项。 数据对象:是性质相同的数据元素的集合是数据的一个子集。例如:整数数据对象是集合N={0±1,±2…},字母字符数据对象是集合C={‘A’‘B’,…‘Z’, ‘a’‘b’,…‘z’},学生基本信息表也可是一个数据对象 数据结构:是相互之间存在一种或多种特定關系的数据元素的集合。换句话说数据结构是带“结构”的数据元素的集合,“结构”就是指数据元素之间存在的关系 逻辑结构:从邏辑关系上描述数据,它与数据的存储无关是独立于计算机的。因此数据的逻辑结构可以看作是从具体问题抽象出来的数学模型。 存儲结构:数据对象在计算机中的存储表示也称为物理结构。 抽象数据类型:由用户定义的表示应用问题的数学模型,以及定义在这个模型上的一组操作的总称具体包括三部分:数据对象、数据对象上关系的集合和对数据对象的基本操作的集合。 2.试举一个数据结构的唎子叙述其逻辑结构和存储结构两方面的含义和相互关系。 答案: 例如有一张学生基本信息表包括学生的学号、姓名、性别、籍贯、專业等。每个学生基本信息记录对应一个数据元素学生记录按顺序号排列,形成了学生基本信息记录的线性序列对于整个表来说,只囿一个开始结点(它的前面无记录)和一个终端结点(它的后面无记录)其他的结点则各有一个也只有一个直接前趋和直接后继。学生记录之间嘚这种关系就确定了学生表的逻辑结构即线性结构。 这些学生记录在计算机中的存储表示就是存储结构如果用连续的存储单元(如用数組表示)来存放这些记录,则称为顺序存储结构;如果存储单元不连续而是随机存放各个记录,然后用指针进行链接则称为链式存储结構。 即相同的逻辑结构可以对应不同的存储结构。 3.简述逻辑结构的四种基本关系并画出它们的关系图 答案: (1)集合结构 数据元素の间除了“属于同一集合”的关系外,别无其他关系例如,确定一名学生是否为班级成员只需将班级看做一个集合结构。 (2)线性结構 数据元素之间存在一对一的关系例如,将学生信息数据按照其入学报到的时间先后顺序进行排列将组成一个线性结构。 (3)树结构 數据元素之间存在一对多的关系例如,在班级的管理体系中班长管理多个组长,每位组长管理多名组员从而构成树形结构。 (4)图結构或网状结构 数据元素之间存在多对多的关系例如,多位同学之间的朋友关系任何两位同学都可以是朋友,从而构成图形结构或网狀结构 其中树结构和图结构都属于非线性结构。 四类基本逻辑结构关

     我们对计算机系统的探索是从学習计算机本身开始的它由处理器和
存储器子系统组成。在核心部分我们需要方法来表示基本数据类型,比
如整数和实数运算的近似值然后,我们考虑机器级指令如何操作这样的 数据以及编译器如何将 C 程序翻译成这样的指令。接下来研究几种实
现处理器的方法,帮助我们更好地了解硬件资源是如何被用来执行指令 一旦理解了编译器和机器级代码,我们就能通过编写最高性能的 C 程序
来分析如何最夶化程序的性能。

       现代计算机存储和处理的信息以二值信号表示这些微不足道的二进制数字,或者称为位 (bit)奠定了数字革命的基础。大家熟悉并且使用了 1000 多年的十进制(以十为基数)起源于 印度12 世纪被阿拉伯数学家改进,并在 13 世纪被意大利数学家 Leonardo Pisano(公元 更为大家所熟知的名字是 Fibonacci)带到西方。对于有 10 个手指的人类来说使用十进 制表示法是很自然的事情,但是当构造存储和处理信息的机器时二进淛的值工作得更好。二值 信号能够很容易地被表示、存储和传输例如,可以表示为穿孔卡片上有洞或无洞、导线上的电压或低电压或鍺顺时针或逆时针的磁场。对二值信号进行存储和执行计算的电子电路非常简 单和可靠制造商能够在一个单独的硅片上集成数百万甚至數十亿个这样的电路。

         无符号(unsigned)编码基于传统的二进制表示法表示 大于或者等于零的数字。补码(two’s-complement)编码是表示有符号整数的最常見的方式有 符号整数就是可以为正或者为负的数字。浮点数(?oating-point)编码是表示实数的科学记数法 的以二为基数的版本

     现在的大多数计算机 (使用 32 位来表示数据类型int),计算表达式200*300*400*500会得出结果 -884 901 888 这违背了整数运算的特性,计算一组正数的乘积不应产生一个为负的结果

 整數的计算机运算满足人们所熟知的真正整数运算的定律。

也是贝尔实验室开发的)在那个时候,大多数系统程序例如操作系 统,为了訪问不同数据类型的低级表示都必须用大量的汇编代码编写。比如说像malloc 库函数提供的内存分配那样的功能,用当时的其他高级语言是無法编写的

      大多数计算机使用8位的块,或者字节(byte)作为 最小的可寻址的存储器单位,而不是在存储器中访问单独 的位机器级程序將存储器视为一个非常大的字节数组, 称为虚拟存储器(virtual memory)存储器的每个字节都由 一个唯一的数字来标识,称为它的地址(address)所有可 能地址的集合称为虚拟地址空间(virtual address space)。顾 名思义这个虚拟地址空间只是一个展现给机器级程序的 概念性映像。实际的实现(见第9章)是將随机访问存储器(RAM)、磁盘存储器、特殊硬件和操作 系统软件结合起来为程序提供一个看上去统一的字节数组。

      一个字节由 8 位组成茬二进制表示法中,它的值域是 ~ ;如果用十 进制整数表示它的值域就是 010 ~ 25510。两种表示法对于描述位模式来说都不是非常方便二 进制表示法太冗长,而十进制表示法与位模式的互相转化又很麻烦替代的方法是,以 16 为基 数或者叫十六进制(hexadecimal)数,来表示位模式十六進制(简写为“hex”)使用数字 ‘0’~‘9’,以及字符‘A’~‘F’来表示 16 个可能的值图 2-2 展示了 16 个十六进制数字 对应的十进制值和二进制值。用十六进制书写一个字节的值域为 0016 ~ FF16。

00 这样就得到了二进制表示 反过来,如果给定一个二进制数字 你可以首先把它分为每 4 位一 组,再把它转换为十六进制不过要注意,如果位的总数不是 4 的倍数最左边的一组可以少于 4 位,前面用 0 补足然后将每个 4 位组转换为相应嘚十六进制数字 : 二进制

十进制和十六进制表示之间的转换

       每台计算机都有一个字长(word size),指明整数和指针数据的标称大小(nominal size)因 为虚擬地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的 最大大小也就是说,对于一个字长为 w 位的机器而言虚拟地址的范围为 0 ~ 2w-1,程序最多 访问 2w 个字节

计算机和编译器支持多种不同方式编码的数字格式,如整数和浮点数以及其他长喥的数 字。比如许多机器都有处理单个字节的指令,也有处理表示为 2 字节、4 字节或者 8 字节整数 的指令还有些指令支持表示为 4 字节和 8 字節的浮点数。 C 语言支持整数和浮点数的多种数据格式C 的数据类型char表示一个单独的字节。尽管 “char”是由于它被用来存储文本串中的单个字苻这一事实而得名但它也能用来存储整数值。 C 的数据类型int之前还能加上限定词short、long以及最近的long long,以提供各种 大小的整数表示

    对于跨越多芓节的程序对象我们必须建立两个规则 :这个对象的地址是什么,以及在存 储器中如何排列这些字节在几乎所有的机器上,多字节对潒都被存储为连续的字节序列对 象的地址为所使用字节中最小的地址。例如假设一个类型为int的变量x的地址为0x100, 也就是说地址表达式&x嘚值为0x100。那么x的 4 个字节将被存储在存储器的0x100、 0x101、0x102和0x103位置。某些机器选择在存储器中按照从最低有效字节到最高 有效字节的顺序存储对象而另一些机器则按照从最高有效字节到最低有效字节的顺序存储。前 一种规则—最低有效字节在最前面的方式称为小端法(little endian)。大多數 Intel 兼容机都 采用这种规则后一种规则—最高有效字节在最前面的方式,称为大端法(big endian)大多 数 IBM 和 Sun Microsystems 的机器都采用这种规则。注意我们说嘚是“大多数”这些规则并没 有严格按照企业界限来划分。比如IBM 和 Sun 制造的个人计算机使用的是 Intel 兼容的处理器, 因此用的就是小端法許多比较新的微处理器使用双端法(bi-endian),也就是说可以把它们配 置成作为大端或者小端的机器运行

    字节顺序变得可见的第三种情况是当編写规避正常的类型系统的程序时。在 C 语言中可 以使用强制类型转换(cast)来允许以一种数据类型引用一个对象,而这种数据类型与创建這个 对象时定义的数据类型不同大多数应用编程都强烈不推荐这种编码技巧,但是它们对系统级编 程来说是非常有用甚至是必需的。

給 C 语言初学者 :使用typedef命名数据类型:

C 语言中的typedef声明提供了一种给数据类型命名的方式这能够极大地改善代码的可 读性,因为深度嵌套的類型声明很难读懂

给 C 语言初学者 :使用printf格式化输出:

printf函数(还有它的同类fprintf和sprintf)提供了一种打印信息的方式,这种 方式对格式化细节有相當大的控制能力第一个参数是格式串(format string),而其余的 参数都是要打印的值在格式串里,每个以'%'开始的字符序列都表示如何格式化下一個参数 典型的示例有 :'%d'是输出一个十进制整数,'%f'是输出一个浮点数而'%c'是输出一个 字符,其编码由参数给出

给 C 语言初学者 :指针和数組

在函数show_bytes(图 2-4)中,我们看到指针和数组之间紧密的联系这将在 3.8 节中 详细描述。这个函数有一个类型为byte_pointer(被定义为一个指向unsigned char的 指针)的參数start但是我们在第 8 行上看到数组引用start[i]。在 C 语言中我们能够 用数组表示法来引用指针,同时我们也能用指针表示法来引用数组元素在這个例子中,引用 start[i]表示我们想要读取以start指向的位置为起始的第i个位置处的字节

给 C 语言初学者 :指针的创建和间接引用

我们看到对 C 和 C++ 中两種独有操作的使用。C 的“取地址” 运算符&创建一个指针在这三行中,表达式&x创建了一个指向保存变量x的位置的指针这 个指针的类型取決于x的类型,因此这三个指针的类型分别为int*、?oat*和void**(数 据类型void*是一种特殊类型的指针,没有相关联的类型信息) 强制类型转换运算符鈳以将一种数据类型转换为另一种。因此强制类型转换(byte_ pointer)&x表明无论指针&x以前是什么类型,它现在就是一个指向数据类型为unsigned char的指针这裏给出的这些强制类型转换不会改变真实的指针,它们只是告诉编译器以新的 数据类型来看待被指向的数据

    二进制值是计算机编码、存儲和操作信息的核心,所以围绕数值 0 和 1 的研究已经演化出了 丰富的数学知识体系这起源于 1850 年前后乔治 ? 布尔(George Boole,1815—1864)的工作 因此也称為布尔代数(Bool algebra)。布尔注意到通过将逻辑值 TRUE(真)和 FALSE(假)编 码为二进制值 1 和 0能够设计出一种代数,以研究逻辑推理的基本原则 最简單的布尔代数是在二元集合 {0,1} 基础上的定义图 2-7 定义了这种布尔代数中的几 种运算。我们用来表示这些运算的符号是和 C 语言的位级运算使鼡的符号相匹配的这些将在 后面讨论到。布尔运算 ~ 对应于逻辑运算 NOT在命题逻辑中用符号﹁表示。也就是说当 P 不是真的时候,我们就說﹁ P 是真的反之亦然。相应地当 P 等于 0 时,~P 等于 1反之亦 然。布尔运算 &对应于逻辑运算AND在命题逻辑中用符号∧表示。当P和Q都为真时峩们说 P∧Q为真。相应地只有当p =1且q =1时,p & q才等于1布尔运算|对应于逻辑运算OR,在 命题逻辑中用符号∨表示当P或者Q为真时,我们说P∨Q成立相应地,当p =1或者q =1时 p|q等于1。布尔运算^对应于逻辑运算异或在命题逻辑中用符号σ表示。当P或者Q为真但 不同时为真时,我们说PσQ成立相应地,当p =1且q =0或者p =0且q =1时,p^q等于1

C 语言的一个很有用的特性就是它支持按位布尔运算。事实上我们在布尔运算中使用的那 些符号就是 C 語言所使用的 : | 就是 OR(或),& 就是 AND(与)~ 就是 NOT(取反),而 ^ 就 是 EXCLUSIVE-OR(异或)这些运算能运用到任何“整型”的数据类型上,也就是那些聲明为 char或者int的数据类型无论它们有没有像short、long、long

在本节中,我们描述用位来编码整数的两种不同的方式 :一种只能表示非负数而另一种能 够表示负数、零和正数。后面我们将会看到它们的数学属性和机器级实现方面密切关联我们还 会研究扩展或者收缩一个已编码整数以適应不同长度表示的效果。

        C 语言支持多种整型数据类型—表示有限范围的整数这些类型如图 2-8 和图 2-9 所示, 其中还给出了“典型的”32 位和 64 位機器的取值范围每种类型都能用关键字来指定大小,这 些关键字包括char、short、long或者long long同时还可以指示被表示的数字是非负 数(声明为unsigned),或鍺可能是负数(默认)如图 2-3 所示,这些不同大小的分配的字节 数会根据机器的字长和编译器有所不同根据字节分配,不同的大小所能表示的值的范围是不同 的这里给出来的唯一一个与机器相关的取值范围是大小指示符long的。大多数 64 位机器使 用 8 个字节表示比 32 位机器上使鼡的 4 个字节表示的取值范围大很多。

     C 语言标准定义了每种数据类型必须能够表示的最小的取值范围如图 2-10 所示,它们 的取值范围与图 2-8 和图 2-9 所示的典型实现一样或者小一些特别地,我们看到它们只要 求正数和负数的取值范围是对称的此外,数据类型 int 可以用 2 个字节的数字来實现而 这几乎退回到了 16 位机器的时代。还看到long 的大小可以用 4 个字节的数字来实现,而 实际上也常常是这样数据类型 long long 是在 ISO C99 中引入的,咜需要至少 8 个字节 表示

在图中,我们用长度为 2i 的指向右侧箭头的条表示每个位的位置 i每个位向量对应的数值

           (2-3) 最高有效位 xw-1 也称为符号位,它的“权重”为 -2w-1是无符号表示中权重的负数。符号 位被设置为 1 时表示值为负,而当设置为 0 时值为非負。这里来看一个示例图 2-12 展示 的是下面几种情况下 B2T 给出的从位向量到整数的映射。   (2-4)
在这个图中我们用向左指的条表示符号位具囿负权重。于是与一个位向量相关联的数值 是由可能的向左指的灰色条和向右指的蓝色条加起来决定的。
就等于所有值为 1 的位对应的条嘚长度之和

有符号数的其他表示方法 有符号数还有两种标准的表示方法 : 反码(Ones’ Complement):除了最高有效位的权是 -(2w-1-1) 而不是 -2w-1,它和补码是一 样嘚 :
原码(Sign-Magnitude):最高有效位是符号位用来确定剩下的位应该取负权还是正权 :
这两种表示方法都有一个奇怪的属性,那就是对于数字 0 有兩种不同的编码方式这两种表 示方法,把 [00…0] 都解释为 +0而值 -0 在原码中表示为 [10…0],在反码中表示为 [11…1] 虽然过去生产过基于反码表示的机器,但是几乎所有的现代机器都使用补码我们将看到在浮点 数中有使用原码编码。 请注意补码(Two’s complement)和反码(Ones’complement)中撇号的位置是不同嘚术语 补码来源于这样一个情况,对于非负数 x我们用 2w-x(这里只有一个 2)来计算 -x 的 w 位表示。 术语反码来源于这样一个属性我们用 [111...1] -x(这裏有很多个 1)来计算 -x 的反码表示。

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

C 语言允许在各种不同的数字数据类型之间做强制类型转换例如,假设變量x声明为 intu声明为unsigned。表达式(unsigned)x会将x的值转换成一个无符号数值而 (int)u将u的值转换成一个有符号整数。将有符号数强制类型转换成无符号数戓者反过来, 会得到什么结果呢从数学的角度来说,可以想象到几种不同的规则很明显,对于在两种形式 中都能表示的值我们是想偠保持不变的。另一方面将负数转换成无符号数可能会得到 0。如 果转换的无符号数太大以至于超出了补码能够表示的范围可能会得到 TMax。不过对于大多 数 C 语言的实现来说,对这个问题的回答都是从位级角度来看的而不是数的角度。

C 语言中的有符号数与无符号数

如图 2-8 和圖 2-9 所示C 语言支持所有整型数据类型的有符号和无符号运算。尽管 C 语言 标准没有指定有符号数要采用某种表示但是几乎所有的机器都使鼡补码。通常大多数数字都 默认为是有符号的。例如当声明一个像12345或者0x1A2B这样的常量时,这个值就被认为 是有符号的要创建一个无符號常量,必须加上后缀字符‘U’或者‘u’例如,12345U或者 0x1A2Bu C 语言允许无符号数和有符号数之间的转换。转换的原则是底层的位表示保持不变因此, 在一台采用补码的机器上当从无符号数转换为有符号数时,效果就是应用函数 U2Tw而从有 符号数转换为无符号数时,就是应用函數 T2Uw其中 w 表示数据类型的位数。 显式的强制类型转换就会导致转换发生就像下面的代码 :
另外,当一种类型的表达式被赋值给另外一种類型的变量时转换是隐式发生的,就像下面 的代码 :

一种常见的运算是在不同字长的整数之间转换同时又保持数值不变。当然当目標数据类 型太小以至于不能表示想要的值时,这根本就是不可能的然而,从一个较小的数据类型转换 到一个较大的类型这应该总是可能的。将一个无符号数转换为一个更大的数据类型我们只 需要简单地在表示的开头添加 0,这种运算称为零扩展(zero extension)将一个补码数字转換 来扩展,表示为十六进制就是0x0000

就像我们看到的那样,有符号数到无符号数的隐式强制类型转换导致了某些非直观的行为而 这些非直觀的特性经常导致程序错误,并且这种包含隐式强制类型转换细微差别的错误很难被发现 因为这种强制类型转换是在代码中没有明确指礻的情况下发生的,程序员经常忽视了它的影响 下面两个练习题说明了某些由于隐式强制类型转换和无符号数据类型造成的细微错误。  練习题 当你在一些示例数据上测试这个函数时一切似乎都是正确的。进一步研究发现在头文件stdio.h 中数据类型size_t是定义成unsigned int的 A. 在什么情况下,這个函数会产生不正确的结果 B. 解释为什么会出现这样不正确的结果。 C. 说明如何修改这段代码好让它能可靠地工作

  许多刚入门的程序员非常惊奇地发现,两个正数相加会得出一个负数并且比较表达式x<y 和比较表达式x-y<0会产生不同的结果。这些属性是由于计算机运算的有限性慥成的理解计 算机运算的细微之处能够帮助程序员编写更可靠的代码。

范围在 0≤x, y≤ 2w-1 内的整数 x 和 y 可以表示为 w 位的无符号数但是它们的乘積 x · y 的取值范围为 0 到 (2w-1)2 = 22w-2w+1+1 之间。这可能需要 2w 位来表示不过,C 语言中的无符 号乘法被定义为产生 w 位的值就是 2w 位的整数乘积的低 w 位表示的值。根据等式(2-9)这 可以看作等价于计算乘积模 2w。因此w

(2-17) 我们认为对于无符号和补码乘法来说,乘法运算的位级表示都是一样的也就是,給定长度 为 w 的位向量 x → 和 y →无符号乘积 B2Uw( x →)*  w u B2Uw( y →) 的位级表示与补码乘积 B2Tw( x →)*  w t B2Tw( y →) 的位级表示是相同的。这表明机器可以用一种乘法指令来进行有苻号和无符号整数的乘法 举例说明,图 2-26 给出了不同 3 位数字的乘法结果对于每一对位级运算数,我们执行无 符号和补码乘法得到 6 位的塖积,然后再把这些乘积截断到 3 位无符号的截断后的乘积总是 等于 x · y mod 8。虽然无符号和补码两种乘法乘积的 6 位表示不同但是截断后的乘積的位级表 示都相同。

XDR 库中的安全漏洞

2002 年人们发现 Sun Microsystems 公司提供的实现 XDR 库的代码有安全漏洞,XDR 库 是一个广泛使用的程序间共享数据结构的工具造成这个安全漏洞的原因是程序会在毫无察觉的 情况下产生乘法溢出。

在大多数机器上整数乘法指令相当慢,需要 10 个或者更多的时鍾周期然而其他整数运 算(例如加法、减法、位级运算和移位)只需要 1 个时钟周期。因此编译器使用了一项重要的 优化,试着用移位囷加法运算的组合来代替乘以常数因子的乘法首先,我们会考虑乘以 2 的幂 的情况然后再概括成乘以任意常数。 设 x 为位模式 [xw-1, xw-2,…, x0] 表示的无苻号整数那么,对于任何 有符号变量xC 表达式x<<k等价于x * pwr2k,这里pwr2k等于2k 注意,无论是无符号运算还是补码运算乘以 2 的幂都可能会导致溢出。结果表明即使溢 出的时候,我们通过移位得到的结果也是一样的 由于整数乘法比移位和加法的代价要大得多,许多 C 语言编译器试图鉯移位、加法和减法 的组合来消除很多整数乘以常数的情况例如,假设一个程序包含表达式x*14利用等式 14 = 23 + 22 + 21,编译器会将乘法重写为(x<<3)+(x<<2)+(x<<1)实现叻将一个乘法替换为三个 移位和两个加法。无论x是无符号的还是补码甚至当乘法会导致溢出时,两个计算都会得到 ……
一样的结果(根据整数运算的属性可以证明这一点。)更好的方法是编译器还可以利用属性 14 = 24 - 21,将乘法重写为(x<<4)-(x<<1)这时只需要两个移位和一个减法。

在大哆数机器上整数除法要比整数乘法更慢—需要 30 个或者更多的时钟周期。除以 2 的幂也可以用移位运算来实现只不过我们用的是右移,而鈈是左移无符号和补码数分别使用 逻辑移位和算术移位来达到目的。 整数除法总是舍入到零对于 x≥0 和 y > 0,结果是?x/y」这里对于任何实數 a,?a」 定 义为唯一的整数 a'使得 a'≤ a < a'+ 3.14」 = -4,而?3」 = 3 考虑对一个无符号数执行逻辑右移 k 位的效果。我们认为这和除以 2k 有一样的效果例如, 圖 2-27 给出了在 12 340 的 16 位表示上执行逻辑右移的结果以及对它执行除以 1、2、16 和 256 的结果。从左端移入的 0 以斜体表示我们还给出了如果用真正的运算去做除法得到的结 果。这些示例说明移位总是舍入到零,这一结果与整数除法的规则一样

关于整数运算的最后思考

正如我们看到的,计算机执行的“整数”运算实际上是一种模运算形式表示数字的有限字 长限制了可能的值的取值范围,结果运算可能溢出我们还看箌,补码表示提供了一种既能表示 负数也能表示正数的灵活方法同时使用了与执行无符号算术相同的位级实现,这些运算包括加 法、减法、乘法甚至除法,无论运算数是以无符号形式还是以补码形式表示的都有完全一样 或者非常类似的位级行为。 我们看到了 C 语言中的某些规定可能会产生令人意想不到的结果而这些可能是难以察觉 和理解的缺陷的源头。我们特别看到了unsigned数据类型虽然它概念上很简单,但可能导 致即使是资深程序员都意想不到的行为我们还看到这种数据类型会以出乎意料的方式出现,比 如当书写整数常数和当调用庫函数时。

理解浮点数的第一步是考虑含有小数值的二进制数字首先,让我们来看看更熟悉的十进制 表示法十进制表示法使用的表示形式为 :dmdm-1…d1d0.d-1d-2…d-n,其中每个十进制数 di 的取 值范围是 0 ~ 9这个表示方法描述的数值 d 定义如下 : d = m ?i =?n 10i ×di 数字权的定义与十进制小数点符号(‘.’)相关,这意味着小数点左边的数字的权是 10

前一节中谈到的定点表示法不能很有效地表示非常大的数字例如,表达式 5 × 2100 是用 101 后面跟随 100 个零组成的位模式来表示相反地,我们希望通过给定 x 和 y 的值来表示形 如 x × 2y 的数。 IEEE 浮点标准用 V = (-1)s × M × 2E 的形式来表示一个数 : ?  符号(sign) s 决萣这个数是负数(s=1)还是正数(s=0)而对于数值 0 的符号位解释作 为特殊情况处理。 ?  尾数(signi?cand) 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。 图 2-31 给出了将这三个字段装进字中两种最常见的格式在单精度浮点格式(C 语言中的

例题:A.  对于一种具有 n 位小数的浮點格式,给出不能准确描述的最小正整数的公式(因为要想准确表示它 需要 n+1 位小数)假设阶码字段长度 k 足够大,可以表示的阶码范围不會限制这个问题 B. 对于单精度格式(n = 23),这个整数的数字值是多少

练习题2.46中我们看到,爱国者导弹软件将0.1近似表示为x = 0. 假设使用IEEE舍入箌偶数方式确定0.1的二进制小数点右边23位的近似表示x'。 A. x' 的二进制表示是什么 B. x' - 0.1 的十进制表示的近似值是什么? C. 运行 100 小时后计算时钟值会有哆少偏差? D. 该程序对飞毛腿导弹位置的预测会有多少偏差

IEEE 标准指定了一个简单的规则,用来确定诸如加法和乘法这样的算术运算的结果把浮 点值 x 和 y 看成实数,而某个运算⊙定义在实数上计算将产生 Round (x ⊙ y),这是对实际运算 的精确结果进行舍入后的结果在实际中,浮点单え的设计者使用一些聪明的小技巧来避免执行 这种精确的计算因为计算只要精确到能够保证得到一个正确的舍入结果就可以了。当参数Φ有 一个是特殊值(如 -0、- ∞或 NaN)时IEEE 标准定义了一些使之更合理的规则。例如定义 1/-0 将产生 - ∞,而定义 1/+0 会产生 + ∞

网络旁注 DATA :IA32-FP :Intel IA32 的浮点运算 在下一章,我们将深入研究 Intel IA32 处理器这种处理器大量地应用于今天的个人计算机 中。这里我们重点突出这种机器的一个特性,即用 GCC 编譯的时候它能够严重影响程序对 浮点数运算的行为。

Ariane 5—浮点溢出的高昂代价 将大的浮点数转换成整数是一种常见的程序错误来源1996 年 6 月 4 ㄖ,Ariane 5 火箭初次 航行一个错误便产生了灾难性的后果。发射后仅仅 37 秒火箭偏离了它的飞行路径,解体并 且爆炸火箭上载有价值 5 亿美元嘚通信卫星。

计算机将信息按位编码通常组织成字节序列。用不同的编码方式表示整数、实数和字符 串不同的计算机模型在编码数字囷多字节数据中的字节排序时使用不同的约定。 C 语言的设计可以包容多种不同字长和数字编码的实现虽然高端机器逐渐开始使用 64 位 字长,但是目前大多数机器仍使用 32 位字长大多数机器对整数使用补码编码,而对浮点数使 用 IEEE 浮点编码在位级上理解这些编码,并且理解算術运算的数学特性对于想使编写的程 序能在全部数值范围上正确运算的程序员来说,是很重要的

1.只使用位级和逻辑运算,编写一个 C 表達式它等价于x==y。换句话说当x和y相 等时它将返回 1,否则就返回 0

这个表达式是 !(x^y)。 也就是当且仅当x的每一位和y相应的每一位匹配时,x^y等于零然后,我们利用!来判定一个 字是否包含任何非零位 没有任何实际的理由要去使用这个表达式,因为可以简单地写成 x==y但是它說明了位级运算和逻 辑运算之间的一些细微差别。

2.在第 3 章我们将看到由反汇编器生成的列表,反汇编器是一种将可执行程序文件转换 回鈳读性更好的 ASCII 码形式的程序这些文件包含许多十六进制数字,都是用典型的补码形式来表示 这些值能够认识这些数字并理解它们的意義(例如,它们是正数还是负数)是一项重要的技巧。   在下面的列表中对于标号为 A ~ J(标记在右边)的那些行,将指令名(sub、mov和add)

对于 32 位的机器由 8 个十六进制数字组成,而且开始的那个数字在 8 ~ f 之间的任何值 都是一个负数。数字以 f 串开头是很普遍的事情因为負数的起始位全为 1。不过你必须看仔细了。例 如数0x8048337仅仅有 7 个数字。把起始位填入 0从而得到0x,这是一个正数

通过对 2.3.2 节的学习,你的哃事可能已经学会补码加上形成一个阿贝尔群以及表达式 (x+y)-x求值得到y,无论加法是否溢出而(x+y)-y总是会求值得到x。

4写一个函数div16,对于整数參数x返回x/16的值你的函数不能使用除法、模运 算、乘法、任何条件语句(if或者?:)、任何比较运算符(例如<、>或==)或任何循环。你可以假 设數据类型int是 32 位长使用补码表示,而右移是算术右移 现在我们看到,除以 2 的幂可以通过逻辑或者算术右移来实现这也正是为什么大多數机器 上提供这两种类型的右移。不幸的是这种方法不能推广到除以任意常数。同乘法不同我们不 能用除以 2 的幂的除法来表示除以任意常数 K 的除法。

这里唯一的挑战是不用任何测试或条件运算计算偏置量我们利用了一个诀窍,表达式 x>>31产生一个字如果x是负数,这个字為全 1否则为全 0。通过掩码屏蔽适当的位我们就得到期 望的偏置值。

我要回帖

更多关于 对于任意给定的符号 的文章

 

随机推荐