2.57
编写程序
show_short
、show_long
、show_double
,它们分别打印类型为short
、long
和double
的字节表示。
char
是字符型,但也是属于整形的,因为 char
最终存储的是 ASCLL 码值到内存的。一个 char
有 8 个位,所以能存储的范围为 $-128\sim 127$ ,而 unsigned char
存储的范围为 $0\sim 255$ 。因为它占用一个字节,所以 unsigned char
可以精确地表示一个字节的所有可能值,所以它通常用于存储和处理字节数据。
1 |
|
2.58
编写过程
is_little_endian
,当在小端法机器上编译和运行时返回1,在大端法机器上编译运行时则返回0。这个程序应该可以运行在任何机器上,无论机器的字长是多少。
计算机存储数据在地址上都是由低到高,大小端的区别为小端法存储数据由低位到高位,而大端法则是由高位到低位,例如数据 0x12345678
小端法存储就是 78 56 34 12
,而大端法存储则是 12 34 56 78
。
可以使用 union
来实现,在 union
中存放两个数据,一个 int
一个 char
,char
占的地址为 int
数据的第一个字节,修改 int
值为1,这样就只需要判断 char 数据是否为 1 即可。
1 | int is_little_endian() { |
2. 59
编写一个 C 表达式,它生成一个字,由 $x$ 的最低有效字节和 $y$ 中剩下的字节组成。对于运算数
x = 0x89ABCDEF
和y = 0x76543210
得到0x765432EF
。
- 位(bit)是计算机内部数据存储的最小单位,只能取0或者取1;
- 字节(byte)是计算机数据处理的最小单位,每个字节有8个二进制位,其中最右边的一位为最低位,最左边的一位为最高位;
- 字是计算机进行数据处理和运算的单位,即cpu一次处理二进制代码的位数,字的位数叫做字长,字长的大小与计算架构有关,通常说的32位机就是一个字有4个字节,32个位。
x & 0xFF
, 得到 x 最低有效位,y & ~0xFF
得到 y 最低有效位之外的位,两者进行或运算,得到组合结果1
2
3
4
5
6
7
8
9
10
11
12
13typedef unsigned char *byte_pointer;
...
size_t x = 0x89ABCDEF;
size_t y = 0x76543210;
size_t mask = 0xff;
size_t res = ((x & mask) | (y & ~mask));
byte_pointer start = (byte_pointer)& res;
for(int i = 0; i < sizeof(size_t); i ++) {
printf("%.2x", start[i]);
}
/*
输出: ef32547600000000 (小端法机器)
*/
2.60
假设我们将一个 $w$ 位的字中的字节从 0(最低位)到 $\frac{w}{8} - 1$(最高位)编号。写出下面C函数代码,它会返回一个无符号值,其中参数 $x$ 的字节 $i$ 被替换成字节 $b$ :
unsigned replace_byte (unsigned x, inti, unsigned char b);
以下示例说明其如何工作:replace_byte(Ox12345678, 2, OxAB) --> Ox12AB5678
replace_byte(Ox12345678, 0, OxAB) --> Ox123456AB
按要求模拟就好了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef unsigned char *byte_pointer;
/*
* @brief 参数 x 的字节 i 被替换成字节 b
*/
unsigned replace_byte (unsigned x, int i, unsigned char b) {
byte_pointer start = (byte_pointer)& x;
start[i] = b;
return *(unsigned*)start;
}
void show_byte(byte_pointer start, size_t sz) {
for(int i = 0; i < sz; i ++) {
printf("%.2x", start[i]);
}
printf("\n");
}
int main() {
unsigned res1 = replace_byte(0x12345678, 2, 0xAB);
unsigned res2 = replace_byte(0x12345678, 0, 0xAB);
show_byte((byte_pointer)&res1, sizeof(unsigned));
show_byte((byte_pointer)&res2, sizeof(unsigned));
return 0;
}
/*
output:
7856ab12
ab563412
*/
这里提一下最后为什么返回 *(unsigned*)start
,这里先要将 unsigned char *
类型的 start
转变为 unsigned*
,即先转换指针类型,再去指针指向地址的值,而如果使用 (unsigned)(*start)
的话,*start
由于是 unsigned char
类型的指针,所以只会取出第一个地址的值,而不是所有的内存单元。
在接下来的作业中,我们特意限制了你能使用的编程结构,来帮你更好地理解 C 语言的位级、逻辑
和算术运算。在回答这些问题时,你的代码必须遵守以下规则:
- 假设:
- 整数用补码形式表示;
- 有符号数的右移是算术右移;
- 数据类型
int
是 $w$ 位长的。对于某些题目,会给定 $w$ 的值,但是在其他情况下,只要 $2$ 是 8 的整数倍,你的代码就应该能工作。你可以用表达式sizeof(int)<<3
来计算 $w$。
- 禁止使用
- 条件语句、循环、分支语句、函数调用和宏调用;
- 除法、模运算和乘法;
- 相对比较运算($>$ 、$<$ 、$\le$ 和 $\ge$)。
- 允许的运算
- 所有的位级和逻辑运算;
- 左移和右移,但是位移量只能在 $0$ 到 $w-1$ 之间;
- 加法和减法;
- 相等(==)和不等(!=)测试。(有些题目里也不允许这些运算);
- 整型常数
INT_MAX
和INT_MIN
; - 对
int
和unsigned
进行强制类型转换,无论是显式的还是隐式的。
2.61
写一个C表达式,在下列描述的条件下产生1, 而在其他情况下得到 0。假设 $x$ 是
int
类型。
A. $x$ 的任何位都等于 1
B. $x$ 的任何位都等于 0
C. $x$ 的最低有效字节中的位都等于1
D. $x$ 的最高有效字节中的位都等于0
1 |
|
解释:
~
是对每一位取反!
是对值取反
而 C 部分,首先是和 ~0xFF
(亦即 0xFFFFFF00
) 做或运算,前 3 个字节都为 1,最后一个字节为 $x$ 本来的最低字节,要判断是否为全 1 只需要全部取反再判断是否为0即可
D 部分,(x >> ((sizeof(int) - 1) << 3))
会得到最高位字节右移后的结果,例如0011 1011 1001 1010 1100 1010 0000 0000
会变为 0000 0000 0000 0000 0000 0000 0011 1011
,最后判断即可。
2. 62
编写一个函数
int_shifts_are_arithmetic()
在对int
类型的数使用算术右移的机器上运行时这个函数生成 1, 而其他情况下生成 0 。你的代码应该可以运行在任何字长的机器上。在几种机器上测试你的代码。
1 | int int_shifts_are_arithmetic() { |
后面的有时间慢慢做…