寻址和字节顺序
在几乎所有的机器上,多字节对象被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。比如,类型为 的变量 的地址为 ,即在 中地址表达式 的值为 ,那么 的4个字节将会被存储在内存的
那么具体怎么存放呢?有两种方式:
- 大端法:最高有效字节在最前面。
- 小端法:最低有效字节在最前面。
还是以刚刚的例子说明, 的 进制值是 ,地址范围 ~
采用大端还是小端要看机器的类型,大多数是只用小端模式,也有新的微处理器采用双端法。
对于我们来说,机器使用的字节顺序我们是无法见到的,但是字节顺序对于使用者来说确实很重要的。比如说,一台大端机器和一台小端机器进行网络通信,接收程序中会发现字里的字节是反序的。为了避免这样的问题,接受方就需要做出一些转换。
库中的文件 定义了一组常量:
确定宽度类型的带格式打印需要使用宏,以与系统相关的方式扩展为格式串。比如:变量 和 的类型是 和 ,可以通过调用
编译为 位程序时,宏 展开成字符串 “”,宏 展开成两个字符串 “” “”。当 预处理器遇到仅用空格(或其他空白字符)分隔的一个字符串常量序列时,就把他们串联起来。因此上面的
使用宏能保证:不论代码是如何被编译的,都能生成正确的格式字符串。
注: 和 在 中需要使用头文件
C语言中的有符号数和无符号数
2147483648U 的补码是:1000 0000, 0000 0000, 0000 0000, 0000 0000
-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-1 | int |
2~2-1 | unsigned int |
2~2-1 | long long |
2~2-1 | unsigned long long |
而对于c99,相关标准有了些变化:
范围 | 类型 |
0~2-1 | int |
2~2-1 | long long |
2~2-1 | unsigned long long |
编译器在遇到负值的时候,先不看符号,而是根据数字来确定类型,然后再处理负号。
C语言中 TMin 的写法
头文件
- 根据
- 对于,编译器依次尝试 (位机器上 跟 一样,是 位), 最终选择 来表示。对于 和 ,如果表示为 位的二进制数字,它们的位表示是一样的,都是 。所以这个常量表达式 的数据类型为 且值为 。
- 对于,编译器依次选择 ,最终选择 类型才能容纳 。用 位,可以唯一表示 和 ,所以这个常量表达式的数据类型为 ,值为 。
- 对于 进制常数 (注意,按照C语言中整型常量的定义,这个整数常量是正数,值为 ),在 位机器上,编译器也是利用同样的规则,依照表一中的 进制的列表来处理。两个语言标准中,都是首先跟 ()比较,由于 更大,所以这个值不能用 来表示。接下来和 ()比较,由于比它小一些,所以选择 来表示。所以这个常量表达式的数据类型是 ,值为 (或者说,是等于
- 在 位的机器上,事情稍微有些不同。两个语言标准中,十进制的格式 都是(位)类型,值为 ,然而十六进制格式 都是 类型,值为 (或者说,是 )。
- 用一句话来解释 语言中 的古怪写法的原因:虽然 这个数值能够用int类型来表示,但在 语言中却没法写出对应这个数值的
数据大小转换
- 在一台大端法机器上:
uy = 4294954951: ff ff cf c7
- 这表明当把
short
转换成unsigned
时,我们先要改变大小
,之后再完成有符号
到无符号
的转换。这个规则是C语言标准要求的。
截断数字
- 将一个位的数截断为一个位数字时,我们会丢弃高位,得到一个位向量。截断一个数字会改变它的值——溢出的一种形式。
无符号数减法
- 对于这样一个求和函数,当的时候,本应该返回,但实际上会出错。循环条件是无符号数加减法,这等效于模数加法(因为会溢出,而实际上都是按照无符号数读取数值)。最终陷入死循环。
- 很多 函数(例如
strlen、STL集合相关
) 返回值都是unsigned
,要格外小心。最好不要在加减法中使用无符号数。 - 可以将条件修改为
i < len
判断溢出
- 加法
- 减法
- 乘法
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
型变量有着这样的规定:首位表示符号位 ,接下来的位(指数域)用于表示的指数,剩余的位(小数域)表示(取值范围为 或 )。除了上述规定以外,根据指数域的二进制表示情况不同,被编码的float
型数字又可以分成三种情况——
- 规格化值。当指数域的个二进制数字既非全零又非全 时,
float
数值就是这种情况。设指数域的八位二进制所表示的十进制数为e, 则公式中的就是 (公式2);
而且此时,将小数域所表示的二进制假设为 ,则该小数域所表示的值即为 .于是 - 非规格化值。当指数域的个二进制数字为全时,
float
数值就为这种情况。这时指数域所表示的十进制数为,规定指数值为 , 也就是为定值;此时小数域的值仍表示,但是M的值却变成 。 - 特殊值。当指数域的个二进制数字为全1时即为这种情况。当小数域为全零时,该
float
值根据符号位的不同表示正无穷或者负无穷;当小数域为非全零时,该float
值为NaN
(Not a Number)。
- 以上,只是在C语言中对
int
和float
的规约。具体在代码中执行强制类型转化究竟会发生什么?从下面两句很简单的语句开始:
那么在内存中和究竟存放的是什么值呢?
- 将展开为二进制,其值为
0000 0000 0011 0101 0100 0011 0010 0001
,其十六进制即为0x00354321
。 因为要转化为float
型,所以首先要对上述二进制的表示形式改变为的形式.由于该数明显大于,所以按照IEEE的标准,其浮点形势必然为规格化值。因此 ,转化后的形式为 - 根据 规格化值的定义,. 所以
f = 0.101010100001100100001
.因为float型变量的小数域一共23位。所以b的最后23位可以得出,其值为1010 1010 0001 1001 000 0100
- 下面再演绎指数域的值:因为a的指数表示法中,指数。根据公式2,.所以可以得出b的指数域的二进制表示为:
10010100
。在加上原数为正,所以符号位。 - 所以,可以得出b的二进制表示为
0 10010100 10101010000110010000100
。转化为十六位进制则是0x4A550C84
。换句话说,它存储在内存中的值是与a是完全不同的。但是其间还是有关联性的——a的首位为的数值位后的二进制表示是与b的小数域完全相同的。 - 很快,问题就出现了。int型的有效位数是31,而float型小数域的有效位只有位,也就是说如果上面的的二进制的有效位超过了位,那么
float
型的小数域的精度就不够了。因此必须进行舍入。比如:如果上面的a的二进制为0000 0001 1111 0101 0100 0011 0010 0001
。这时b的小数域必须有位才够,但是,这显然是不现实的,因此必须舍入到位,舍入的原则是:所得结果的最低有效位为0。因此这个在转换到float
时,其精度就会丢失,因为该float
的最后位变成了11110101010000110010000
——这显然是与原值不符的。 - 实际上,C语言中对于
double
型在位机器上的小数域有位,对于int
型的位有效位是绰绰有余了。这就是为什么大部分C语言教材上鼓励读者在执行强制类型转换时将int
型转换成double
。同时,这可能也是为什么int
型能够直接隐式转换到double
型的缘故。