CSAPP第二章 信息表示与处理(一) 信息存储

第二章

2.1 信息存储

C数据类型大小

基本 C 数据类型的大小(以字节为单位)

C 声明 32 位 64 位
char 1 1
short 2 2
int 4 4
long 4 8
int32_t 4 4
int64_t 8 8
char * 4 8
float 4 4
double 8 8

大端法和小端法

特性 大端法 小端法
存储逻辑 高位字节 → 低地址 低位字节 → 低地址
人类可读性 高(内存布局与书写顺序一致) 低(需逆序读取)
  • 变量 x 类型为 int(4 字节),地址从 0x100 开始。
  • 十六进制值:0x01234567(高位字节 0x01,低位字节 0x67)。
地址 0x100 0x101 0x102 0x103
大端法 01 23 45 67
小端法 67 45 23 01

练习题 2.5 思考下面对 show_bytes 的三次调用:

c 复制代码
int val = 0x87654321;
byte_pointer valp = (byte_pointer) &val;
show_bytes(valp, 1); /* A. */
show_bytes(valp, 2); /* B. */
show_bytes(valp, 3); /* C. */

指出在小端法机器和大端法机器上,每次调用的输出值。

解答

  • A.
    • 小端法:21
    • 大端法:87
  • B.
    • 小端法:21 43
    • 大端法:87 65
  • C.
    • 小端法:21 43 65
    • 大端法:87 65 43

说明

  • 变量 val 的十六进制值为 0x87654321(4 字节)。
  • 小端法存储顺序(低地址 → 高地址):21, 43, 65, 87
  • 大端法存储顺序(低地址 → 高地址):87, 65, 43, 21
  • show_bytes 从最低地址开始输出指定字节数。

C语言中的位级运算

C语言支持按位布尔运算,可应用于任何整型数据类型(如charintlong等)。主要运算符包括:

  • |:按位或(OR)- 任一操作数对应位为1时结果为1
  • &:按位与(AND)- 两操作数对应位都为1时结果为1
  • ~:按位取反(NOT)- 0变1,1变0
  • ^:按位异或(XOR)- 两操作数对应位不同时结果为1

运算方法:将十六进制参数扩展为二进制表示 → 执行二进制运算 → 转换回十六进制

c 复制代码
#include <stdio.h>

int main() {
    // 示例1: ~0x41
    // 二进制: ~[100 0001] = [0111 1110]
    // 十六进制: 0xBE
    printf("示例1: ~0x41 = 0x%02X\n", ~0x41);
    // 预计输出: 示例1: ~0x41 = 0xBE

    // 示例2: ~0x00
    // 二进制: ~[0000 0000] = [1111 1111]
    // 十六进制: 0xFF
    printf("示例2: ~0x00 = 0x%02X\n", ~0x00);
    // 预计输出: 示例2: ~0x00 = 0xFF

    // 示例3: 0x69 & 0x55
    // 二进制: [0110 1001] & [0101 0101] = [0100 0001]
    // 十六进制: 0x41
    printf("示例3: 0x69 & 0x55 = 0x%02X\n", 0x69 & 0x55);
    // 预计输出: 示例3: 0x69 & 0x55 = 0x41

    // 示例4: 0x69 | 0x55
    // 二进制: [0110 1001] | [0101 0101] = [0110 1101]
    // 十六进制: 0x6D
    printf("示例4: 0x69 | 0x55 = 0x%02X\n", 0x69 | 0x55);
    // 预计输出: 示例4: 0x69 | 0x55 = 0x6D

    return 0;
}

练习题 2.10 对于任一位置向量 a,有 a ^ a = 0。应用这一特性,考虑下面的程序:

c 复制代码
void inplace_swap(int *x, int *y) {
*y = *x ^ *y;   // Step 1
*x = *x ^ *y;   // Step 2
*y = *x ^ *y;   // Step 3
}

填写下表,给出程序每一步后存储在两个位置中的值:

解答

步骤 *x *y
初始 a b
第1步 a a ^ b
第2步 b a ^ b
第3步 b a

