寻址和字节顺序

​ ​ ​ ​ ​ ​ ​ 在几乎所有的机器上,多字节对象被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。比如,类型为 深入理解计算机系统——信息的表示和处理_c语言 的变量 深入理解计算机系统——信息的表示和处理_无符号数_02 的地址为 深入理解计算机系统——信息的表示和处理_无符号数_03,即在 深入理解计算机系统——信息的表示和处理_无符号数_04 中地址表达式 深入理解计算机系统——信息的表示和处理_无符号数_05 的值为 深入理解计算机系统——信息的表示和处理_无符号数_03,那么 深入理解计算机系统——信息的表示和处理_无符号数_02 的4个字节将会被存储在内存的 深入理解计算机系统——信息的表示和处理_d3_08

那么具体怎么存放呢?有两种方式:

  • 大端法:最高有效字节在最前面。
  • 小端法:最低有效字节在最前面。

​ ​ ​ ​ ​ ​ ​ 还是以刚刚的例子说明,深入理解计算机系统——信息的表示和处理_无符号数_02深入理解计算机系统——信息的表示和处理_d3_10 进制值是 深入理解计算机系统——信息的表示和处理_d3_11,地址范围 深入理解计算机系统——信息的表示和处理_无符号数_03 ~ 深入理解计算机系统——信息的表示和处理_无符号数_13

深入理解计算机系统——信息的表示和处理_d3_14

采用大端还是小端要看机器的类型,大多数是只用小端模式,也有新的微处理器采用双端法。

对于我们来说,机器使用的字节顺序我们是无法见到的,但是字节顺序对于使用者来说确实很重要的。比如说,一台大端机器和一台小端机器进行网络通信,接收程序中会发现字里的字节是反序的。为了避免这样的问题,接受方就需要做出一些转换。


深入理解计算机系统——信息的表示和处理_d3_15


深入理解计算机系统——信息的表示和处理_无符号数_04 库中的文件 深入理解计算机系统——信息的表示和处理_无符号数_17 定义了一组常量:深入理解计算机系统——信息的表示和处理_c语言_18
确定宽度类型的带格式打印需要使用宏,以与系统相关的方式扩展为格式串。比如:变量 深入理解计算机系统——信息的表示和处理_无符号数_02深入理解计算机系统——信息的表示和处理_c语言_20 的类型是 深入理解计算机系统——信息的表示和处理_d3_21深入理解计算机系统——信息的表示和处理_无符号数_22 ,可以通过调用 深入理解计算机系统——信息的表示和处理_无符号数_23

printf("x = %" PRId32 ", y = %" PRId64 "\n", x, y);

编译为 深入理解计算机系统——信息的表示和处理_无符号数_24 位程序时,宏 深入理解计算机系统——信息的表示和处理_无符号数_25 展开成字符串 “深入理解计算机系统——信息的表示和处理_c语言_26”,宏 深入理解计算机系统——信息的表示和处理_d3_27 展开成两个字符串 “深入理解计算机系统——信息的表示和处理_无符号数_28” “深入理解计算机系统——信息的表示和处理_无符号数_29”。当 深入理解计算机系统——信息的表示和处理_无符号数_04 预处理器遇到仅用空格(或其他空白字符)分隔的一个字符串常量序列时,就把他们串联起来。因此上面的 深入理解计算机系统——信息的表示和处理_无符号数_23

printf("x = %d, y = %lu\n", x, y);

使用宏能保证:不论代码是如何被编译的,都能生成正确的格式字符串。
注: 深入理解计算机系统——信息的表示和处理_无符号数_25深入理解计算机系统——信息的表示和处理_d3_27深入理解计算机系统——信息的表示和处理_无符号数_04 中需要使用头文件 深入理解计算机系统——信息的表示和处理_无符号数_35


C语言中的有符号数和无符号数

深入理解计算机系统——信息的表示和处理_c语言_36

