第二章

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的三次调用:
cint 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语言支持按位布尔运算,可应用于任何整型数据类型(如char、int、long等)。主要运算符包括:
|:按位或(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。应用这一特性,考虑下面的程序:
cvoid 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函数基础上,实现数组反转:
cvoid 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),最后一次循环中
first和last的值是什么?
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
- Step 1:
C.
-
修改循环条件:将
first <= last改为first < last -
修正后代码:
cfor (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
cx & 0xFF说明 :
0xFF为掩码(二进制...0000000011111111),AND 操作保留最低 8 位,其他位清零。 -
B. 除最低有效字节外,其他位取补,最低有效字节保持不变
cx ^ ~0xFF说明 :
~0xFF为掩码(二进制...1111111100000000),XOR 操作使除最低 8 位外的所有位取反。 -
C. x 的最低有效字节置成全 1,其他字节保持不变
cx | 0xFF说明 :
0xFF为掩码(二进制...0000000011111111),OR 操作将最低 8 位设为 1,其他位不变。
练习题 2.13 VAX 计算机的
bis(位设置)和bic(位清除)指令:
bis(x, m):将x中m为 1 的位置设为 1bic(x, m):将x中m为 1 的位置设为 0
解答:
-
实现布尔 OR 运算:
cint bool_or(int x, int y) { int result = bis(x, y); return result; }原理 :
bis(x, y)会将y中为 1 的位置在x中也设为 1,等价于x | y。 -
实现布尔 XOR 运算:
cint bool_xor(int x, int y) { int result = bis(bic(x, y), bic(y, x)); return result; }原理:
bic(x, y):将x中y为 1 的位置清零bic(y, x):将y中x为 1 的位置清零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位,在右端补k个0 - 位表示:[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位 - 两种右移方式 :
- 逻辑右移 :在左端补
k个0- 位表示:[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),结果仍为 0xFEDCBA980xFEDCBA98 >> 36实际移位 4 位(36 mod 32 = 4),结果为 0xFFEDCBA90xFEDCBA98 >> 40实际移位 8 位(40 mod 32 = 8),结果为 0x00FEDCBA移位运算符的优先级陷阱
C语言中运算符优先级从高到低的部分顺序是:
- 括号
()- 一元运算符(如
!,~)- 乘除
*,/,%- 加减
+,-- 移位
<<,>>- 比较运算符
<,<=,>,>=加法运算符
+的优先级高于移位运算符<<例如,表达式
1<<2+3<<4会被解释为1<<(2+3)<<4而非(1<<2)+(3<<4)。