推导过程

  • Step 1*y = *x ^ *y → *y 变为 a ^ b
  • Step 2*x = *x ^ *y → a ^ (a ^ b) = (a ^ a) ^ b = 0 ^ b = b
  • Step 3*y = *x ^ *y → b ^ (a ^ b) = (b ^ b) ^ a = 0 ^ a = a

练习题 2.11 在 2.10 的 inplace_swap 函数基础上,实现数组反转:

c 复制代码
void reverse_array(int a[], int cnt) {
int first, last;
for (first = 0, last = cnt-1;
   first <= last;
   first++, last--)
  inplace_swap(&a[first], &a[last]);
}

A. 对于长度为奇数的数组(cnt = 2k+1),最后一次循环中 firstlast 的值是什么?
B. 为什么此时调用 inplace_swap 会将中间元素设为 0?
C. 如何修改代码解决此问题?

解答
A.

  • 对于 cnt = 2k+1,最后一次循环时:
    first = k, last = cnt-1-k = (2k+1)-1-k = k
    first = last = k

B.

  • first = last 时,inplace_swap 传入同一地址(设值为 a):
    • Step 1: *y = *x ^ *y → a ^ a = 0
    • Step 2: *x = *x ^ *y → 0 ^ 0 = 0
    • Step 3: *y = *x ^ *y → 0 ^ 0 = 0
      中间元素被置为 0

C.

  • 修改循环条件:将 first <= last 改为 first < last

  • 修正后代码:

    c 复制代码
    for (first = 0, last = cnt-1; first < last; first++, last--)
        inplace_swap(&a[first], &a[last]);

    跳过中间元素,避免对同一地址调用 inplace_swap


练习题 2.12 对于下面的值,写出变量 x 的 C 语言表达式。代码应对任何字长 w≥8w \geq 8w≥8 都能工作。

解答

  • A. x 的最低有效字节,其他位均置为 0

    c 复制代码
    x & 0xFF

    说明0xFF 为掩码(二进制 ...0000000011111111),AND 操作保留最低 8 位,其他位清零。

  • B. 除最低有效字节外,其他位取补,最低有效字节保持不变

    c 复制代码
    x ^ ~0xFF

    说明~0xFF 为掩码(二进制 ...1111111100000000),XOR 操作使除最低 8 位外的所有位取反。

  • C. x 的最低有效字节置成全 1,其他字节保持不变

    c 复制代码
    x | 0xFF

    说明0xFF 为掩码(二进制 ...0000000011111111),OR 操作将最低 8 位设为 1,其他位不变。


练习题 2.13 VAX 计算机的 bis(位设置)和 bic(位清除)指令:

  • bis(x, m):将 xm 为 1 的位置设为 1
  • bic(x, m):将 xm 为 1 的位置设为 0

解答

  • 实现布尔 OR 运算

    c 复制代码
    int bool_or(int x, int y) {
        int result = bis(x, y);
        return result;
    }

    原理bis(x, y) 会将 y 中为 1 的位置在 x 中也设为 1,等价于 x | y

  • 实现布尔 XOR 运算

    c 复制代码
    int bool_xor(int x, int y) {
        int result = bis(bic(x, y), bic(y, x));
        return result;
    }

    原理

    1. bic(x, y):将 xy 为 1 的位置清零
    2. bic(y, x):将 yx 为 1 的位置清零
    3. bis(bic(x, y), bic(y, x)):合并两个结果中为 1 的位,等价于 x ^ y

C语言中的逻辑运算

逻辑运算将所有非零参数视为 TRUE,参数 0 视为 FALSE,返回 1(TRUE)或 0(FALSE)。

  • ||:逻辑或(OR)- 任一操作数非零时结果为 1
  • &&:逻辑与(AND)- 两操作数均非零时结果为 1
  • !:逻辑非(NOT)- 操作数为 0 时结果为 1,否则为 0
c 复制代码
#include <stdio.h>