2147483648U 的补码是:1000 0000, 0000 0000, 0000 0000, 0000 0000

深入理解计算机系统——信息的表示和处理_d3_37

-2147483647 的补码是:1000 0000, 0000 0000, 0000 0000, 0000 0001
和1U运算先变成无符号数,对应十进制2147483649U
所以最终运算得到:2147483648U,补码:1000 0000, 0000 0000, 0000 0000, 0000 0000


C编译器怎么确定数值常量的类型

对于不同版本的c标准,相关规定的具体内容有所差异,对于c90,相关规定如下表所示:

范围

类型

0~2深入理解计算机系统——信息的表示和处理_d3_38-1

int

2深入理解计算机系统——信息的表示和处理_d3_38~2深入理解计算机系统——信息的表示和处理_无符号数_40-1

unsigned int

2深入理解计算机系统——信息的表示和处理_无符号数_40~2深入理解计算机系统——信息的表示和处理_无符号数_42-1

long long

2深入理解计算机系统——信息的表示和处理_无符号数_42~2深入理解计算机系统——信息的表示和处理_c语言_44-1

unsigned long long

而对于c99,相关标准有了些变化:

范围

类型

0~2深入理解计算机系统——信息的表示和处理_d3_38-1

int

2深入理解计算机系统——信息的表示和处理_d3_38~2深入理解计算机系统——信息的表示和处理_无符号数_42-1

long long

2深入理解计算机系统——信息的表示和处理_无符号数_42~2深入理解计算机系统——信息的表示和处理_c语言_44-1

unsigned long long

编译器在遇到负值的时候,先不看符号,而是根据数字来确定类型,然后再处理负号。


C语言中 TMin 的写法

深入理解计算机系统——信息的表示和处理_无符号数_04 头文件深入理解计算机系统——信息的表示和处理_无符号数_17

/* Minimum and maximum values a 'signed int' can hold. */
#define
#define

