2025-12-01T14:02:09.png

第2章 信息的表示和处理

2.1 信息存储

1.十六进制表示法

当值 x 是 2 的非负整数 n 次幂时,也就是 x=2ⁿ,我们可以很容易地将 x 写成十六进制形式,只要记住 x 的二进制表示就是 1 后面跟 n 个 0。十六进制数字 0 代表 4 个二进制 0。所以,当 n 表示成 i+4j 的形式,其中 0≤i≤3,我们可以把 x 写成开头的十六进制数字为 1 (i=0)、2 (i=1)、4 (i=2) 或者 8 (i=3),后面跟随着 j 个十六进制的 0。比如,x=2048=2¹¹,我们有 n=11=3+4・2,从而得到十六进制表示 0x800。

2.字数据大小

  • 字长

每台计算机都有一个字长 (word size),指明指针数据的标称大小 (nominal size)。因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为 w 位的机器而言,虚拟地址的范围为 0~2ʷ−1,程序最多访问 2ʷ个字节。

字节是信息存储的基本单位;

字长是CPU处理能力的基本单位;

64位系统中,1字长等于8字节

C语言标准对不同数据类型的数字范围设置了 下界(这点在后面还将讲到),但是却没有上界。

3.寻址和字节顺序

对于跨越多字节的程序对象,必须建立两个规则:对象的地址、如何在内存中排列这些字节。

  1. 对象的地址为字节中最小的地址,如int类型(32位)的变量x,它的4个字节分别存储在0x100, 0x101, 0x102, 0x103位置。
  2. 排列对象字节的规则有两种:一种是小端法,最低有效字节在前面,还有一种是大端法,最高有效字节在后面。
  • 大端法:(人类阅读顺序)
低地址  --->  高地址
地址: 0x1000  0x1001  0x1002  0x1003
数据:  0x12    0x34    0x56    0x78
(MSB)                        (LSB)
  • 小端法:(机器计算友好)
低地址  --->  高地址
地址: 0x1000  0x1001  0x1002  0x1003
数据:  0x78    0x56    0x34    0x12
(LSB)                        (MSB)

与大端法相反,小端法的内存的第一个字节(低地址)是最不重要的部分(0x78)。你必须从右向左(从高地址到低地址)读取内存,才能拼出正确的数字。

对于大多数应用程序员来说,其机器所使用的字节顺序是完全不可见的。无论为哪种 类型的机器所编译的程序都会得到同样的结果;

  • 不过有时候,字节顺序会成为问题

第一种情况是在不同类型的机器之间通过网络传送二进制数据时, 一个常见的问题是当小端法机器产生的数据被发送到大端法机器或者反过来时,接收程序会发现,字里的字节成了反序的。

第二种情况是,当阅读表示整数数据的字节序列时字节顺序也很重要。

4004d3: 01 05 43 Ob 20 00 

add %eax,Ox200b43(%rip) 

第三种情况是,当编写规避正常的类型系统的程序时。在 C语言中,可以通过使用强制类型转换(cast)或联合(union)来允许以一种数据类型引用一个对象,而这种数据类型与创建这个对象时定义的数据类型不同。

#include <stdio.h>

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");
}

void show_int(int x) {
    show_bytes((byte_pointer)&x, sizeof(int));
}

void show_float(float x) {
    show_bytes((byte_pointer)&x, sizeof(float));
}

void show_pointer(void* x) {
    show_bytes((byte_pointer)&x, sizeof(void*));
}

void test_show_bytes(int val) {
    int ival = val;
    float fval = (float)ival;
    int* pval = &ival;
    show_int(ival);
    show_float(fval);
    show_pointer(pval);
}

int main() {
    test_show_bytes(12345);
    return 0;
}

运行结果:

xiin@XIIN:~/csapp$ ./test1
 39 30 00 00
 00 e4 40 46
 28 bd a2 83 fe 7f 00 00

4.表示字符串

字符串以'\0'结尾。

5.表示代码

从机器的角度来看,程序仅仅只是字节序列,机器没有关于原始源程序的任何信息。

6.布尔代数简介

  • 布尔环

7.C语言中的位级运算

  • 掩码

8.C语言中的逻辑运算

逻辑运算符&&和 II 与它们对应的位级运算&和1之间第二个重要的区别是,如果对第一个参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值。

例如:

  • 表达式 a&&5/a将不会造成被零除。

执行顺序:

  1. 先判断 a 是否为真(非零)。
  2. 如果 a == 0,则 a 为假,&& 右边 5 / a 不再计算,因此不会发生除以零的错误。
  3. 如果 a != 0,才会继续计算 5 / a,而此时 a 非零,所以除法安全。
  • 表达式 p&&*p++不会导致间接引用空指针。

执行顺序:

  1. 先判断 p 是否为真(即 p != NULL)。
  2. 如果 p == NULLp 为假,&& 右边 *p++ 不再计算,因此不会对空指针解引用。
  3. 如果 p != NULL,才会继续计算 *p++,这时解引用是安全的。

9.C语言中的移位运算

左移(<<)和右移(>>)运算符是左结合(left-associative)的,这意味着当连续出现多个移位运算符时,会从左向右计算,所以 x<<j<<k 等价于 (x<<j)<<k。

机器支持两种形式的右移:逻辑右移和算术右移。 逻辑右移在左端补 k 个 0 ,而算术右移是在左端补 K 个最高有效位的值。

在C语言中的整数右移:

  • 无符号整数右移:逻辑右移(高位补 0)
  • 有符号整数右移:由实现定义,但通常为算术右移(高位补符号位)

在C语言中,移动 k 位,如果 k 很大:数据类型本身是 w 位,若 k >= w,最终会移动 k mod w 位。

在 C 语言中,表达式 1<<2+3<<4 等价于 1<<(2+3)<<4, 这是由于加法和减法的优先级比移位运算要高

分类: CS-Basics 标签: CSAPP

评论

暂无评论数据

暂无评论数据

目录