信息存储

大多数计算机使用 8 位的块,或者字节( byte ),作为最小的可寻址的内存单位,而不是访问内存中单独的位。

  • 机器级程序将内存视为一个非常大的字节数组,称为虚拟内存virtual memory )。
  • 内存的每个字节都由一个唯一的数字来标识,称为它的地址address ),所有可能地址的集合就称为虚拟地址空间virtual address space )。
  • 顾名思义,这个虚拟地址空间只是一个展现给机器级程序的概念性映像。实际的实现是将动态随机访问存储器( DRAM )、闪存、磁盘存储器、特殊硬件和操作系统软件结合起来,为程序提供一个看上去统一的字节数组。

一、 十六进制表示法

  • 一个字节由 8 位组成。在二进制表示法中,它的值域是 0000000 0 2 00000000_2 000000002 ~ 1111111 1 2 11111111_2 111111112。如果看成十进制整数,它的值域就是 0 10 0_{10} 010 ~ 25 5 10 255_{10} 25510
  • 两种符号表示法对于描述位模式来说都不是非常方便二进制表示法太冗长,而十进制表示法与位模式的互相转化很麻烦
  • 替代的方法是,以 16 为基数,或者叫做十六进制hexadecimal )数,来表示位模式。十六进制(简写为 “hex”)使用数字 0 ~ 9,以及字符 “A” ~ “F” 来表示 16 个可能的值。用十六进制书写,一个字节的值域为 0 0 16 00_{16} 0016 ~ F F 16 FF_{16} FF16
  • 下表展示了 16 个十六进制数字对应的十进制值和二进制值:
十六进制数字01234567
十进制值01234567
二进制值00000001001000110100010101100111
十六进制数字89ABCDEF
十进制值89101112131415
二进制值10001001101010111100110111101111
  • 在 C 语言中,以 0x 或 0x 开头的数字常量被认为是十六进制的值。字符 A ~ F 既可以是大写,也可以是小写。
  • 假设给你一个数字 0x173A4C。可以通过展开每个十六进制数字,将它转换为二进制格式,如下所示:
十六进制173A4C
二进制000101110011101001001100
  • 这样就得到了二进制表示:000101110011101001001100 。
  • 反过来,如果给定一个二进制数字 1111001010110110110011,可以通过首先把它分为每 4 位一组来转换为十六进制,不过要注意,如果位总数不是 4 的倍数,最左边的一组可以少于 4 位,前面用 0 补足。然后将每个 4 位组转换为相应的十六进制数字:
二进制1111001010110110110011
十六进制3CADB3

二、字数据大小

每台计算机都有一个字长( word size ),指明指针数据的标称大小( nominal size )。

  • 因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。

  • 也就是说,对于一个字长为 w w w 位的机器而言,虚拟地址的范围为 0 ~ 2 w − 1 2^{w}-1 2w1,程序最多访问 2 w 2^w 2w 个字节。

  • 32 字长限制虚拟地址空间为 4 千兆字节(写作 4 GB ),也就是说,刚刚超过 4 × 1 0 9 4×10^{9} 4×109 字节。扩展到 64 字长使得虚拟地址空间为 16 EB ,大约是 1.84 × 1 0 19 1.84×10^{19} 1.84×1019 字节。

  • 大多数 64 位机器也可以运行为 32 位机器编译的程序,这是一种向后兼容

  • 我们将程序称为 “32 位程序“ 或 “64 位程序” 时,区别在于该程序是如何编译的,而不是其运行的机器类型。

  • 计算机和编译器支持多种不同方式编码的数字格式,如不同长度的整数和浮点数。

  • 例如,C 语言支持整数和浮点数的多种数据格式:

C 声明字节数
有符合无符号32 64
[signed] charunsigned char11
shortunsigned short22
intunsigned44
longunsigned long48
int32_tuint32_t44
int64_tuint64_t88
char *48
float44
double88
  • 有些数据类型的确切字节数依赖于程序是如何被编译的。上面给出的是 32 位和 64 位程序的典型值。
  • 整数或者为有符号的,即可以表示负数、零和正数;或者为无符号的,即只能表示非负数。