深入理解计算机系统——信息的表示和处理_无符号数_52

  • 根据深入理解计算机系统——信息的表示和处理_c语言_53
  • 对于深入理解计算机系统——信息的表示和处理_c语言_54,编译器依次尝试 深入理解计算机系统——信息的表示和处理_d3_55深入理解计算机系统——信息的表示和处理_无符号数_56位机器上 深入理解计算机系统——信息的表示和处理_d3_57深入理解计算机系统——信息的表示和处理_c语言_58 一样,是 深入理解计算机系统——信息的表示和处理_无符号数_56 位), 最终选择 深入理解计算机系统——信息的表示和处理_无符号数_60 来表示。对于 深入理解计算机系统——信息的表示和处理_c语言_61深入理解计算机系统——信息的表示和处理_d3_62,如果表示为 深入理解计算机系统——信息的表示和处理_无符号数_56 位的二进制数字,它们的位表示是一样的,都是 深入理解计算机系统——信息的表示和处理_无符号数_64。所以这个常量表达式 深入理解计算机系统——信息的表示和处理_d3_62 的数据类型为 深入理解计算机系统——信息的表示和处理_无符号数_60 且值为 深入理解计算机系统——信息的表示和处理_c语言_61
  • 对于深入理解计算机系统——信息的表示和处理_无符号数_68,编译器依次选择 深入理解计算机系统——信息的表示和处理_无符号数_69,最终选择 深入理解计算机系统——信息的表示和处理_无符号数_70 类型才能容纳 深入理解计算机系统——信息的表示和处理_c语言_61 。用 深入理解计算机系统——信息的表示和处理_c语言_72位,可以唯一表示 深入理解计算机系统——信息的表示和处理_c语言_61深入理解计算机系统——信息的表示和处理_d3_62,所以这个常量表达式的数据类型为 深入理解计算机系统——信息的表示和处理_无符号数_70,值为 深入理解计算机系统——信息的表示和处理_d3_62
  • 对于深入理解计算机系统——信息的表示和处理_c语言_77 进制常数 深入理解计算机系统——信息的表示和处理_无符号数_64 (注意,按照C语言中整型常量的定义,这个整数常量是正数,值为 深入理解计算机系统——信息的表示和处理_d3_79),在 深入理解计算机系统——信息的表示和处理_无符号数_56 位机器上,编译器也是利用同样的规则,依照表一中的 深入理解计算机系统——信息的表示和处理_c语言_77 进制的列表来处理。两个语言标准中,都是首先跟 深入理解计算机系统——信息的表示和处理_无符号数_82深入理解计算机系统——信息的表示和处理_d3_83)比较,由于 深入理解计算机系统——信息的表示和处理_无符号数_64 更大,所以这个值不能用 深入理解计算机系统——信息的表示和处理_c语言_58 来表示。接下来和 深入理解计算机系统——信息的表示和处理_c语言_86深入理解计算机系统——信息的表示和处理_c语言_87)比较,由于比它小一些,所以选择 深入理解计算机系统——信息的表示和处理_无符号数_60 来表示。所以这个常量表达式的数据类型是 深入理解计算机系统——信息的表示和处理_无符号数_60,值为 深入理解计算机系统——信息的表示和处理_无符号数_64(或者说,是等于 深入理解计算机系统——信息的表示和处理_c语言_61
  • 深入理解计算机系统——信息的表示和处理_c语言_72 位的机器上,事情稍微有些不同。两个语言标准中,十进制的格式 深入理解计算机系统——信息的表示和处理_c语言_93 都是深入理解计算机系统——信息的表示和处理_d3_57深入理解计算机系统——信息的表示和处理_c语言_72位)类型,值为 深入理解计算机系统——信息的表示和处理_c语言_93,然而十六进制格式 深入理解计算机系统——信息的表示和处理_无符号数_64 都是 深入理解计算机系统——信息的表示和处理_无符号数_60 类型,值为 深入理解计算机系统——信息的表示和处理_无符号数_64(或者说,是 深入理解计算机系统——信息的表示和处理_c语言_61)。
  • 用一句话来解释深入理解计算机系统——信息的表示和处理_c语言_53 语言中 深入理解计算机系统——信息的表示和处理_无符号数_102的古怪写法的原因:虽然 深入理解计算机系统——信息的表示和处理_d3_62 这个数值能够用int类型来表示,但在 深入理解计算机系统——信息的表示和处理_c语言_53 语言中却没法写出对应这个数值的 深入理解计算机系统——信息的表示和处理_c语言_58

数据大小转换

short sx = -12345;
unsigned uy = sx;
  • 在一台大端法机器上:​​uy = 4294954951: ff ff cf c7​
  • 这表明当把​​short​​​转换成​​unsigned​​​时,我们先要​​改变大小​​​,之后再完成​​有符号​​​到​​无符号​​的转换。这个规则是C语言标准要求的。

截断数字

  • 将一个深入理解计算机系统——信息的表示和处理_无符号数_106位的数深入理解计算机系统——信息的表示和处理_c语言_107截断为一个深入理解计算机系统——信息的表示和处理_c语言_108位数字时,我们会丢弃高深入理解计算机系统——信息的表示和处理_无符号数_109位,得到一个位向量深入理解计算机系统——信息的表示和处理_d3_110。截断一个数字会改变它的值——溢出的一种形式。

无符号数减法

int sum(int a[], unsigned len) {
int res = 0;
for(int i = 0; i <= len - 1; ++i)
res += a[i]
return res;
}
  • 对于这样一个求和函数,当深入理解计算机系统——信息的表示和处理_无符号数_111的时候,本应该返回深入理解计算机系统——信息的表示和处理_d3_112,但实际上会出错。循环条件是无符号数加减法,这等效于模数加法(因为会溢出,而实际上都是按照无符号数读取数值)。最终陷入死循环。
  • 很多 函数(例如​​strlen、STL集合相关​​​) 返回值都是​​unsigned​​ ,要格外小心。最好不要在加减法中使用无符号数。
  • 可以将条件修改为​​i < len​

