Chapter 2:信息的表示和处理(§2.1 信息存储)
预习完成:2026-06-03 | §2.1 共 9 小节 + 6 个 C 初学者专栏 + 14 个知识点
CSAPP
- [Chapter 2:信息的表示和处理(§2.1 信息存储)](#Chapter 2:信息的表示和处理(§2.1 信息存储))
-
- [§2.1 信息存储 --- 核心概念](#§2.1 信息存储 — 核心概念)
-
- 基本概念
- [2.1.1 十六进制表示法](#2.1.1 十六进制表示法)
- [2.1.2 字数据大小](#2.1.2 字数据大小)
- [2.1.3 寻址和字节顺序](#2.1.3 寻址和字节顺序)
- [2.1.4 表示字符串](#2.1.4 表示字符串)
- [2.1.5 表示代码](#2.1.5 表示代码)
- [2.1.6 布尔代数简介](#2.1.6 布尔代数简介)
- [2.1.7 C 语言中的位级运算](#2.1.7 C 语言中的位级运算)
- [2.1.8 C 语言中的逻辑运算](#2.1.8 C 语言中的逻辑运算)
- [2.1.9 C 语言中的移位运算](#2.1.9 C 语言中的移位运算)
- [C 初学者专栏速查(§2.1 共 8 个)](#C 初学者专栏速查(§2.1 共 8 个))
- 易错点
- 与其他章节的联系
- [Chapter 2 术语表(§2.1 信息存储)](#Chapter 2 术语表(§2.1 信息存储))
-
- [§2.1 引言](#§2.1 引言)
- [2.1.1 十六进制表示法](#2.1.1 十六进制表示法)
- [2.1.2 字数据大小](#2.1.2 字数据大小)
- [2.1.3 寻址和字节顺序](#2.1.3 寻址和字节顺序)
- [2.1.4 表示字符串](#2.1.4 表示字符串)
- [2.1.5 表示代码](#2.1.5 表示代码)
- [2.1.6 布尔代数简介](#2.1.6 布尔代数简介)
- [2.1.7 C 语言中的位级运算](#2.1.7 C 语言中的位级运算)
- [2.1.8 C 语言中的逻辑运算](#2.1.8 C 语言中的逻辑运算)
- [2.1.9 C 语言中的移位运算](#2.1.9 C 语言中的移位运算)
§2.1 信息存储 --- 核心概念
基本概念
| 概念 | 解释 |
|---|---|
| bit(位) | 最小信息单位,只有 0 或 1 |
| byte(字节) | 8 个 bit 组成的最小可寻址单位,CPU 每次至少存取 1 字节 |
| 虚拟内存(virtual memory) | 从程序视角,内存就是一个巨大的字节数组(byte array) |
| 地址(address) | 每个字节的唯一标识,从 0 开始编号 |
| 虚拟地址空间 | 所有可能地址的集合;32 位系统 = 0 ~ 2³²-1(4GB),64 位系统 = 0 ~ 2⁶⁴-1(16EB) |
| 指针 | 值 = 某个内存块的第一个字节的虚拟地址;类型 = 告诉编译器那个位置存的是什么 |
核心原则:信息 = 位 + 上下文。 同一串 0/1,不同解释 = 不同含义(整数、浮点数、指令、字符串等)。C 编译器的类型信息只存在于编译时,运行时只有字节。
2.1.1 十六进制表示法
为什么用十六进制?
| 进制 | 问题 |
|---|---|
二进制 100011110011... |
太冗长,人眼看瞎 |
| 十进制 | 和 bit 转换麻烦(10 不是 2 的幂) |
| 十六进制 | 16 = 2⁴,每 4 个 bit ⇔ 1 个 hex 数字,完美! |
常用对应关系(必须背熟):
| Hex | 二进制 | 十进制 | Hex | 二进制 | 十进制 |
|---|---|---|---|---|---|
| 0 | 0000 | 0 | 8 | 1000 | 8 |
| 1 | 0001 | 1 | 9 | 1001 | 9 |
| 2 | 0010 | 2 | A | 1010 | 10 |
| 3 | 0011 | 3 | B | 1011 | 11 |
| 4 | 0100 | 4 | C | 1100 | 12 |
| 5 | 0101 | 5 | D | 1101 | 13 |
| 6 | 0110 | 6 | E | 1110 | 14 |
| 7 | 0111 | 7 | F | 1111 | 15 |
转换规则:
| 转换方向 | 方法 |
|---|---|
| 二进制 → Hex | 从右往左,每 4 bit 一组,查表 |
| Hex → 二进制 | 每个 hex 数字展开成 4 bit |
| 十进制 → Hex | 反复除以 16,从下往上读余数 |
| Hex → 十进制 | 每位 × 16 的对应次幂,求和 |
2ⁿ 快捷转换法:
n = i + 4j(i ∈ {0, 1, 2, 3}),开头数字由 i 决定,后面跟 j 个 0。
| i | 开头 hex | 示例 |
|---|---|---|
| 0 | 1 | n=8 → 1×16² = 0x100 |
| 1 | 2 | n=9 → 2×16² = 0x200 |
| 2 | 4 | n=10 → 4×16² = 0x400 |
| 3 | 8 | n=11 → 8×16² = 0x800 |
2.1.2 字数据大小
字长(word size)= 指针的标称大小,决定虚拟地址空间的上限。
| 字长 | 地址空间 | 指针大小 | 主流年代 |
|---|---|---|---|
| 32 位 | 4 GB | 4 字节 | 1980--2010 |
| 64 位 | 16 EB | 8 字节 | 2010--至今 |
关键区分:"32 位程序 / 64 位程序"指的是如何编译的,不是运行的机器类型。
gcc -m32→ 32 位程序(两种机器都能跑),gcc -m64→ 64 位程序(只能 64 位机器跑)。
C 数据类型字节数(32 位 vs 64 位):
| 类型 | 32 位 | 64 位 | 是否随字长变化 |
|---|---|---|---|
| char | 1 | 1 | ❌ |
| short | 2 | 2 | ❌ |
| int | 4 | 4 | ❌ |
| long | 4 | 8 | ✅ |
| char*(指针) | 4 | 8 | ✅ |
| float | 4 | 4 | ❌ |
| double | 8 | 8 | ❌ |
| int32_t | 4 | 4 | ❌(固定大小) |
| int64_t | 8 | 8 | ❌(固定大小) |
移植陷阱:
| 错误假设 | 32 位 | 64 位 |
|---|---|---|
int 可以存指针 |
✅ 都是 4 字节 | ❌ int=4, 指针=8 → 截断 |
long = 指针大小 |
✅ 都是 4 | ✅ 都是 8 |
| 指针大小一定 | ✅ 4 字节 | ❌ 变 8 字节 |
最佳实践:
| 做法 | |
|---|---|
| 明确需要固定大小时 | 用 int32_t、int64_t,不用 int、long |
| 不确定 char 符号时 | 显式写 signed char 或 unsigned char |
| 需要可移植的全 1 掩码 | 用 ~0 而不是 0xFFFFFFFF |
| 需要可移植尺寸信息 | 用 sizeof(T) 而不是硬编码数字 |
2.1.3 寻址和字节顺序
多字节对象:连续存储,地址 = 所用字节中最小的那个。
小端法 vs 大端法:
以 int x = 0x01234567 存放在地址 0x100 为例:
| 0x100 | 0x101 | 0x102 | 0x103 | 规则 | |
|---|---|---|---|---|---|
| 小端(Little Endian) | 67 |
45 |
23 |
01 |
低位字节在低地址 |
| 大端(Big Endian) | 01 |
23 |
45 |
67 |
高位字节在低地址 |
实际分布:
| 端序 | 代表硬件 / 系统 |
|---|---|
| 小端 | Intel x86/x86-64,ARM(Android / iOS) |
| 大端 | IBM 大型机,旧 Sun SPARC |
| 双端(可配置) | 某些 ARM 芯片(但 OS 固定后就不可变) |
字节顺序的选择没有技术上的优劣之分------就像鸡蛋从哪头剥一样,选一个然后始终如一即可。
字节顺序在三种情况下会出问题:
| 情况 | 问题 | 解决 |
|---|---|---|
| ① 网络传输 | 小端机器发数据给大端机器(或反之),接收方字节反序 | 统一网络字节序(第 11 章) |
| ② 阅读反汇编 | 小端机器上 hex dump 显示的数字是反的,需要倒过来读 | 读 hex dump 时注意目标机器的端序 |
| ③ 用强制类型转换规避类型系统 | (byte_pointer) &x 看到原始字节,端序会影响所见 |
用 show_bytes 时注意端序 |
show_bytes --- 核心工具函数:
c
typedef unsigned char *byte_pointer; // "一次只读1字节"的指针
void show_bytes(byte_pointer start, size_t len) {
for (size_t i = 0; i < len; i++)
printf(" %.2x", start[i]); // 逐字节以 hex 打印
}
void show_int(int x) {
show_bytes((byte_pointer) &x, sizeof(int)); // 强制类型转换!
}
核心技巧:
(byte_pointer) &x--- 骗编译器,把 int 地址强行当字节序列看待。
同一数值 12345 在不同机器上的字节表示(图 2-6):
| int (12345) | float (12345.0) | 结论 | |
|---|---|---|---|
| 小端机器 | 39 30 00 00 |
00 e4 40 46 |
int 和 float 编码完全不同 |
| 大端机器 | 00 00 30 39 |
46 40 e4 00 |
同类型下只是字节反序 |
| 跨平台 | 数值相同,端序可能不同 | 数值相同,端序可能不同 | 指针值完全因机器/OS 而异 |
2.1.4 表示字符串
C 字符串 = 以 null 字符(0x00)结尾的字符数组。
"12345" → 字节:31 32 33 34 35 00
'1' '2' '3' '4' '5' '\0'
数字字符 'x' 的 ASCII 码 =
0x3x('3'=0x33,'9'=0x39)。
字符串 vs 二进制数据的跨平台性:
| 数据类型 | 跨平台性 | 原因 |
|---|---|---|
| 字符串(ASCII) | ✅ 完全一致 | 每个字符 1 字节,无端序问题 |
| 二进制数值(int / float) | ⚠️ 可能反序 | 多字节,端序影响 |
| 机器代码 | ❌ 完全不同 | 不同 CPU 指令集不兼容 |
核心结论:文本数据比二进制数据具有更强的平台独立性。
2.1.5 表示代码
同一函数编译后,不同平台上生成的机器代码完全不同。
c
int sum(int x, int y) { return x + y; }
| 平台 | 机器代码字节 |
|---|---|
| Linux 32 | 55 89 e5 8b 45 0c 03 45 08 c9 c3 |
| Windows | 55 89 e5 8b 45 0c 03 45 08 5d c3 |
| Sun | 81 c3 e0 08 90 02 00 09 |
| Linux 64 | 55 48 89 e5 89 7d fc 89 75 f8 03 45 fc c9 c3 |
从机器的角度看,程序仅仅只是字节序列。机器没有关于原始源程序的任何信息。
2.1.6 布尔代数简介
四种基本运算:
| 运算 | C 符号 | 规则 | 口诀 |
|---|---|---|---|
| NOT(非) | ~ |
1→0, 0→1 | 反转 |
| AND(与) | & |
两个都 1 才 1 | 全真才真 |
| OR(或) | ` | ` | 至少一个 1 就是 1 |
| XOR(异或) | ^ |
两个不一样才是 1 | 不同为真 |
位向量运算:每一位独立算,互不影响。
a = [0 1 1 0]
b = [1 1 0 0]
a&b = [0 1 0 0]
a|b = [1 1 1 0]
a^b = [1 0 1 0]
~b = [0 0 1 1]
2.1.7 C 语言中的位级运算
| 运算符 | 含义 | 使用技巧 |
|---|---|---|
& |
按位与 | 掩码:用来提取 / 清零指定位 |
| ` | ` | 按位或 |
~ |
按位取反 | 翻转所有位 |
^ |
按位异或 | 翻转指定位(和 1 异或 = 翻转)、判断两数是否相等 |
求值步骤:hex → 二进制 → 逐位算 → 回 hex。
掩码(Mask)------位级运算最常用的模式:
| 操作 | 表达式 | 效果 |
|---|---|---|
| 提取最低字节 | x & 0xFF |
只保留低 8 位,其他清零 |
| 置位某些位 | `x | 0x0F` |
| 翻转某些位 | x ^ 0x0F |
低 4 位翻转,其他不变 |
| 生成全 1 掩码 | ~0 |
不管字长多少,全 1(可移植) |
2.1.8 C 语言中的逻辑运算
位级运算 vs 逻辑运算(极易混淆!):
| 位级运算(bitwise) | 逻辑运算(logical) | |
|---|---|---|
| 符号 | & ` |
~` `^` |
| 操作对象 | 每个 bit 独立算 | 整个值视为"真"或"假" |
| 规则 | 0=0, 1=1 | 0=假(FALSE),非 0=真(TRUE) |
| 返回结果 | 任意数值 | 只返回 0 或 1 |
| 短路求值 | ❌ 两边都算 | ✅ 左边能确定结果则跳过右边 |
短路求值示例:
| 表达式 | 左值确定结果时 | 安全性 |
|---|---|---|
a && 5/a |
a=0 → 假 → 不执行 5/a |
✅ 安全,不会除零 |
a & 5/a |
无论 a 是什么都算 5/a |
❌ 危险,a=0 时崩 |
p && *p++ |
p=NULL → 假 → 不执行 *p++ |
✅ 安全,不会空指针解引用 |
!!x= 将任意值归一化为 0 或 1(非零→1,零→0)。
对比测试:x=0x66, y=0x39
| 表达式 | 结果 | 原因 |
|---|---|---|
x & y |
0x20 |
逐位 AND:0x66 & 0x39 = 0x20 |
x && y |
0x01 |
两个都非零 → 真 → 1 |
| `x | y` | 0x7F |
| `x | y` |
2.1.9 C 语言中的移位运算
| 移位 | 方向 | 补位 | 等价效果 |
|---|---|---|---|
x << k |
左移 | 右边补 k 个 0 | x × 2 k x \times 2^k x×2k |
x >> k(逻辑右移) |
右移 | 左边补 0 | 无符号数专用 |
x >> k(算术右移) |
右移 | 左边补符号位(最高位是啥补啥) | 有符号数保持正负号 |
示例:x = [10010101]
逻辑右移 4 位: [0000 1001] ← 左边补 0
算术右移 4 位: [1111 1001] ← 左边补符号位(MSB=1 所以补 1)
C 语言的坑:
| 问题 | 说明 |
|---|---|
| 有符号数右移类型未定义 | C 标准不规定用逻辑还是算术右移(实际几乎所有编译器用算术右移) |
| 无符号数右移 | 明确规定:必须是逻辑右移 |
| 移位量 ≥ 字长 | 未定义行为!实际多数机器算 k mod w,但不要依赖 |
| 移位优先级低于加减 | 1 << 2 + 3 << 4 = (1 << (2+3)) << 4 ≠ (1<<2) + (3<<4) |
Java 对比: Java 明确定义 >> = 算术右移,>>> = 逻辑右移,不存在歧义。
C 初学者专栏速查(§2.1 共 8 个)
| # | 专栏标题 | 核心内容 |
|---|---|---|
| 1 | C 语言中指针的作用 | 指针 = 值(地址)+ 类型(指向什么);第 3 章深入机器级实现 |
| 2 | 声明指针 | T *p; 声明一个指向 T 类型的指针 |
| 3 | 使用 typedef 命名数据类型 | typedef 原名 新名; 给复杂类型起小名 |
| 4 | 使用 printf 格式化输出 | %d(整数), %f(浮点), %x(hex), %c(字符), %.2x(2位hex) |
| 5 | 指针和数组 | start[i] 可以用在指针上,和遍历数组一模一样 |
| 6 | 指针的创建和间接引用 | &x 取地址创建指针,*p 解引用取数据;强制类型转换不改变真实指针 |
| 7 | C 语言版本和编译选项 | -std=c89, -std=c99, -std=c11;书默认基于 C90 |
| 8 | 移位运算的优先级陷阱 | 加减法优先级 > 移位 → 拿不准就加括号! |
易错点
- 类型信息只存在于编译时,运行时只有字节。 写类型转换时记住:机器不关心类型,只看见 bit。(§2.1 引言)
- "32 位程序"说的是编译方式,不是机器类型。 64 位机器能跑 32 位程序(向后兼容),反之不行。(§2.1.2)
int在 64 位系统上还是 4 字节。 只有long和指针从 4 变 8。很多人错误以为int会变 8。(§2.1.2)char的符号不明确。 C 标准不保证char是有符号还是无符号------显式写signed char或unsigned char。(§2.1.2)- 小端机器上读 hex dump,多字节数值是反的。 hex dump 显示顺序 = 内存地址从小到大,低位字节在低地址(小端)= 先显示。(§2.1.3)
- 位级运算 ≠ 逻辑运算。
&vs&&:符号完全不同、返回结果完全不同、是否短路完全不同。(§2.1.8) - 有符号数右移的结果不可移植。 C 标准不规定用哪种右移------如果真需要可移植,用无符号数。(§2.1.9)
- 移位量超出字长 = 未定义行为。 不要写
x << 40(32 位 int),即使"实际会 mod 32"也别依赖。(§2.1.9) - 加减法优先级比移位高。
1<<2+3<<4≠(1<<2)+(3<<4),拿不准就加括号。(§2.1.9)
与其他章节的联系
| 本章概念 | 展开章节 |
|---|---|
| 虚拟内存 ≠ 物理内存 | 第 9 章 虚拟内存 |
| 指针在机器级的表示和实现 | 第 3 章(3.10.1 节) |
| 网络字节序(大端) | 第 11 章 网络编程 |
| 反汇编器与机器代码阅读 | 第 3 章 程序机器级表示 |
| 指针和数组的紧密联系 | 第 3 章(3.8 节) |
| 布尔代数在数字系统中的应用 | 第 4 章 处理器体系结构 |
| int 和 float 编码完全不同 --- 补码 vs IEEE 754 | §2.2--§2.4 |
| 算术右移的作用(保持符号位) | §2.2 整数表示(补码运算) |
Chapter 2 术语表(§2.1 信息存储)
四色标注:⚫ 黑色 = 事实 / 定义 | 🔵 蓝色 = 核心概念 | 🔴 红色 = 易错点 / 陷阱 | 🟢 绿色 = 后续章节关联
§2.1 引言
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 位(比特) | bit | 最小信息单位,取值 0 或 1 | ⚫ 事实 |
| 字节 | byte | 8 个 bit 组成的最小可寻址单位,CPU 每次至少存取 1 字节 | ⚫ 事实 |
| 虚拟内存 | virtual memory | 从机器级程序视角看,内存就是一个巨大的字节数组 | 🔵 核心 |
| 地址 | address | 每个字节的唯一数字标识 | ⚫ 事实 |
| 虚拟地址空间 | virtual address space | 所有可能地址的集合;实际由 DRAM + 磁盘 + OS 联合实现 | 🔵 核心 |
| 程序对象 | program object | 程序数据、指令和控制信息,在内存中就是字节块 | ⚫ 事实 |
2.1.1 十六进制表示法
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 十六进制 | hexadecimal (hex) | 以 16 为基数的计数法,用 0-9 和 A-F 表示 16 个值,每 4 个 bit 对应 1 个 hex 数字 |
⚫ 事实 |
| 0x / 0X 前缀 | 0x prefix | C 语言中十六进制常量的前缀,如 0x7AF |
⚫ 事实 |
2.1.2 字数据大小
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 字长 | word size | 指针数据的标称大小;决定虚拟地址空间上限(32 位 = 4GB,64 位 = 16EB) | 🔵 核心 |
| int32_t / int64_t | fixed-size types | ISO C99 引入的固定大小整数类型,不随编译器和机器变化,是精确控制数据大小的最佳途径 | 🔵 核心 |
| sizeof(T) | sizeof | 返回存储类型 T 所需字节数,用来写可移植代码 | ⚫ 事实 |
| 可移植性 | portability | 使程序对不同数据类型的确切大小不敏感 | 🔵 核心 |
2.1.3 寻址和字节顺序
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 小端法 | little endian | 最低有效字节存放在最低地址;Intel x86/x86-64、ARM(Android/iOS) 使用 | 🔵 核心 |
| 大端法 | big endian | 最高有效字节存放在最低地址;IBM 大型机、旧 Sun SPARC 使用 | 🔵 核心 |
| 双端法 | bi-endian | 硬件两种模式都支持,但 OS 选定后固定;某些 ARM 芯片有此特性 | ⚫ 事实 |
| 最低有效字节 | least significant byte | 权重最小的字节,对数值影响最小 | ⚫ 事实 |
| 最高有效字节 | most significant byte | 权重最大的字节,对数值影响最大 | ⚫ 事实 |
| 强制类型转换 | cast | (byte_pointer) &x --- 把指向任意类型的指针强转为字节指针,从而查看原始字节 |
🔵 核心 |
| 网络字节序 | network byte order | 网络传输时的统一字节顺序标准,发送方和接收方各自转换 | 🟢 绿(第 11 章) |
| 反汇编器 | disassembler | 将机器代码字节序列还原为汇编指令的工具 | 🟢 绿(第 3 章) |
| byte_pointer | byte_pointer | typedef unsigned char *byte_pointer; --- 指向单个字节的指针类型 |
⚫ 事实 |
2.1.4 表示字符串
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| ASCII | American Standard Code for Information Interchange | 最常见字符编码标准,每个字符 1 字节;数字 'x' 的 ASCII 码 = 0x3x |
⚫ 事实 |
| null 终止符 | null terminator | C 字符串末尾的值为 0 的字节(\0),标志字符串结束 |
⚫ 事实 |
| Unicode | Unicode | 涵盖近 10 万字符的编码标准,支持多语言 | ⚫ 事实 |
| UTF-8 | UTF-8 | Unicode 的变长编码,ASCII 字符保持单字节兼容 | ⚫ 事实 |
2.1.5 表示代码
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 机器代码 | machine code | 程序编译后的二进制指令字节序列,不同平台(CPU/OS)完全不兼容 | 🔵 核心 |
| 指令集 | instruction set | 处理器支持的指令及其编码方式的集合,不同 CPU 完全不同 | 🟢 绿(第 3、4 章) |
2.1.6 布尔代数简介
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 布尔代数 | Boolean algebra | 研究 0 和 1 运算的数学体系,由 George Boole 于 1850 年代创立 | ⚫ 事实 |
| 位向量 | bit vector | 固定长度 w 的 0/1 序列;布尔运算逐位进行 | ⚫ 事实 |
| NOT(非) | NOT | ~ --- 每位取反:1→0,0→1 |
⚫ 事实 |
| AND(与) | AND | & --- 两位都为 1 结果才为 1 |
⚫ 事实 |
| OR(或) | OR | ` | ` --- 至少一位为 1 结果即为 1 |
| XOR(异或) | XOR | ^ --- 两位不同结果才为 1;关键性质:a ^ a = 0 |
⚫ 事实 |
2.1.7 C 语言中的位级运算
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 位级运算(按位运算) | bit-level operation | 对整型数据的每个 bit 独立进行布尔运算 | ⚫ 事实 |
| 掩码 | mask | 位模式,用来选取或修改一个字中的指定位;x & 0xFF 提取低字节 |
🔵 核心 |
~0 |
~0 | 生成全 1 掩码的可移植写法,自动适配字长;优于硬编码 0xFFFFFFFF |
🔵 核心 |
2.1.8 C 语言中的逻辑运算
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 逻辑运算 | logical operation | &&、` |
|
| 短路求值 | short-circuit evaluation | && / ` |
2.1.9 C 语言中的移位运算
| 术语 | 英文 | 解释 | 标注 |
|---|---|---|---|
| 左移 | left shift (<<) |
向左移 k 位,丢弃高位,右边补 k 个 0;等价于 x × 2 k x \times 2^k x×2k | ⚫ 事实 |
| 逻辑右移 | logical right shift | 向右移 k 位,左边补 0;无符号数专用 | ⚫ 事实 |
| 算术右移 | arithmetic right shift | 向右移 k 位,左边补符号位(MSB 是啥补啥);有符号数常用,保持正负号 | 🔵 核心 |
| 移位量未定义行为 | undefined shift amount | 移位量 ≥ 字长时,C 标准不规定行为;实际多数机器取 k mod w | 🔴 红色 |
| 移位优先级陷阱 | shift precedence pitfall | 加减法优先级 > 移位运算符,1<<2+3<<4 ≠ (1<<2)+(3<<4) |
🔴 红色 |