三、寻址和字节顺序

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

  • 在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。

  • 例如,假设一个类型为 int 的变量 × 的地址为 0x100,也就是说,地址表达式 &x 的值为 0x100。那么,(假设数据类型 int 为 32 位表示)× 的 4 个字节将被存储在内存的 0x100、0x101、0x102 和 0x103 位置。

  • 排列表示一个对象的字节有两个通用的规则。考虑一个 w w w 位的整数,其位表示为 [ x w − 1 x_{w-1} xw1 x w − 2 x_{w-2} xw2 ,…, x 1 x_1 x1 x 0 x_0 x0],其中 x w − 1 x_{w-1} xw1最高有效位,而 x 0 x_0 x0最低有效位

  • 假设 w w w 是 8 的倍数,这些位就能被分组成为字节,其中最高有效字节包含位 [ x w − 1 x_{w-1} xw1 x w − 2 x_{w-2} xw2 ,…, x w − 8 x_{w-8} xw8 ],而最低有效字节包含位 [ x 7 x_7 x7 x 6 x_6 x6 ,…, x 0 x_0 x0 ],其他字节包含中间的位。

  • 某些机器选择在内存中按照从最低有效字节到最高有效字节的顺序存储对象,而另一些机器则按照从最高有效字节到最低有效字节的顺序存储。前一种规则——最低有效字节在最前面的方式,称为小端法little endian )。后一种规则——最高有效字节在最前面的方式,称为大端法big endian )。

  • 假设变量 x 的类型为 int ,位于地址 0x100 处,它的十六进制值为 0x01234567,地址范围 0x100~0x103 的字节顺序依赖于机器的类型

在这里插入图片描述

  • 注意:在字 0x01234567 中,高位字节的十六进制值为 0x01,而低位字节值为 0×67。

  • 提示:两种顺序并没有优劣之分,一旦选择了特定操作系统,那么字节顺序也就固定下来。

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

    • 首先是在不同类型的机器之间通过网络传送二进制数据时,一个常见的问题是当小端法机器产生的数据被发送到大端法机器或者反过来时,接收程序会发现,字里的字节成了反序的。为了避免这类问题,网络应用程序的代码编写必须遵守已建立的关于字节顺序的规则,以确保发送方机器将它的内部表示转换成网络标准,而接收方机器则将网络标准转换为它的内部表示。
    • 第二种情况是,当阅读表示整数数据的字节序列时字节顺序也很重要。
    • 第三种情况是当编写规避正常的类型系统的程序时。在 C 语言中,可以通过使用强制类型转换cast )或联合( union )来允许以一种数据类型引用一个对象,而这种数据类型与创建这个对象时定义的数据类型不同。大多数应用编程都强烈不推荐这种编码技巧,但是它们对系统级编程来说是非常有用,甚至是必需的。

四、表示字符串

C 语言中字符串被编码为一个以 null(其值为 0 )字符结尾的字符数组。

  • 每个字符都由某个标准编码来表示,最常见的是 ASCII 字符码。因此,如果我们以参数 “12345” 和 6(包括终止符)来运行例程 show_bytes ,我们得到结果 31 32 33 34 35 00
  • 请注意,十进制数字 x 的 ASCII 码正好是 0x3x ,而终止字节的十六进制表示为 0x00,在使用 ASCII 码作为字符码的任何系统上都将得到相同的结果,与字节顺序字大小规则无关
  • 因而,文本数据比二进制数据具有更强的平台独立性

五、表示代码

  • 不同的机器类型使用不同的且不兼容的指令和编码方式,即使是完全一样的进程,运行在不同的操作系统上也会有不同的编码规则,因此二进制代码是不兼容的。
  • 二进制代码很少能在不同机器和操作系统组合之间移植。
  • 计算机系统的一个基本概念就是,从机器的角度来看,程序仅仅只是字节序列。机器没有关于原始源程序的任何信息,除了可能有些用来帮助调试的辅助表以外。

六、布尔代数

起源于1850年前后乔治·布尔( George Boole , 1815-1864)的工作,因此也称为布尔代数Boolean algebra )。布尔注意到通过将逻辑值 TRUE ()和 FALSE ()编码为二进制值 1 和 0,能够设计出一种代数,以研究逻辑推理的基本原则。

  • 常见的布尔代数运算包括

    • 逻辑(AND):表示两个命题同时成立的情况。用符号 “∧” 表示,例如 A ∧ B 表示命题 A 和 B 同时成立

    • 逻辑(OR):表示两个命题中至少有一个成立的情况。用符号 “∨” 表示,例如 A ∨ B 表示命题 A 或 B 至少有一个成立

    • 逻辑(NOT):表示对命题的否定。用符号 “¬” 或 “~” 表示,例如 ¬A 或 ~A 表示命题 A 的否定

    • 异或(XOR):表示两个命题中仅有一个成立的情况。用符号 “⊕” 或 “⊻” 表示,例如 A ⊕ B 表示命题 A 和 B 中仅有一个成立

  • 示意图

在这里插入图片描述

七、C 语言中的位级运算

C 语言的一个很有用的特性就是它支持按位布尔运算

  • 以下是一些对 char 数据类型表达式求值的例子:
C 的表达式二进制表达式二进制结果十六进制结果
~0x41~[0100 0001][1011 1110]0xBE
~0x00~ [0000 0000][1111 1111]0xFF
0x69&0x55[0110 1001]&[0101 0101][0100 0001]0x41
0x69``0x55[0110 1001]``[0101 0101]
  • 正如示例说明的那样,确定一个位级表达式的结果最好的方法,就是将十六进制的参数扩展成二进制表示并执行二进制运算,然后再转换回十六进制

  • 位运算一个常见的用法就是实现掩码运算,通俗点讲,通过位运算可以得到特定的位序列。例如对于操作数 0x89ABCDEF,我们想要得到该操作数的最低有效字节的值,可以通过 &0xFF,这样我们就得到了最低有效字节 0x000000EF。

0x89ABCDEF => &0xFF(0x000000FF) => 0x000000EF

八、C 语言中的逻辑运算

C 语言还提供了一组逻辑运算符 ||&&!,分别对应于命题逻辑中的 OR 、AND 和 NOT 运算。

  • 逻辑运算很容易和位级运算相混淆,但是它们的功能是完全不同的

  • 逻辑运算认为所有非零的参数都表示 TRUE,只有参数 0 表示 FALSE

  • 以下是一些表达式求值的示例:

表达式结果
!0x410x00
!0×000x01
!!0x410x01
0x69&&0x550x01
0x69`
  • 可以观察到,按位运算只有在特殊情况下,也就是参数被限制为 0 或者 1 时,才和与其对应的逻辑运算有相同的行为
  • 逻辑运算符 &&|| 与它们对应的位级运算 &|之间第二个重要的区别是:如果对第一个参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值
  • 因此,例如,表达式 ( a && 5/a ) 将不会造成被零除,而表达式 p && *p++ 也不会导致间接引用空指针。
// 若:a=0,则返回FALSE。因此,不会进行 5/0 的错误计算。
if(a && 5/a)

九、C 语言中的移位运算

C 语言还提供了一组移位运算,向左或者向右移动位模式。

  • 对于一个位表示为 [ x w − 1 x_{w-1} xw1 , x w − 2 x_{w-2} xw2 , … , x 0 x_{0} x0 ] 的操作数 x x x, C 表达式 x<<k 会生成一个值,其位表示为 [ x w − k − 1 x_{w-k-1} xwk1 , x w − k − 2 x_{w-k-2} xwk2 , … , x 0 x_{0} x0 , 0 , … , 0 ]。也就是说, x x x左移 k k k 位,丢弃最高 k k k 位,并在 k k k 个 0。
  • 移位量应该是一个 0 ~ ( w w w -1) 之间的值。
  • 移位运算是从左至右可结合的,所以 x <<j<< k 等价于 (x<< j) << k 。

  • 有一个相应的右移运算 x>>k,但是它的行为有点微妙。

  • 一般而言,机器支持两种形式的右移逻辑右移算术右移

  • 逻辑右移 k k k0,得到的结果是 [ 0 , … , 0 , x w − 1 x_{w-1} xw1 , x w − 2 x_{w-2} xw2 , … , x k x_{k} xk ] 。

  • 算术右移是在 k k k最高有效位的值,得到的结果是 [ x w − 1 x_{w-1} xw1 , … , x w − 1 x_{w-1} xw1 , x w − 1 x_{w-1} xw1 , x w − 2 x_{w-2} xw2 , … , x k x_{k} xk ] 。这种做法看上去可能有点奇特,但是我们会发现它对有符号整数数据的运算非常有用。

  • 让我们来看一个例子,下面的表给出了对一个 8 位参数 x x x 的两个不同的值做不同的移位操作得到的结果:

操作
参数 ×[01100011] [10010101]
x << 4[00110000] [01010000]
x >> 4(逻辑右移)[00000110] [00001001]
x >> 4(算术右移)[00000110] [11111001]
  • 斜体的数字表示的是最右端(左移)或最左端(右移)填充的值。

  • 可以看到唯一的例外是算术右移,因为操作数的最高位是 1,填充的就是 1。

  • C 语言标准并没有明确定义对于有符号数应该使用哪种类型的右移 —— 算术右移或者逻辑右移都可以

  • 不幸地,这就意味着任何假设一种或者另一种右移形式的代码都可能会遇到可移植性问题

  • 然而,实际上,几乎所有的编译器 / 机器组合对有符号数使用算术右移

  • 另一方面,对于无符号数,右移必须是逻辑的

  • 与 C 相比, Java 对于如何进行右移有明确的定义。表达是 x >> k 会将 × 算术右移 k 个位置,而 x >>> k 会对 x 做逻辑右移。

十、结束语


“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值