判断溢出

  • 加法
int tadd_ok(int x, int y) {
int sum = x+y;
int neg_over = x < 0 && y < 0 && sum >= 0;
int pos_over = x >= 0 && y >= 0 && sum < 0;
return !neg_over && !pos_over;
}
  • 减法
int tsub_ok(int x, int y) {
// 当y为最小整数的时候,就产生了溢出,因为任何数减最小数都会溢出
if (y == INT_MIN) {
return 0;
}

int neg_y = -y;
int sum = x + neg_y;
int pos_over = x > 0 && neg_y > 0 && sum < 0;
int neg_over = x < 0 && neg_y < 0 && sum >= 0;

return !(pos_over || neg_over);
}
  • 乘法
int tmult_ok(int x, int y) {
int p = x*y;
/* Either x is zero, or dividing p by x gives y */
return !x || p/x == y;
}

int转换到float精度缺失

  • 首先来看看我们可爱的int型变量吧,在一台典型的32位机器上一个有符号的int型的取值范围为-2147483648 ~ 2147483647(-2^31 ~ (2^31-1))。也就是说,在一个4字节(32位2进制),除去首位用于符号位表示正负外,其余的31位都是数字的有效位。
  • 下面再来看看“万恶的”float型变量:根据IEEE的浮点标准,一个浮点数应该用下述形式来表示:

V=(-1)^s * M * 2^E (公式1)

  • 在C语言中,32位的​​float​​型变量有着这样的规定:首位表示符号位 深入理解计算机系统——信息的表示和处理_c语言_113 ,接下来的深入理解计算机系统——信息的表示和处理_c语言_114位(指数域)用于表示深入理解计算机系统——信息的表示和处理_c语言_115的指数深入理解计算机系统——信息的表示和处理_c语言_116,剩余的深入理解计算机系统——信息的表示和处理_c语言_117位(小数域)表示深入理解计算机系统——信息的表示和处理_c语言_118(取值范围为 深入理解计算机系统——信息的表示和处理_无符号数_119深入理解计算机系统——信息的表示和处理_d3_120)。除了上述规定以外,根据指数域的二进制表示情况不同,被编码的​​float​​型数字又可以分成三种情况——
  1. 规格化值。当指数域的深入理解计算机系统——信息的表示和处理_d3_121个二进制数字既非全零又非全 深入理解计算机系统——信息的表示和处理_d3_122 时,​​float​​数值就是这种情况。设指数域的八位二进制所表示的十进制数为e, 则公式深入理解计算机系统——信息的表示和处理_d3_122中的深入理解计算机系统——信息的表示和处理_c语言_124就是 深入理解计算机系统——信息的表示和处理_d3_125(公式2);
    而且此时,将小数域所表示的二进制假设为 深入理解计算机系统——信息的表示和处理_无符号数_126,则该小数域所表示的值即为 深入理解计算机系统——信息的表示和处理_无符号数_127.于是 深入理解计算机系统——信息的表示和处理_无符号数_128
  2. 非规格化值。当指数域的深入理解计算机系统——信息的表示和处理_d3_121个二进制数字为全深入理解计算机系统——信息的表示和处理_无符号数_130时,​​float​​数值就为这种情况。这时指数域所表示的十进制数为深入理解计算机系统——信息的表示和处理_无符号数_130,规定指数值为 深入理解计算机系统——信息的表示和处理_无符号数_132, 也就是深入理解计算机系统——信息的表示和处理_c语言_124为定值深入理解计算机系统——信息的表示和处理_无符号数_134;此时小数域的值仍表示深入理解计算机系统——信息的表示和处理_d3_135,但是M的值却变成 深入理解计算机系统——信息的表示和处理_d3_136
  3. 特殊值。当指数域的深入理解计算机系统——信息的表示和处理_d3_121个二进制数字为全1时即为这种情况。当小数域为全零时,该​​float​​值根据符号位的不同表示正无穷或者负无穷;当小数域为非全零时,该​​float​​值为​​NaN​​(Not a Number)。
  • 以上,只是在C语言中对​​int​​和​​float​​的规约。具体在代码中执行强制类型转化究竟会发生什么?从下面两句很简单的语句开始:
int a = 3490593;
float b = (float)a;

那么在内存中深入理解计算机系统——信息的表示和处理_无符号数_138深入理解计算机系统——信息的表示和处理_c语言_139究竟存放的是什么值呢?

  • 深入理解计算机系统——信息的表示和处理_无符号数_138展开为二进制,其值为​​​0000 0000 0011 0101 0100 0011 0010 0001​​​,其十六进制即为​​0x00354321​​​。 因为要转化为​​float​​​型,所以首先要对上述二进制的表示形式改变为深入理解计算机系统——信息的表示和处理_c语言_141的形式.由于该数明显大于深入理解计算机系统——信息的表示和处理_c语言_142,所以按照IEEE的标准,其浮点形势必然为规格化值。因此 ,转化后的形式为
    深入理解计算机系统——信息的表示和处理_无符号数_143
  • 根据 规格化值的定义,深入理解计算机系统——信息的表示和处理_c语言_144. 所以​​​f = 0.101010100001100100001​​​.因为float型变量的小数域一共23位。所以b的最后23位可以得出,其值为​​1010 1010 0001 1001 000 0100​
  • 下面再演绎指数域的值:因为a的指数表示法中,指数深入理解计算机系统——信息的表示和处理_无符号数_145。根据公式2深入理解计算机系统——信息的表示和处理_c语言_146.所以可以得出b的指数域的二进制表示为:​​10010100​​​。在加上原数为正,所以符号位深入理解计算机系统——信息的表示和处理_c语言_147
  • 所以,可以得出b的二进制表示为​​0 10010100 10101010000110010000100​​​。转化为十六位进制则是​​0x4A550C84​​​。换句话说,它存储在内存中的值是与a是完全不同的。但是其间还是有关联性的——a的首位为深入理解计算机系统——信息的表示和处理_c语言_142的数值位后的二进制表示是与b的小数域完全相同的。
  • 很快,问题就出现了。int型的有效位数是31,而float型小数域的有效位只有深入理解计算机系统——信息的表示和处理_c语言_117位,也就是说如果上面的深入理解计算机系统——信息的表示和处理_无符号数_138的二进制的有效位超过了深入理解计算机系统——信息的表示和处理_d3_151位,那么​​​float​​​型的小数域的精度就不够了。因此必须进行舍入。比如:如果上面的a的二进制为​​0000 0001 1111 0101 0100 0011 0010 0001​​​。这时b的小数域必须有深入理解计算机系统——信息的表示和处理_d3_151位才够,但是,这显然是不现实的,因此必须舍入到深入理解计算机系统——信息的表示和处理_c语言_117位,舍入的原则是:所得结果的最低有效位为0。因此这个深入理解计算机系统——信息的表示和处理_无符号数_138在转换到​​​float​​​时,其精度就会丢失,因为该​​float​​​的最后深入理解计算机系统——信息的表示和处理_c语言_117位变成了​​​11110101010000110010000​​——这显然是与原值不符的。
  • 实际上,C语言中对于​​double​​​型在深入理解计算机系统——信息的表示和处理_无符号数_56位机器上的小数域有深入理解计算机系统——信息的表示和处理_d3_157位,对于​​​int​​​型的深入理解计算机系统——信息的表示和处理_无符号数_158位有效位是绰绰有余了。这就是为什么大部分C语言教材上鼓励读者在执行强制类型转换时将​​​int​​​型转换成​​double​​​。同时,这可能也是为什么​​int​​​型能够直接隐式转换到​​double​​型的缘故。