在ILP32中, char, short, int, long, long long, pointer分别占1, 2, 4, 4, 8, 4个字节,
在 LP64中, char, short, int, long, long long, pointer分别占1, 2, 4, 8, 8, 8个字节,
无论是在ILP32中还是LP64中, long long总是占8个字节,下面给出简单的C代码实现表征出整数的取值范围先。
o foo.c
1 #include <stdio.h> 2 /** 3 * The size (n bytes) of basic types 4 * ================================= 5 * char short int long long long pointer 6 * ----- ---- ----- --- ---- --------- ------- 7 * LP64 1 2 4 8 8 8 8 * ILP32 1 2 4 4 8 4 9 */ 10 typedef char __s8; 11 typedef short __s16; 12 typedef int __s32; 13 typedef long long __s64; 14 typedef unsigned char __u8; 15 typedef unsigned short __u16; 16 typedef unsigned int __u32; 17 typedef unsigned long long __u64; 18 19 #define SMAX8 ((__s8 )(((__u8 )~0) >> 1)) 20 #define SMAX16 ((__s16)(((__u16)~0) >> 1)) 21 #define SMAX32 ((__s32)(((__u32)~0) >> 1)) 22 #define SMAX64 ((__s64)(((__u64)~0) >> 1)) 23 24 #define SMIN8 -SMAX8 25 #define SMIN16 -SMAX16 26 #define SMIN32 -SMAX32 27 #define SMIN64 -SMAX64 28 29 #define UMAX8 ((__u8 )~0) 30 #define UMAX16 ((__u16)~0) 31 #define UMAX32 ((__u32)~0) 32 #define UMAX64 ((__u64)~0) 33 34 #define UMIN8 ((__u8 )0) 35 #define UMIN16 ((__u16)0) 36 #define UMIN32 ((__u32)0) 37 #define UMIN64 ((__u64)0) 38 39 int main(int argc, char *argv[]) 40 { 41 __s8 smax8 = SMAX8; 42 __s16 smax16 = SMAX16; 43 __s32 smax32 = SMAX32; 44 __s64 smax64 = SMAX64; 45 __s8 smin8 = SMIN8; 46 __s16 smin16 = SMIN16; 47 __s32 smin32 = SMIN32; 48 __s64 smin64 = SMIN64; 49 printf("s64: [%llx, %llx] [%lld, %lld] ", smin64, smax64, smin64, smax64); 50 printf("s32: [%x, %x] [%d, %d] ", smin32, smax32, smin32, smax32); 51 printf("s16: [%x, %x] [%d, %d] ", smin16, smax16, smin16, smax16); 52 printf("s8 : [%x, %x] [%d, %d] ", smin8, smax8, smin8, smax8); 53 printf(" "); 54 55 __u8 umax8 = UMAX8; 56 __u16 umax16 = UMAX16; 57 __u32 umax32 = UMAX32; 58 __u64 umax64 = UMAX64; 59 __u8 umin8 = UMIN8; 60 __u16 umin16 = UMIN16; 61 __u32 umin32 = UMIN32; 62 __u64 umin64 = UMIN64; 63 printf("u64: [%llx, %llx] [%lld, %llu] ", umin64, umax64, umin64, umax64); 64 printf("u32: [%x, %x] [%d, %u] ", umin32, umax32, umin32, umax32); 65 printf("u16: [%x, %x] [%d, %u] ", umin16, umax16, umin16, umax16); 66 printf("u8 : [%x, %x] [%d, %u] ", umin8, umax8, umin8, umax8); 67 68 return 0; 69 }
o 编译并执行
$ gcc -g -Wall -m32 -o foo foo.c $ ./foo s64: [8000000000000001, 7fffffffffffffff] [-9223372036854775807, 9223372036854775807] s32: [80000001, 7fffffff] [-2147483647, 2147483647] s16: [ffff8001, 7fff] [-32767, 32767] s8 : [ffffff81, 7f] [-127, 127] u64: [0, ffffffffffffffff] [0, 18446744073709551615] u32: [0, ffffffff] [0, 4294967295] u16: [0, ffff] [0, 65535] u8 : [0, ff] [0, 255]
注意: 二进制数在计算机中一律以补码表示。 这里简单说说二进制编码中的原码,反码以及补码(注:移码这里不谈)以帮助理解上面的输出。
1. 原码的编码规则
1.1 原码即"原始编码", 最高位为符号位,0表示整数,1表示负数;
1.2 +0和-0的原码表示是不同的。在16位机器上,
+0 = 0000 0000 0000 0000b -0 = 1000 0000 0000 0000b
2. 反码的编码规则
2.1 正数的反码等于其原码;
2.2 负数的反码是符号位不变,除符号外之外的其他位按位取反;
2.3 +0和-0的反码表示也是不同的。在16位机器上,
+0 = 0111 1111 1111 1111b -0 = 1111 1111 1111 1111b
3. 补码的编码规则
3.1 正数的补码等于原码;
3.2 负数的补码是符号位不变,除符号外之外的其他位按位取反,再给最低位加1;
3.3 +0和-0的补码是唯一的,都是0。在16位机器上,
+0 = 0000 0000 0000 0000b ;= +0(反) -0 = 0000 0000 0000 0000b ;= -0(反)+1
4. 为什么要引入补码?
4.1 无论是原码,还是反码,都无法解决0的的二义性问题。补码的引入,解决了这一问题,也就是0的表示是唯一的;
4.2 让符号位参与运算。因此,所有减法都可以用加法器实现。
o 因为编译选项是-m32, 所以:
-127 的补码表示是 0xffffff81 = (1111 1111 1111 1111 1111 1111 1000 0001b)
-32767 的补码表示是 0xffff8001 = (1111 1111 1111 1111 1000 0000 0000 0001b)
-2147483647 的补码表示是 0x80000001 = (1000 0000 0000 0000 0000 0000 0000 0001b)
-9223372036854775807 的补码表示是0x8000000000000001 = (1000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0001b)
以-127为例,在32位机器上,其原码、反码和补码可表示为:
o 1000 0000 0000 0000 0000 0000 0111 1111b ; 原码
o 1111 1111 1111 1111 1111 1111 1000 0000b ; 反码: 在原码的基础上, 符号位不变, 剩下31位按位取反
o 1111 1111 1111 1111 1111 1111 1000 0001b ; 补码: 在反码的基础上, 给最低位加1
小结: 将常见的整数的取值范围牢记于心,有利于在实际的程序设计中根据需求快速地确定变量(或结构体成员)的基本数据类型,写出优质无错的代码。
- 对于占N个二进制位的有符号整数, 能表示的范围是[- (2^N-1 - 1), +((2^N-1 - 1)], N=8, 16, 32, 64, ... (因为符号位占了一位,所以是N-1)
- 对于占N个二进制位的无符号整数, 能表示的范围是[0, +((2^N - 1)], N=8, 16, 32, 64, ...
另外,在做算法设计的时候,将下面的表格(2的N次方)烂熟于心也有利于快速做出判断。 例如,一个将每个32位无符号整数映射为布尔值的hash表可以将一台计算机的内存填满。
2的N次方 | 准确值 | 近似值 | K/M/G/T...表示 |
7 | 128 | ||
8 | 256 | ||
10 | 1024 | 千(Thousand) | 1K |
16 | 65, 536 | 64K | |
20 | 1, 048, 576 | 百万(Million) | 1M |
30 | 1, 073, 741, 824 | 十亿(Billion) | 1G |
32 | 4, 294, 967, 296 | 4G | |
40 | 1, 099, 511, 627, 776 | 万亿(Trillion) | 1T |
1K : 2^10 : 千 1M : 2^20 : 百万 (Million) ; 千千 1G : 2^30 : 十亿 (Billion) ; 千百万 1T : 2^40 : 万亿 (Trillion); 千十亿 1E : 2^50 1Z : 2^60 256: 2^8 64K: 2^16 4G : 2^32
4Z : 2^64