CSAPP第二章 信息表示与处理(二) 整数表示

2.2 整数表示

C语言整数取值范围

C 数据类型 32 位系统取值范围 64 位系统取值范围
[signed] char −27-2^{7}−27 ~ 27−12^{7} - 127−1 −27-2^{7}−27 ~ 27−12^{7} - 127−1
unsigned char 000 ~ 28−12^{8} - 128−1 000 ~ 28−12^{8} - 128−1
short −215-2^{15}−215 ~ 215−12^{15} - 1215−1 −215-2^{15}−215 ~ 215−12^{15} - 1215−1
unsigned short 000 ~ 216−12^{16} - 1216−1 000 ~ 216−12^{16} - 1216−1
int −231-2^{31}−231 ~ 231−12^{31} - 1231−1 −231-2^{31}−231 ~ 231−12^{31} - 1231−1
unsigned int 000 ~ 232−12^{32} - 1232−1 000 ~ 232−12^{32} - 1232−1
long −231-2^{31}−231 ~ 231−12^{31} - 1231−1 −263-2^{63}−263 ~ 263−12^{63} - 1263−1
unsigned long 000 ~ 232−12^{32} - 1232−1 000 ~ 264−12^{64} - 1264−1
long long −263-2^{63}−263 ~ 263−12^{63} - 1263−1 −263-2^{63}−263 ~ 263−12^{63} - 1263−1
unsigned long long 000 ~ 264−12^{64} - 1264−1 000 ~ 264−12^{64} - 1264−1
  • char = 8 位
  • short = 16 位
  • int = 32 位
  • long long = 64 位
  • 对于 有符号 n 位整数 :取值范围为
    −2n−1到2n−1−1 -2^{n-1} \quad \text{到} \quad 2^{n-1} - 1 −2n−1到2n−1−1

  • 对于 无符号 n 位整数 :取值范围为
    0到2n−1 0 \quad \text{到} \quad 2^{n} - 1 0到2n−1

  • 唯一随机器位数变化的是 long

    • 32 位系统:long 为 32 位 → 范围含 2312^{31}231
    • 64 位系统(Linux/macOS):long 为 64 位 → 范围含 2632^{63}263

取值范围不是对称的,负数的范围比整 数的范围大1。

C 标准 (如 ISO/IEC 9899)不规定具体位数 ,而是规定每种整型类型必须至少支持的最小取值范围。这是为了保证可移植性。

C 数据类型 最小值 最大值
[signed] char -127 (−27+1-2^7 + 1−27+1) 127 (27−12^7 - 127−1)
unsigned char 0 255 (28−12^8 - 128−1)
short -32 767 (−215+1-2^{15} + 1−215+1) 32 767 (215−12^{15} - 1215−1)
unsigned short 0 65 535 (216−12^{16} - 1216−1)
int -32 767 (−215+1-2^{15} + 1−215+1) 32 767 (215−12^{15} - 1215−1)
unsigned 0 65 535 (216−12^{16} - 1216−1)
long -2 147 483 647 (−231+1-2^{31} + 1−231+1) 2 147 483 647 (231−12^{31} - 1231−1)
unsigned long 0 4 294 967 295 (232−12^{32} - 1232−1)
int32_t -2 147 483 648 (−231-2^{31}−231) 2 147 483 647 (231−12^{31} - 1231−1)
uint32_t 0 4 294 967 295 (232−12^{32} - 1232−1)
int64_t -9 223 372 036 854 775 808 (−263-2^{63}−263) 9 223 372 036 854 775 807 (263−12^{63} - 1263−1)
uint64_t 0 18 446 744 073 709 551 615 (264−12^{64} - 1264−1)

!NOTE

C 和 C++ 都支持有符号(默认)和无符号数。Java 只支持有符号数。

无符号整数

对于一个 www 位的整数数据类型,位向量表示为 x⃗=[xw−1,xw−2,...,x0]\vec{x} = [x_{w-1}, x_{w-2}, \dots, x_0]x =[xw−1,xw−2,...,x0]。将位向量看作二进制表示的数,即可获得其无符号表示。

编码的定义

使用函数 B2UwB2U_wB2Uw (Binary to Unsigned) 来表示:

B2U_w(\vec{x}) \doteq \sum_{i=0}^{w-1} x_i 2^i