int main() {
    // 示例1: !0x41
    // 逻辑非: 非零值(0x41)被视为TRUE, 逻辑非后为FALSE(0)
    printf("示例1: !0x41 = 0x%02X\n", !0x41);
    // 预计输出: 示例1: !0x41 = 0x00

    // 示例2: !0x00
    // 逻辑非: 零值(0x00)被视为FALSE, 逻辑非后为TRUE(1)
    printf("示例2: !0x00 = 0x%02X\n", !0x00);
    // 预计输出: 示例2: !0x00 = 0x01

    // 示例3: 0x69 && 0x55
    // 逻辑与: 两个非零值(0x69和0x55)均被视为TRUE, 结果为TRUE(1)
    printf("示例3: 0x69 && 0x55 = 0x%02X\n", 0x69 && 0x55);
    // 预计输出: 示例3: 0x69 && 0x55 = 0x01

    // 示例4: 0x00 || 0x55
    // 逻辑或: 0x00为FALSE, 但0x55为TRUE, 结果为TRUE(1)
    printf("示例4: 0x00 || 0x55 = 0x%02X\n", 0x00 || 0x55);
    // 预计输出: 示例4: 0x00 || 0x55 = 0x01

    // 附加示例: 展示短路求值特性 (无输出变化,但演示原理)
    int a = 0;
    int b = 5;
    // 由于a为0,b/a不会被执行,避免除零错误
    int result = a && (b / a); // 安全:不会触发除零错误
    printf("附加示例: 短路求值演示 (a && b/a) = %d\n", result);
    // 预计输出: 附加示例: 短路求值演示 (a && b/a) = 0

    return 0;
}

练习题 2.14 假设 x 和 y 的字节值分别为 0x66 和 0x39。填写下表,指明各个 C 表达式的字节值:(本题体现了 C语言中位运算与逻辑运算的区别)

解答

表达式 表达式
x & y 0x20 x && y 0x01
x | y 0x7F x || y 0x01
!x | !y 0xDF !x || !y 0x00
x & !y 0x00 x && !y 0x01

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

解答

c 复制代码
!(x ^ y)
  • x == y 时,x ^ y = 0,则 !(x ^ y) = 1
  • x != y 时,x ^ y != 0,则 !(x ^ y) = 0

C语言中的移位运算

左移运算符<<

  • 语法:x << k
  • 效果:将操作数 x 的所有位向左移动 k
  • 规则:丢弃最高的 k 位,在右端补 k0
  • 位表示:[xw−k−1,...,x0,0,...,0][x_{w-k-1}, ..., x_0, 0, ..., 0][xw−k−1,...,x0,0,...,0](共 w 位)

右移运算符>>

  • 语法:x >> k
  • 效果:将操作数 x 的所有位向右移动 k
  • 两种右移方式
    • 逻辑右移 :在左端补 k0
      • 位表示:[0,...,0,xw−1,...,xk][0, ..., 0, x_{w-1}, ..., x_k][0,...,0,xw−1,...,xk]
    • 算术右移 :在左端补 k 个最高有效位(符号位)
      • 位表示:[xw−1,...,xw−1,xw−2,...,xk][x_{w-1}, ..., x_{w-1}, x_{w-2}, ..., x_k][xw−1,...,xw−1,xw−2,...,xk]

C语言标准未明确定义有符号数应该使用哪种右移方式。

  • 有符号数通常使用算数右移
  • 无符号数应使用逻辑右移

!NOTE

大移位量的处理

当移位量 k 大于或等于数据类型的位宽 w 时,C 语言标准没有明确定义结果。在实际实现中,大多数处理器会将移位量对位宽取模。以 32 位 int 为例:

  • 0xFEDCBA98 << 32 实际移位 0 位(32 mod 32 = 0),结果仍为 0xFEDCBA98
  • 0xFEDCBA98 >> 36 实际移位 4 位(36 mod 32 = 4),结果为 0xFFEDCBA9
  • 0xFEDCBA98 >> 40 实际移位 8 位(40 mod 32 = 8),结果为 0x00FEDCBA
移位运算符的优先级陷阱

C语言中运算符优先级从高到低的部分顺序是:

  1. 括号 ()
  2. 一元运算符(如 !, ~
  3. 乘除 *, /, %
  4. 加减 +, -
  5. 移位 <<, >>
  6. 比较运算符 <, <=, >, >=

加法运算符 + 的优先级高于移位运算符 <<

例如,表达式 1<<2+3<<4 会被解释为 1<<(2+3)<<4 而非 (1<<2)+(3<<4)

相关推荐
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
玛了个玛卡巴卡2 年前
csapp archlab PartC满分解答
csapp