* **xix_ixi** :位向量中的第 iii 位,取值为 0 或 1。 * **2i2\^i2i** :第 iii 位对应的权重(由 2 的幂组成)。 *** ** * ** *** **映射示例** 通过将每个值为 1 的位对应的权重相加得到最终的十进制数值: | 位向量 \[x3,x2,x1,x0\]\[x_3, x_2, x_1, x_0\]\[x3,x2,x1,x0\] | 计算过程 | 十进制结果 | |:---------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------|:-------| | \[0001\]\[0001\]\[0001\] | 0⋅23+0⋅22+0⋅21+1⋅20=0+0+0+10\\cdot2\^3 + 0\\cdot2\^2 + 0\\cdot2\^1 + 1\\cdot2\^0 = 0+0+0+10⋅23+0⋅22+0⋅21+1⋅20=0+0+0+1 | **1** | | \[0101\]\[0101\]\[0101\] | 0⋅23+1⋅22+0⋅21+1⋅20=0+4+0+10\\cdot2\^3 + 1\\cdot2\^2 + 0\\cdot2\^1 + 1\\cdot2\^0 = 0+4+0+10⋅23+1⋅22+0⋅21+1⋅20=0+4+0+1 | **5** | | \[1011\]\[1011\]\[1011\] | 1⋅23+0⋅22+1⋅21+1⋅20=8+0+2+11\\cdot2\^3 + 0\\cdot2\^2 + 1\\cdot2\^1 + 1\\cdot2\^0 = 8+0+2+11⋅23+0⋅22+1⋅21+1⋅20=8+0+2+1 | **11** | | \[1111\]\[1111\]\[1111\] | 1⋅23+1⋅22+1⋅21+1⋅20=8+4+2+11\\cdot2\^3 + 1\\cdot2\^2 + 1\\cdot2\^1 + 1\\cdot2\^0 = 8+4+2+11⋅23+1⋅22+1⋅21+1⋅20=8+4+2+1 | **15** | *** ** * ** *** **取值范围** 无符号数能够表示的数值范围由位数 www 决定: * **最小值 (UMinwUMin_wUMinw)** :位向量为 \[00...0\]\[00\\dots0\]\[00...0\] 时,对应整数 **0**。 * **最大值 (UMaxwUMax_wUMaxw)** :位向量为 \[11...1\]\[11\\dots1\]\[11...1\] 时,对应整数 2w−12\^w - 12w−1。 * 公式:UMaxw≐∑i=0w−12i=2w−1UMax_w \\doteq \\sum_{i=0}\^{w-1} 2\^i = 2\^w - 1UMaxw≐∑i=0w−12i=2w−1 * 例如:4位最大值为 24−1=152\^4 - 1 = 1524−1=15。 *** ** * ** *** **编码的唯一性 (双射)** 函数 B2UwB2U_wB2Uw 是一个**双射 (Bijection)**。 * **含义** : 1. 每个长度为 www 的位向量都有唯一一个整数值与之对应。 2. 反之,在 0∼2w−10 \\sim 2\^w-10∼2w−1 范围内的每一个整数,都有唯一一个长度为 www 的位模式与之对应。 * **反函数** :U2BwU2B_wU2Bw (Unsigned to Binary),即将十进制无符号数转换回二进制位模式。 #### 补码 补码是计算机中表示有符号整数(Signed Integers)最常见的形式。其核心逻辑是将字(word)的**最高有效位(MSB)**解释为**负权重(Negative Weight)**。 对于一个 www 位的位向量 x⃗=\[xw−1,xw−2,...,x0\]\\vec{x} = \[x_{w-1}, x_{w-2}, \\dots, x_0\]x =\[xw−1,xw−2,...,x0\],其补码值通过函数 B2TwB2T_wB2Tw(Binary to Two's-complement)计算: B2Tw(x⃗)≐−xw−1⋅2w−1+∑i=0w−2xi⋅2iB2T_w(\\vec{x}) \\doteq -x_{w-1} \\cdot 2\^{w-1} + \\sum_{i=0}\^{w-2} x_i \\cdot 2\^iB2Tw(x )≐−xw−1⋅2w−1+i=0∑w−2xi⋅2i * **符号位** :最高位 xw−1x_{w-1}xw−1 被称为符号位。 * 当 xw−1=1x_{w-1} = 1xw−1=1 时,表示值为负(权重为 −2w−1-2\^{w-1}−2w−1)。 * 当 xw−1=0x_{w-1} = 0xw−1=0 时,表示值为非负。 **计算示例 (w=4w=4w=4):** | 位模式 | 权重分解计算 | 结果值 | |:---------|:----------------------------------------------------------------------------------------------------------------------------|:----| | `[0001]` | −0⋅23+0⋅22+0⋅21+1⋅20=1-0\\cdot2\^3 + 0\\cdot2\^2 + 0\\cdot2\^1 + 1\\cdot2\^0 = 1−0⋅23+0⋅22+0⋅21+1⋅20=1 | 1 | | `[0101]` | −0⋅23+1⋅22+0⋅21+1⋅20=4+1-0\\cdot2\^3 + 1\\cdot2\^2 + 0\\cdot2\^1 + 1\\cdot2\^0 = 4+1−0⋅23+1⋅22+0⋅21+1⋅20=4+1 | 5 | | `[1011]` | −1⋅23+0⋅22+1⋅21+1⋅20=−8+2+1-1\\cdot2\^3 + 0\\cdot2\^2 + 1\\cdot2\^1 + 1\\cdot2\^0 = -8+2+1−1⋅23+0⋅22+1⋅21+1⋅20=−8+2+1 | -5 | | `[1111]` | −1⋅23+1⋅22+1⋅21+1⋅20=−8+4+2+1-1\\cdot2\^3 + 1\\cdot2\^2 + 1\\cdot2\^1 + 1\\cdot2\^0 = -8+4+2+1−1⋅23+1⋅22+1⋅21+1⋅20=−8+4+2+1 | -1 | 由于**最高位的负权重特性**,补码表示的数值范围具有不对称性。 * **最小值 (TMinwTMin_wTMinw)** :位向量为 `[10...0]` * 公式:TMinw=−2w−1TMin_w = -2\^{w-1}TMinw=−2w−1 * **最大值 (TMaxwTMax_wTMaxw)** :位向量为 `[01...1]` * 公式:TMaxw=∑i=0w−22i=2w−1−1TMax_w = \\sum_{i=0}\^{w-2} 2\^i = 2\^{w-1} - 1TMaxw=∑i=0w−22i=2w−1−1 **以 w=4w=4w=4 为例:** * TMin4=−23=−8TMin_4 = -2\^3 = -8TMin4=−23=−8 * TMax4=23−1=7TMax_4 = 2\^3 - 1 = 7TMax4=23−1=7 *** ** * ** *** 函数 B2TwB2T_wB2Tw 是一个**双射**。这意味着: 1. 每个长度为 www 的位模式对应唯一一个 TMinTMinTMin 到 TMaxTMaxTMax 之间的数字。 2. 反之,在该范围内的每个数字也都有唯一的 www 位补码编码(由反函数 T2BwT2B_wT2Bw 定义)。 这一特性保证了有符号数在计算机底层表示的确定性,与无符号数原理相似。 > \[!NOTE\] > > 补码(Two's Complement)与无符号(Unsigned)编码最本质的区别在于对位向量\*\*最高有效位(MSB)\*\*的权重解释。 > > 对于一个 www 位的位向量 x⃗=\[xw−1,xw−2,...,x0\]\\vec{x} = \[x_{w-1}, x_{w-2}, \\dots, x_0\]x =\[xw−1,xw−2,...,x0\]: > > * **无符号编码 (B2UwB2U_wB2Uw)** :所有位均具有**正权重** 。 > B2Uw(x⃗)=∑i=0w−1xi⋅2i B2U_w(\\vec{x}) = \\sum_{i=0}\^{w-1} x_i \\cdot 2\^i B2Uw(x )=i=0∑w−1xi⋅2i > *其最高位的权重为:+2w−1+2\^{w-1}+2w−1* > > * **补码编码 (B2TwB2T_wB2Tw)** :最高有效位具有**负权重** 。 > B2Tw(x⃗)=−xw−1⋅2w−1+∑i=0w−2xi⋅2i B2T_w(\\vec{x}) = \\mathbf{-x_{w-1} \\cdot 2\^{w-1}} + \\sum_{i=0}\^{w-2} x_i \\cdot 2\^i B2Tw(x )=−xw−1⋅2w−1+i=0∑w−2xi⋅2i > *其最高位的权重为:−2w−1-2\^{w-1}−2w−1* > > 当位模式为 `1011` 时,两者的解释差异如下: > > | 编码方式 | 最高位权重 | 计算过程 | 最终数值 | > |:--------|:-----------------------|:-------------------------------|:-------| > | **无符号** | +23=8+2\^3 = 8+23=8 | 8+0+2+18 + 0 + 2 + 18+0+2+1 | **11** | > | **补码** | −23=−8-2\^3 = -8−23=−8 | −8+0+2+1-8 + 0 + 2 + 1−8+0+2+1 | **-5** | > \[!IMPORTANT\] > > 练习题 2.17 > > > > 假设 w=4w=4w=4,我们能给每个可能的十六进制数字赋予一个数值,假设用一个无符号或者补码表示。请根据这些表示,通过写出等式 (2.1) 和等式 (2.3) 所示的求和公式中的 2 的非零次幂,填写下表: > > 答案 > > 根据无符号编码 B2UwB2U_wB2Uw(全部权重为正)和补码编码 B2TwB2T_wB2Tw(最高位权重为负)的定义,完成表格如下: > > | 十六进制 | 二进制 \\vec{x} | 无符号 B2U_4(\\vec{x}) | 补码 B2T_4(\\vec{x}) | > |:--------|:-------------|:-------------------------------|:--------------------------------| > | **0xE** | `[1110]` | 2\^3 + 2\^2 + 2\^1 = 14 | -2\^3 + 2\^2 + 2\^1 = -2 | > | **0x0** | `[0000]` | 0 | 0 | > | **0x5** | `[0101]` | 2\^2 + 2\^0 = 5 | 2\^2 + 2\^0 = 5 | > | **0x8** | `[1000]` | 2\^3 = 8 | -2\^3 = -8 | > | **0xD** | `[1101]` | 2\^3 + 2\^2 + 2\^0 = 13 | -2\^3 + 2\^2 + 2\^0 = -3 | > | **0xF** | `[1111]` | 2\^3 + 2\^2 + 2\^1 + 2\^0 = 15 | -2\^3 + 2\^2 + 2\^1 + 2\^0 = -1 | 在计算机系统中,针对不同的字长 www,无符号数和补码表示的范围和特殊数值如下表所示: | 数值符号 | 数值含义 | 字长 w=8w=8w=8 | 字长 w=16w=16w=16 | 字长 w=32w=32w=32 | 字长 w=64w=64w=64 | |:---------------------|:---------|:-------------|:----------------|:--------------------------|:----------------------------------------------| | **UMaxwUMax_wUMaxw** | 无符号最大值 | 0xFF 255 | 0xFFFF 65 535 | 0xFFFFFFFF 4 294 967 295 | 0xFFFFFFFFFFFFFFFF 18 446 744 073 709 551 615 | | **TMinwTMin_wTMinw** | 补码最小值 | 0x80 -128 | 0x8000 -32 768 | 0x80000000 -2 147 483 648 | 0x8000000000000000 -9 223 372 036 854 775 808 | | **TMaxwTMax_wTMaxw** | 补码最大值 | 0x7F 127 | 0x7FFF 32 767 | 0x7FFFFFFF 2 147 483 647 | 0x7FFFFFFFFFFFFFFF 9 223 372 036 854 775 807 | | **-1** | 补码表示的 -1 | 0xFF | 0xFFFF | 0xFFFFFFFF | 0xFFFFFFFFFFFFFFFF | | **0** | 数值 0 | 0x00 | 0x0000 | 0x00000000 | 0x0000000000000000 | * **补码范围的不对称性** :补码能够表示的负数比正数多一个,即 ∣TMin∣=∣TMax∣+1\|TMin\| = \|TMax\| + 1∣TMin∣=∣TMax∣+1。这是因为一半的位模式(符号位为 1)表示负数,另一半位模式中,符号位为 0 的一部分表示非负数(包含 0),导致正数比负数少一个。 * **UMaxUMaxUMax 与 TMaxTMaxTMax 的关系** :最大无符号数值大约是补码最大值的两倍再加 1,公式为 UMaxw=2TMaxw+1UMax_w = 2TMax_w + 1UMaxw=2TMaxw+1。 * **特殊位模式** : * **全 1 序列** :在补码表示中对应数值 −1-1−1,而在无符号表示中对应最大值 UMaxUMaxUMax。 * **全 0 序列** :在两种表示方式中都对应数值 000。 *** ** * ** *** 虽然 C 语言标准并未强制要求使用补码表示有符号整数,但几乎所有现代机器都采用补码。 * **`` 库** :定义了一组宏来限制不同整型数据的取值范围,如 `INT_MAX`、`INT_MIN` 和 `UINT_MAX`。 * **可移植性建议**:程序员应尽量编写假设典型的取值范围(如补码范围)的代码,以保证在大量机器和编译器上可移植。 与 C 语言不同,Java 对整数类型的取值范围和表示方法有非常严格且统一的要求: * **强制补码表示**:Java 要求采用补码形式表示整数。 * **固定范围**:其取值范围在所有机器上表现完全一致。 * **单字节类型** :Java 中的单字节数据类型称为 `byte`(对应 8 位补码),而非 `char`。 > \[!NOTE\] > > 在 C 语言中,某些基本数据类型(如 `long`)在不同机器上的取值范围可能不同,这会影响程序的**可移植性** 。为了解决这一问题,ISO C99 标准在 `` 中引入了一类确定宽度的整数类型。 > > * **类型声明** :形式为 `intN_t`(有符号)和 `uintN_t`(无符号),其中 NNN 的典型值为 8、16、32 和 64。 > * 例如:`uint16_t` 声明一个 16 位无符号变量;`int32_t` 声明一个 32 位有符号变量。 > * **范围宏定义** :这些类型对应一组宏,定义了每个 NNN 对应的最小值和最大值,如 `INTN_MIN`、`INTN_MAX` 和 `UINTN_MAX`。 > > *** ** * ** *** > > 由于这些类型的具体实现与系统相关,打印它们时需要使用 `` 中定义的**宏**,以确保生成的格式字符串在不同编译环境下均正确。 > > * **使用方式** :通过宏(如 `PRId32`、`PRIu64`)来扩展格式化字符串。 > > * **示例代码**: > > ```c > int32_t x; > uint64_t y; > // 使用宏进行跨平台格式化打印 > printf("x = %" PRId32 ", y = %" PRIu64 "\n", x, y); > ``` > > * **编译原理** :在 64 位程序中,宏 `PRId32` 通常展开为 `"d"`,而 `PRIu64` 展开为 `"lu"`。C 预处理器会将相邻的字符串常量拼接在一起,最终生成类似 `"x = %d, y = %lu\n"` 的字符串。 #### 反码 除了最高有效位的权重是 −(2w−1−1)-(2\^{w-1}-1)−(2w−1−1) 而不是 −2w−1-2\^{w-1}−2w−1,它和补码是一样的。 B2Ow(x⃗)≐−xw−1(2w−1−1)+∑i=0w−2xi2i B2O_w(\\vec{x}) \\doteq -x_{w-1}(2\^{w-1}-1) + \\sum_{i=0}\^{w-2} x_i 2\^i B2Ow(x )≐−xw−1(2w−1−1)+i=0∑w−2xi2i 对于位向量 \[1011\]\[1011\]\[1011\]: * w=4w = 4w=4 * x3=1x_3 = 1x3=1 * x2=0,x1=1,x0=1x_2 = 0, x_1 = 1, x_0 = 1x2=0,x1=1,x0=1 B2O4(\[1011\])=−1⋅(24−1−1)+(0⋅22+1⋅21+1⋅20)=−1⋅(23−1)+(0+2+1)=−1⋅(8−1)+3=−7+3=−4 \\begin{aligned} B2O_4(\[1011\]) \&= -1 \\cdot (2\^{4-1} - 1) + (0 \\cdot 2\^2 + 1 \\cdot 2\^1 + 1 \\cdot 2\^0) \\\\ \&= -1 \\cdot (2\^3 - 1) + (0 + 2 + 1) \\\\ \&= -1 \\cdot (8 - 1) + 3 \\\\ \&= -7 + 3 \\\\ \&= -4 \\end{aligned} B2O4(\[1011\])=−1⋅(24−1−1)+(0⋅22+1⋅21+1⋅20)=−1⋅(23−1)+(0+2+1)=−1⋅(8−1)+3=−7+3=−4 #### 原码 最高有效位是符号位,用来确定剩下的位应该取负权重还是正权重。 B2Sw(x⃗)≐(−1)xw−1⋅(∑i=0w−2xi2i) B2S_w(\\vec{x}) \\doteq (-1)\^{x_{w-1}} \\cdot \\left( \\sum_{i=0}\^{w-2} x_i 2\^i \\right) B2Sw(x )≐(−1)xw−1⋅(i=0∑w−2xi2i) 对于位向量 \[1011\]\[1011\]\[1011\]: * w=4w = 4w=4 * 最高位(符号位)x3=1x_3 = 1x3=1 * 剩余数值位 x2=0,x1=1,x0=1x_2 = 0, x_1 = 1, x_0 = 1x2=0,x1=1,x0=1 **计算步骤:** 1. **确定符号部分:** (−1)x3=(−1)1=−1(-1)\^{x_3} = (-1)\^1 = -1(−1)x3=(−1)1=−1 2. **确定数值部分(绝对值):** ∑i=02xi2i=(0⋅22+1⋅21+1⋅20)=0+2+1=3\\sum_{i=0}\^{2} x_i 2\^i = (0 \\cdot 2\^2 + 1 \\cdot 2\^1 + 1 \\cdot 2\^0) = 0 + 2 + 1 = 3i=0∑2xi2i=(0⋅22+1⋅21+1⋅20)=0+2+1=3 3. **组合结果:** B2S4(\[1011\])=−1⋅3=−3B2S_4(\[1011\]) = -1 \\cdot 3 = -3B2S4(\[1011\])=−1⋅3=−3 反码和原码对于数字 0 都有两种不同的编码方式: * **+0 的表示:** 两种方法都将 \[00...0\]\[00\\dots0\]\[00...0\] 解释为 +0+0+0。 * **-0 的表示:** \* 在**原码** 中表示为 \[10...0\]\[10\\dots0\]\[10...0\]。 * 在**反码** 中表示为 \[11...1\]\[11\\dots1\]\[11...1\]。 #### 有符号与无符号的转换 C语言允许不同数字数据类型间强制类型转换,例如 `unsigned` 与 `int` 之间的转换。从位级角度看,转换**不改变位模式** ,**仅改变对位的解释方式**。 ```c // 强制类型转换仅改变解释方式,位表示不变。 // short 转 unsigned short short int v = -12345; unsigned short uv = (unsigned short) v; printf("v = %d, uv = %u\n", v, uv); // 输出 v = -12345, uv = 53191 // -12345 的 16 位补码表示与 53191 的 16 位无符号表示位模式完全相同。 ``` ```c // 强制类型转换仅改变解释方式,位表示不变。 unsigned u = 4294967295u; /* UMax_32 */ int tu = (int) u; printf("u = %u, tu = %d\n", u, tu); // u = 4294967295, tu = -1 ``` 为了精确描述这种位模式不变的转换,我们定义以下函数: UUU 无符号数、TTT补码表示、 BBB 位表示 * **U2Bw(x)U2B_w(x)U2Bw(x)** :将整数 xxx 映射为 www 位无符号数的位表示。 * **T2Bw(x)T2B_w(x)T2Bw(x)** :将整数 xxx 映射为 www 位补码形式的位表示。 * **B2Uw(u)B2U_w(u)B2Uw(u)** :将位向量 uuu 转换为无符号数。 * **B2Tw(u)B2T_w(u)B2Tw(u)** :将位向量 uuu 转换为补码形式的有符号数。 **补码转无符号数 (T2UT2UT2U)** : T2Uw(x)≐B2Uw(T2Bw(x)) T2U_w(x) \\doteq B2U_w(T2B_w(x)) T2Uw(x)≐B2Uw(T2Bw(x)) 输入范围:TMinw∼TMaxwTMin_w \\sim TMax_wTMinw∼TMaxw,结果范围:0∼UMaxw0 \\sim UMax_w0∼UMaxw。 **无符号数转补码 (U2TU2TU2T)** : U2Tw(x)≐B2Tw(U2Bw(x)) U2T_w(x) \\doteq B2T_w(U2B_w(x)) U2Tw(x)≐B2Tw(U2Bw(x)) 输入范围:0∼UMaxw0 \\sim UMax_w0∼UMaxw,结果范围:TMinw∼TMaxwTMin_w \\sim TMax_wTMinw∼TMaxw。 > \[!IMPORTANT\] > > 填写下列描述函数 T2U4T2U_4T2U4 的表格。该函数将 444 位补码数值 xxx 转换为对应的无符号数值。 > > 通过保持底层位模式不变,将补码解释为无符号数。对于 4 位字长,2w=24=162\^w = 2\^4 = 162w=24=16。当 x\<0x \< 0x\<0 时,T2U4(x)=x+16T2U_4(x) = x + 16T2U4(x)=x+16。 > > | 位模式 (十六进制) | 补码数值 xxx | 无符号数值 T2U4(x)T2U_4(x)T2U4(x) | 转换逻辑 | > |:-----------|:---------|:-----------------------------|:---------------------------------------------| > | `0x8` | −8-8−8 | **8** | −8+16=8-8 + 16 = 8−8+16=8 (TMinT_MinTMin) | > | `0xD` | −3-3−3 | **13** | −3+16=13-3 + 16 = 13−3+16=13 | > | `0xE` | −2-2−2 | **14** | −2+16=14-2 + 16 = 14−2+16=14 | > | `0xF` | −1-1−1 | **15** | −1+16=15-1 + 16 = 15−1+16=15 (UMaxU_MaxUMax) | > | `0x0` | 000 | **0** | 数值保持不变 | > | `0x5` | 555 | **5** | 数值保持不变 | > > * 对于非负数 (x≥0x \\ge 0x≥0),数值保持不变。 > * 对于负数 (x\<0x \< 0x\<0),无符号值等于补码值加上 2w2\^w2w。 *** ** * ** *** **补码转无符号数** 对于满足 TMinw≤x≤TMaxwTMin_w \\le x \\le TMax_wTMinw≤x≤TMaxw 的补码数值 xxx,其转换为无符号数的公式为: T2Uw(x)={x+2w,x\<0x,x≥0(2.5) T2U_w(x) = \\begin{cases} x + 2\^w, \& x \< 0 \\\\ x, \& x \\ge 0 \\end{cases} \\qquad (2.5) T2Uw(x)={x+2w,x,x\<0x≥0(2.5) * **负数转换** :T2U16(−12345)=−12345+216=53191T2U_{16}(-12345) = -12345 + 2\^{16} = 53191T2U16(−12345)=−12345+216=53191 * **边界情况** :T2Uw(−1)=−1+2w=UMaxwT2U_w(-1) = -1 + 2\^w = UMax_wT2Uw(−1)=−1+2w=UMaxw 通过比较无符号数解码公式 B2UB2UB2U 和补码解码公式 B2TB2TB2T,可以发现: 对于位模式 x⃗\\vec{x}x ,**从 000 到 w−2w-2w−2 位的加权和在两种表示中是完全相同的,它们会在减法中互相抵消**。 唯一不同的位是**最高有效位(MSB)**: * **无符号差值计算** :B2Uw(x⃗)−B2Tw(x⃗)=xw−1(2w−1−(−2w−1))=xw−12wB2U_w(\\vec{x}) - B2T_w(\\vec{x}) = x_{w-1}(2\^{w-1} - (-2\^{w-1})) = x_{w-1} 2\^wB2Uw(x )−B2Tw(x )=xw−1(2w−1−(−2w−1))=xw−12w 由此得到转换关系式: B2Uw(T2Bw(x))=T2Uw(x)=x+xw−12w(2.6) B2U_w(T2B_w(x)) = T2U_w(x) = x + x_{w-1} 2\^w \\qquad (2.6) B2Uw(T2Bw(x))=T2Uw(x)=x+xw−12w(2.6) 在补码表示中,位 xw−1x_{w-1}xw−1 决定了 xxx 的符号: * 如果 x\<0x \< 0x\<0(负数),则 xw−1=1x_{w-1} = 1xw−1=1,值增加 2w2\^w2w。 * 如果 x≥0x \\ge 0x≥0(非负数),则 xw−1=0x_{w-1} = 0xw−1=0,值保持不变。 *** ** * ** *** **无符号数转补码** 对于满足 0≤u≤UMaxw0 \\le u \\le UMax_w0≤u≤UMaxw 的无符号数 uuu,其转换为补码 U2Tw(u)U2T_w(u)U2Tw(u) 的关系如下: U2Tw(u)={u,u≤TMaxwu−2w,u\>TMaxw(2.7) U2T_w(u) = \\begin{cases} u, \& u \\le TMax_w \\\\ u - 2\^w, \& u \> TMax_w \\end{cases} \\qquad (2.7) U2Tw(u)={u,u−2w,u≤TMaxwu\>TMaxw(2.7) 设位向量为 u⃗\\vec{u}u ,其补码表示为 U2Tw(u)U2T_w(u)U2Tw(u)。结合无符号和补码的定义公式,可以得出: U2Tw(u)=−uw−12w−1+u(2.8) U2T_w(u) = -u_{w-1} 2\^{w-1} + u \\qquad (2.8) U2Tw(u)=−uw−12w−1+u(2.8) 在 uuu 的无符号表示中,最高位 uw−1u_{w-1}uw−1 决定了 uuu 是否大于 TMaxw=2w−1−1TMax_w = 2\^{w-1} - 1TMaxw=2w−1−1: * 当 uw−1=0u_{w-1} = 0uw−1=0 时,uuu 在补码表示的范围内。 * 当 uw−1=1u_{w-1} = 1uw−1=1 时,uuu 超出了补码的正数范围,转换后变为负数。 *** ** * ** *** 无符号数与补码之间的相互转换,在位级(Bit-level)表示上是不变的,只是解释位的方式发生了变化。 | 转换方向 | 范围条件 | 结果 | |:-----------------|:---------------------------------------|:-----------------| | **通用** | 0≤x≤TMaxw0 \\le x \\le TMax_w0≤x≤TMaxw | 保持不变 | | **T2U (补码转无符号)** | x\<0x \< 0x\<0 | x+2wx + 2\^wx+2w | | **U2T (无符号转补码)** | u\>TMaxwu \> TMax_wu\>TMaxw | u−2wu - 2\^wu−2w | #### C中的有符号数与无符号数 **默认是有符号** C 语言支持所有整型数据类型的**有符号** 和**无符号** 运算。尽管 C 语言标准没有指定有符号数要采用某一种表示,但是几乎所有的机器都使用**补码**。 通常,**大多数数字都默认为是有符号的** 。例如,声明 `12345` 或 `0x1A2B` 时,它们被视为有符号数。 要创建无符号常量,必须加上后缀字符 `U` 或 `u`(如 `12345U` 或 `0x1A2Bu`)。 **类型转换不改变底层定义** C 语言允许无符号数和有符号数之间的转换。大多数系统遵循的原则是:**底层的位表示保持不变**。 * **T2UwT2U_wT2Uw**:从补码(有符号)转换为无符号数。 * **U2TwU2T_wU2Tw**:从无符号数转换为补码(有符号)。 * 其中 www 表示数据类型的位数。 **显式与隐式转换** **显式强制类型转换**:通过代码手动指定目标类型。 ```c int tx, ty; unsigned ux, uy; tx = (int) ux; uy = (unsigned) ty; ``` **隐式类型转换**:当一种类型的表达式被赋值给另外一种类型的变量时,转换会自动发生。 ```c int tx, ty; unsigned ux, uy; tx = ux; /* Cast to signed */ uy = ty; /* Cast to unsigned */ ``` **printf 输出处理** `printf` 函数不使用任何类型信息,它仅根据格式指示符来解释内存中的位模式: * `%d`:有符号十进制 * `%u`:无符号十进制 * `%x`:十六进制 ```c int x = -1; unsigned u = 2147483648; /* 2 to the 31st */ printf("x = %u = %d\n", x, x); printf("u = %u = %d\n", u, u); /* 32 位机器输出结果: x = 4294967295 = -1 u = 2147483648 = -2147483648 */ ``` **表达式中的隐式转换规则** 当一个运算中同时包含有符号和无符号数时,**C 语言会隐式地将有符号参数转换为无符号数** ,并假设这两个数都是非负的。这在执行关系运算(如 `<`、`>`)时经常会导致非直观的结果。 | 表达式 | 类型 | 求值 | |:---------------------------------|:----|:----| | `0 == 0U` | 无符号 | 1 | | `-1 < 0` | 有符号 | 1 | | `-1 < 0U` | 无符号 | 0\* | | `2147483647 > -2147483647-1` | 有符号 | 1 | | `2147483647U > -2147483647-1` | 无符号 | 0\* | | `2147483647 > (int) 2147483648U` | 有符号 | 1\* | | `-1 > -2` | 有符号 | 1 | | `(unsigned) -1 > -2` | 无符号 | 1 | *注:标有 `*` 的行表示其结果可能由于隐式转换而违背直觉。例如 `-1 < 0U`,由于 `-1` 转换为无符号数是巨大的正数,因此结果为假(0)。* > \[!IMPORTANT\] > > 假设在采用 **补码(Two's Complement)** 运算的 **32 位** 机器上对以下表达式进行求值。 > > > > 根据 C 语言的 **升级规则(Promotion Rule)**:当一个运算数是无符号的,另一个运算数也会被强制转换为无符号数再进行比较。 > > 请填写下表,描述表达式的类型以及求值结果(1 表示真,0 表示假)。 > > | 表达式 | 类型 | 求值 | > |:---------------------------------|:---|:---| > | `-2147483647 - 1 == 2147483648U` | | | > | `-2147483647 - 1 < 2147483647` | | | > | `-2147483647 - 1U < 2147483647` | | | > | `-2147483647 - 1 < -2147483647` | | | > | `-2147483647 - 1U < -2147483647` | | | > > 在 32 位补码机器中: > > * **TMin (有符号最小负数)** : `-2147483647 - 1` = `-2147483648`,其位模式为 `0x80000000`。 > * **TMax (有符号最大正数)** : `2147483647`,其位模式为 `0x7FFFFFFF`。 > > 1. `-2147483647 - 1 == 2147483648U` > > * **计算** :左侧是 `int`,右侧是 `unsigned`。 > * **转换** :左侧结果 `-2147483648` 被强制转换为无符号数。 > * **逻辑** :`-2147483648` 的位模式 `0x80000000` 在无符号解释下等于 2312\^{31}231,即 `2147483648`。 > * **结果** :类型为 **无符号数** ,求值为 **1**。 > > 2. `-2147483647 - 1 < 2147483647` > > * **计算** :两边都是 `int`。 > * **逻辑** :普通的有符号数比较,`-2147483648 < 2147483647` 成立。 > * **结果** :类型为 **有符号数** ,求值为 **1**。 > > 3. `-2147483647 - 1U < 2147483647` > > * **计算** :左侧包含 `1U`,导致整个表达式变为无符号比较。 > * **转换** :右侧的 `2147483647` 被转换为无符号数(值不变)。左侧计算出的位模式 `0x80000000` 转换为无符号数 `2147483648`。 > * **逻辑** :比较 `2147483648 < 2147483647`,不成立。 > * **结果** :类型为 **无符号数** ,求值为 **0**。 > > 4. `-2147483647 - 1 < -2147483647` > > * **计算** :两边都是 `int`。 > * **逻辑** :有符号数比较,`-2147483648 < -2147483647` 成立。 > * **结果** :类型为 **有符号数** ,求值为 **1**。 > > 5. `-2147483647 - 1U < -2147483647` > > * **计算** :左侧包含 `1U`,全式提升为无符号比较。 > * **转换** : > * 左侧:`-2147483648` 的位模式 `0x80000000` →\\rightarrow→ 无符号 `2147483648`。 > * 右侧:`-2147483647` 的位模式 `0x80000001` →\\rightarrow→ 无符号 `2147483649`。 > * **逻辑** :`2147483648 < 2147483649` 成立。 > * **结果** :类型为 **无符号数** ,求值为 **1**。 > > | 表达式 | 类型 | 求值 | > |:---------------------------------|:---------|:------| > | `-2147483647 - 1 == 2147483648U` | **无符号数** | **1** | > | `-2147483647 - 1 < 2147483647` | **有符号数** | **1** | > | `-2147483647 - 1U < 2147483647` | **无符号数** | **0** | > | `-2147483647 - 1 < -2147483647` | **有符号数** | **1** | > | `-2147483647 - 1U < -2147483647` | **无符号数** | **1** | #### 拓展数字 在一个不同字长的整数之间进行转换,同时保持数值不变,是计算机底层常见的运算。 * **从小向大转换**:从较小的数据类型转换到较大的类型,总是可能的。 * **从大向小转换**:可能发生溢出或截断,导致数值改变。 **无符号数的零扩展** 将一个无符号数转换为更大的数据类型时,只需在开头添加 0。 **原理** :定义宽度为 www 的位向量 u⃗=\[ww−1,ww−2,...,w0\]\\vec{u} = \[w_{w-1}, w_{w-2}, \\dots, w_0\]u =\[ww−1,ww−2,...,w0\] 和宽度为 w′w'w′ 的位向量 u⃗′\\vec{u}'u ′,其中 w′\>ww' \> ww′\>w。则 u⃗′=\[0,...,0,ww−1,ww−2,...,w0\]\\vec{u}' = \[0, \\dots, 0, w_{w-1}, w_{w-2}, \\dots, w_0\]u ′=\[0,...,0,ww−1,ww−2,...,w0\]。 **依据**:该原理直接遵循无符号数编码的定义,高位的权值为 0,不影响数值。 **补码数字的符号扩展** 将一个补码数字转换为更大的数据类型时,在表示中添加\*\*最高有效位(符号位)\*\*的值。 **原理** :定义宽度为 www 的位向量 x⃗=\[xw−1,xw−2,...,x0\]\\vec{x} = \[x_{w-1}, x_{w-2}, \\dots, x_0\]x =\[xw−1,xw−2,...,x0\] 和宽度为 w′w'w′ 的位向量 x⃗′\\vec{x}'x ′,其中 w′\>ww' \> ww′\>w。则 x⃗′=\[xw−1,...,xw−1,xw−1,xw−2,...,x0\]\\vec{x}' = \[x_{w-1}, \\dots, x_{w-1}, x_{w-1}, x_{w-2}, \\dots, x_0\]x ′=\[xw−1,...,xw−1,xw−1,xw−2,...,x0\]。 > \[!NOTE\] > > * `sx = -12345`:16 位十六进制为 `cf c7`。 > * `x = (int)sx`:扩展到 32 位。符号位为 1(`c` 的二进制是 `1100`),填充 `f`,得到 `ff ff cf c7`。 > * `usx = 53191`:16 位无符号表示同样为 `cf c7`。 > * `ux = (unsigned)usx`:执行零扩展,填充 0,得到 `00 00 cf c7`。 **符号扩展的证明** 通过归纳法证明:**扩展 1 位且保持数值不变,即可证明扩展任意位。** **证明目标** : B2Tw+1(\[xw−1,xw−1,xw−2,...,x0\])=B2Tw(\[xw−1,xw−2,...,x0\]) B2T_{w+1}(\[x_{w-1}, x_{w-1}, x_{w-2}, \\dots, x_0\]) = B2T_w(\[x_{w-1}, x_{w-2}, \\dots, x_0\]) B2Tw+1(\[xw−1,xw−1,xw−2,...,x0\])=B2Tw(\[xw−1,xw−2,...,x0\]) **推导过程** : B2Tw+1(\[xw−1,xw−1,xw−2,...,x0\])=−xw−12w+∑i=0w−1xi2i=−xw−12w+xw−12w−1+∑i=0w−2xi2i其中(−2w+2w−1)xw−1=−2w−1xw−1=−xw−12w−1+∑i=0w−2xi2i=B2Tw(x⃗) B2T_{w+1}(\[x_{w-1}, x_{w-1}, x_{w-2}, \\dots, x_0\]) =-x_{w-1} 2\^w + \\sum_{i=0}\^{w-1} x_i 2\^i \\\\ = -x_{w-1} 2\^w + x_{w-1} 2\^{w-1} + \\sum_{i=0}\^{w-2} x_i 2\^i \\\\ 其中 (-2\^w + 2\^{w-1})x_{w-1} = -2\^{w-1}x_{w-1} \\\\ = -x_{w-1} 2\^{w-1} + \\sum_{i=0}\^{w-2} x_i 2\^i = B2T_w(\\vec{x}) B2Tw+1(\[xw−1,xw−1,xw−2,...,x0\])=−xw−12w+i=0∑w−1xi2i=−xw−12w+xw−12w−1+i=0∑w−2xi2i其中(−2w+2w−1)xw−1=−2w−1xw−1=−xw−12w−1+i=0∑w−2xi2i=B2Tw(x ) 新增位的权值为 −2w-2\^w−2w,而原符号位权值从 −2w−1-2\^{w-1}−2w−1 变为 +2w−1+2\^{w-1}+2w−1。两者的综合效果是 −2w+2w−1=−2w−1-2\^w + 2\^{w-1} = -2\^{w-1}−2w+2w−1=−2w−1,保持了原始数值的偏移量。 ```c #include // 32位大端法机器运行 typedef unsigned char *byte_pointer; void show_bytes(byte_pointer start, size_t len) { size_t i; for (i = 0; i < len; i++) printf(" %.2x", start[i]); printf("\n"); } int main() { short sx = -12345; /* -12345 */ unsigned short usx = sx; /* 53191 */ int x = sx; /* -12345 */ unsigned ux = usx; /* 53191 */ printf("sx = %d:\t", sx); show_bytes((byte_pointer) &sx, sizeof(short)); // sx = -12345: c7 cf printf("usx = %u:\t", usx); show_bytes((byte_pointer) &usx, sizeof(unsigned short)); // usx = 53191: c7 cf printf("x = %d:\t", x); show_bytes((byte_pointer) &x, sizeof(int)); // x = -12345: c7 cf ff ff printf("ux = %u:\t", ux); show_bytes((byte_pointer) &ux, sizeof(unsigned)); // ux = 53191: c7 cf 00 00 return 0; } ``` **相对顺序的影响** 在 C 语言中,当一个程序同时需要改变数据的大小(如 `short` 到 `int`)以及转换有无符号属性(如 `signed` 到 `unsigned`)时,转换的**相对顺序**会直接影响最终的数值结果。 根据 C 语言标准的要求,当执行此类复合转换时,系统遵循以下顺序: * **第一步:改变大小**(进行符号扩展或零扩展)。 * **第二步:符号转换**(从有符号转换为无符号,或反之)。 **先进行拓展,再进行转换** 考虑以下代码段,其中 `sx` 是一个 16 位有符号短整型,其值为 `-12345`: ```c #include typedef unsigned char *byte_pointer; void show_bytes(byte_pointer start, size_t len) { size_t i; for (i = 0; i < len; i++) printf(" %.2x", start[i]); printf("\n"); } int main() { short sx = -12345; /* -12345 */ unsigned uy = sx; /* Mystery! */ printf("uy = %u:\t", uy); show_bytes((byte_pointer) &uy, sizeof(unsigned)); // 输出 uy = 4294954951: c7 cf ff ff return 0; } ``` 在大端法机器上,执行上述代码会产生如下输出: `uy = 4294954951: ff ff cf c7` **转换过程详解:** 1. **原始值** :`sx = -12345`。其 16 位补码表示为 `cf c7`。 2. **符号扩展** :由于 `sx` 是有符号数,转换到 32 位时进行**符号扩展** 。高 16 位补符号位(1),得到 `ff ff cf c7`。此时数值在逻辑上仍为 `-12345`(如果是 32 位有符号 `int`)。 3. **解释为无符号** :将 `ff ff cf c7` 直接解释为 `unsigned` 类型,得到的十进制数值即为 `4,294,954,951`。 | 转换路径 | 等价表达式 | 内存字节 (大端法) | 十进制结果 | |:-----------|:--------------------------------|:--------------|:------------------| | **标准要求路径** | `(unsigned)(int) sx` | `ff ff cf c7` | **4,294,954,951** | | **错误路径** | `(unsigned)(unsigned short) sx` | `00 00 cf c7` | 53,191 | **结论** :事实证明,C 语言会先将 `short` 符号扩展为 `int`,然后再将其转换为 `unsigned`。 > \[!IMPORTANT\] > > 考虑下面的 C 函数: > > > > ```c > > int fun1(unsigned word) { > > return (int) ((word << 24) >> 24); > > } > > > > int fun2(unsigned word) { > > return ((int) word << 24) >> 24; > > } > > ``` > > > > 假设在一个采用补码运算的机器上以 32 位程序来执行这些函数。还假设有符号数值的右移是**算术右移** ,而无符号数值的右移是**逻辑右移**。 > > * **`fun1`** :提取参数的最低字节(Byte 0),并将其余 24 位**清零** 。其结果始终是一个正整数(000 到 255255255)。 > * **`fun2`** :提取参数的最低字节,并进行**符号扩展**。如果该字节的最高位(第 7 位)是 1,则结果的高 24 位全补 1,将其解释为负数。 > > | `w` | `fun1(w)` | `fun2(w)` | > |:-------------|:-------------|:-------------| > | `0x00000076` | `0x00000076` | `0x00000076` | > | `0x87654321` | `0x00000021` | `0x00000021` | > | `0x000000C9` | `0x000000C9` | `0xFFFFFFC9` | > | `0xEDCBA987` | `0x00000087` | `0xFFFFFF87` | #### 截断数字 截断数字是指减少表示一个数字的位数。例如,当我们将一个 `int`(32位)强制类型转换为 `short`(16位)时,高位会被丢弃,这可能会改变数值的大小,这是**溢出**的一种形式。 *** ** * ** *** **截断无符号数** 假设将一个 www 位的数 x⃗=\[xw−1,xw−2,...,x0\]\\vec{x} = \[x_{w-1}, x_{w-2}, \\dots, x_0\]x =\[xw−1,xw−2,...,x0\] 截断为一个 kkk 位数字时,我们会丢弃高 w−kw-kw−k 位,得到一个位向量 x⃗′=\[xk−1,xk−2,...,x0\]\\vec{x}' = \[x_{k-1}, x_{k-2}, \\dots, x_0\]x ′=\[xk−1,xk−2,...,x0\]。 令 x=B2Uw(x⃗)x = B2U_w(\\vec{x})x=B2Uw(x ),则截断后的值 x′=x mod 2kx' = x \\bmod 2\^kx′=xmod2k。 该原理背后的直觉是所有被截断的位其权重形式都为 2i2\^i2i,其中 i≥ki \\geq ki≥k,因此,每一个权重在取模操作下结果都为零。 B2Uw(\[xw−1,xw−2,...,x0\]) mod 2k=\[∑i=0w−1xi2i\] mod 2k=\[∑i=0k−1xi2i\] mod 2k=∑i=0k−1xi2i=B2Uk(\[xk−1,...,x0\]) B2U_w(\[x_{w-1}, x_{w-2}, \\dots, x_0\]) \\bmod 2\^k = \\left\[ \\sum_{i=0}\^{w-1} x_i 2\^i \\right\] \\bmod 2\^k \\\\= \\left\[ \\sum_{i=0}\^{k-1} x_i 2\^i \\right\] \\bmod 2\^k \\\\= \\sum_{i=0}\^{k-1} x_i 2\^i \\\\ = B2U_k(\[x_{k-1}, \\dots, x_0\]) B2Uw(\[xw−1,xw−2,...,x0\])mod2k=\[i=0∑w−1xi2i\]mod2k=\[i=0∑k−1xi2i\]mod2k=i=0∑k−1xi2i=B2Uk(\[xk−1,...,x0\]) 在此推导中,利用了属性:对于任何 i≥ki \\geq ki≥k,2i mod 2k=02\^i \\bmod 2\^k = 02imod2k=0。 *** ** * ** *** **截断补码数值** 补码截断也具有相似的属性,只不过要将最高位转换为符号位。令 x=B2Uw(x⃗)x = B2U_w(\\vec{x})x=B2Uw(x ),x′=B2Tk(x⃗′)x' = B2T_k(\\vec{x}')x′=B2Tk(x ′)。则 x′=U2Tk(x mod 2k)x' = U2T_k(x \\bmod 2\^k)x′=U2Tk(xmod2k)。 * 在这个公式中,x mod 2kx \\bmod 2\^kxmod2k 将是 000 到 2k−12\^k-12k−1 之间的一个数。 * 应用函数 U2TkU2T_kU2Tk 产生的效果是把最高有效位 xk−1x_{k-1}xk−1 的权重从 2k−12\^{k-1}2k−1 转变为 −2k−1-2\^{k-1}−2k−1。 示例将数值 x=53191x = 53191x=53191 从 `int` 转换为 `short`: * 由于 216=65536≥x2\^{16} = 65536 \\geq x216=65536≥x,我们有 x mod 216=x=53191x \\bmod 2\^{16} = x = 53191xmod216=x=53191。 * 当把这个数转换为 16 位的补码时,得到 x′=53191−65536=−12345x' = 53191 - 65536 = -12345x′=53191−65536=−12345。 综上有: * **无符号数的截断结果:** B2Uk(\[xk−1,xk−2,...,x0\])=B2Uw(\[xw−1,xw−2,...,x0\]) mod 2k(2.9)B2U_k(\[x_{k-1}, x_{k-2}, \\dots, x_0\]) = B2U_w(\[x_{w-1}, x_{w-2}, \\dots, x_0\]) \\bmod 2\^k \\quad (2.9)B2Uk(\[xk−1,xk−2,...,x0\])=B2Uw(\[xw−1,xw−2,...,x0\])mod2k(2.9) * **补码数字的截断结果:** B2Tk(\[xk−1,xk−2,...,x0\])=U2Tk(B2Uw(\[xw−1,xw−2,...,x0\]) mod 2k)(2.10)B2T_k(\[x_{k-1}, x_{k-2}, \\dots, x_0\]) = U2T_k(B2U_w(\[x_{w-1}, x_{w-2}, \\dots, x_0\]) \\bmod 2\^k) \\quad (2.10)B2Tk(\[xk−1,xk−2,...,x0\])=U2Tk(B2Uw(\[xw−1,xw−2,...,x0\])mod2k)(2.10) ```c #include typedef unsigned char *byte_pointer; void show_bytes(byte_pointer start, size_t len) { size_t i; for (i = 0; i < len; i++) printf(" %.2x", start[i]); printf("\n"); } int main() { // 1. 初始值:53191 // 二进制: 1100 1111 1100 0111 (十六进制: 0x0000CFC7) int x = 53191; // 2. 强制转换为 short (16位) // 发生截断:只保留低 16 位。 // 0xCFC7 在 16 位有符号数中,最高位是 1,代表负数。 // 根据补码计算:0xCFC7 = -32768 + 20423 = -12345 short sx = (short) x; // 3. 将 short 扩展回 int (32位) // 发生符号扩展:为了保持数值 -12345 不变,高位全部填充符号位 (1)。 // 结果变为: 0xFFFFCFC7 int y = sx; printf("x = %d, bytes: ", x); show_bytes((byte_pointer) &x, sizeof(int)); // x = 53191, bytes: c7 cf 00 00 printf("sx = %d, bytes: ", sx); show_bytes((byte_pointer) &sx, sizeof(short)); // sx = -12345, bytes: c7 cf printf("y = %d, bytes: ", y); show_bytes((byte_pointer) &y, sizeof(int)); // y = -12345, bytes: c7 cf ff ff return 0; } ``` #### 例题 > **题目** > > **练习题 2.25** 考虑下列代码,这段代码试图计算数组 a 中所有元素的和,其中元素的数量由参数 length 给出。 > > ```c > /* WARNING: This is buggy code */ > float sum_elements(float a[], unsigned length) { > int i; > float result = 0; > for (i = 0; i <= length-1; i++) > result += a[i]; > return result; > } > ``` > > 当参数 length 等于 0 时,运行这段代码应该返回 0.0。但实际上,运行时会遇到一个内存错误。请解释为什么会发生这样的情况,并且说明如何修改代码。 **答案** * **原因分析** :因为参数 `length` 是无符号的,计算 0−10-10−1 将使用无符号运算,这等价于模数加法。结果得到 UMaxUMaxUMax(无符号整数的最大值)。≤\\le≤ 比较同样使用无符号数比较,因为任何数都是小于或者等于 UMaxUMaxUMax 的,所以这个比较总是为真!因此,代码将试图访问数组 `a` 的非法元素。 * **解决方法** :有两种方法可以改正这段代码: 1. 将 `length` 声明为 `int` 类型。 2. 将 `for` 循环的测试条件改为 `i < length`。 > **题目** > > **练习题 2.26** 现在给你一个任务,写一个函数用来判定一个字符串是否比另一个更长。前提是你要用字符串库函数 `strlen`,它的声明如下: > > ```c > /* Prototype for library function strlen */ > size_t strlen(const char *s); > ``` > > 最开始你写的函数是这样的: > > ```c > /* Determine whether string s is longer than string t */ > /* WARNING: This function is buggy */ > int strlonger(char *s, char *t) { > return strlen(s) - strlen(t) > 0; > } > ``` > > 当你以一些示例数据上测试这个函数时,一切似乎都是正确的。进一步研究发现,在头文件 `stdio.h` 中数据类型 `size_t` 是定义成 `unsigned int` 的。 > > A. 在什么情况下,这个函数会产生不正确的结果? > > B. 解释为什么会出现这样不正确的结果。 > > C. 说明如何修改这段代码好让它能可靠地工作。 **答案** **2.26** 这个例子说明了无符号运算的一个细微的特性,同时也是我们执行无符号运算时不会意识到的属性。这会导致一些非常棘手的错误。 * **A. 错误情况** :当 `s` 比 `t` 短的时候,该函数会不正确地返回 1。 * **B. 原因解释** :由于 `strlen` 被定义为产生一个无符号的结果,差和比较都采用无符号运算来计算。当 `s` 比 `t` 短的时候,`strlen(s) - strlen(t)` 的差会为负,但是变成了一个很大的无符号数,且大于 0。 * **C. 修改方法** :将测试语句改成 `return strlen(s) > strlen(t);`。因为这样是直接对两个数值进行比较,没有产生中间运算结果。

相关推荐
simon_skywalker1 天前
CSAPP第二章 信息表示与处理(一) 信息存储
csapp
xinwulinzi7 个月前
HIT-csapp大作业:程序人生-HELLO‘s P2P
程序人生·课程设计·csapp·计算机系统·哈工大
Artintel1 年前
[学习笔记]《CSAPP》深入理解计算机系统 - Chapter 6 存储器层次结构
笔记·学习·c·csapp
永恒,怎么可能2 年前
【CSAPP】-linklab实验
csapp
永恒,怎么可能2 年前
【CSAPP】-cachelab实验
csapp
John_Snowww2 年前
CSAPP Lab01——Data Lab完成思路
vscode·csapp·wsl2·cmu15213
念谨2 年前
【目录】CSAPP的实验简介与解法总结(已包含Attack/Link/Architecture/Cache)
csapp·深入理解计算机系统·15213
Tmylyh2 年前
程序的机器级表示
汇编·操作系统·csapp
囚蕤2 年前
csapp-Machine-Level Representation of Program-review
csapp·machine-level