计算机的内存由数以亿万计的位(bit)组成,每个位可以容纳值0和1。由于一个位所能表示的值得范围太有限,所以单独的位的用处不大,通常许多位组成一组作为一个存储单位,这样就可以存储范围较大的值。以下展示了现实机器中的一些内存位置:
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
|
|
|
|
|
|
|
|
这些位置的每一个都被称为字节(byte),每个字节都包含了存储一个字符所需要的位数。在很多现代的机器上,每个字节包含8个位,可以存储无符号值0至255,或者有符号值-128只127。实际内存中每个位置总是包含一些值。每个字节通过地址来标识,如上图中的数字所示。
为了存储更大的值,我们把两个或更多个字节合在一起作为一个更大的内存单位。例如,很多机器以字为单位存储整数,每个字一般由2或4个字节组成。下图所示内存位置与上图相同,但这次它以4个字节的字来表示。
100 |
|
|
| 104 |
|
|
|
|
|
尽管一个字(INT32)包含了4个字节,它仍然只有一个地址。至于它的地址是它最左边那个字节的位置还是最右边那个字节的位置,不同的机器有不同的规定。另一个需要注意的硬件事项是边界对齐(boundary alignment)。在要求边界对齐的机器上,整型值存储的起始位置只能是某些特定的字节,通常是2或4的倍数。但这些问题是硬件设计者的事情,它们很少影响C程序员。我们只对两件事情感兴趣:
1).内存中的每个位置由一个独一无二的地址标识。
2).内存中的每个位置都包含一个值。
在实际程序中我们经常根据需要借助强大的指针对一块内存进行操作,再按字节组合析取出所需数据,平时的程序中经常用到通用指针void*(LPVOID)的妙处就在于可以按照需要操作一块内存,以取所需值类型。
以下测试小程序向我们清晰的展示了三种字节析取情况:
#include <stdio.h> #include <windows.h> int main(void) { int i; BYTE byte[9] = {48, 49, 50, 51, 52, 53, 54, 55,0}; printf("每1个byte的16进制BYTE值:/n"); for(i = 0; i < 9; i++) { printf("byte[%d] = %x /n", i, byte[i]); } printf("----------------------------------/n"); printf("字符串byte[9]:/n"); BYTE *pBYTE = byte; printf("*pBYTE = %s /n", pBYTE); printf("----------------------------------/n"); printf("每2个byte组合而成的16进制INT16值:/n"); INT16 *pINT16 = (INT16*)pBYTE; for (i = 0; i < 4; i++) { INT16 i16 = *(pINT16 + i); // Debug printf("*(pINT16 + %d) = %x /n", i, *(pINT16 + i)); } printf("----------------------------------/n"); printf("每4个byte组合而成的16进制INT32值:/n"); INT32 *pINT32 = (INT32*)pBYTE; for (i = 0; i < 2; i++) { INT32 i32 = *(pINT32 + i); // Debug printf("*(pINT32 + %d) = %x /n", i, *(pINT32 + i)); } printf("----------------------------------/n"); return 0; }
说明:*(pINT16 + 0) = 3130而不是3031,这是因为Windows操作系统使用的是小序在前的存储方式(little endian),也即在起始地址处存放整数的低序号字节(低地址低字节)。关于字节的大小端问题,留待网络编程中进行探讨。
以下展示了两种强制类型转换:
// 多字节的截取(容易造成数据的丢失!)
int i1 = 0x12345678; // 小序存放顺序4byte:0x78,0x56,0x34,0x12
short s1 = (short)i1; // 析取2byte:0x5678
char c1 = (char)i1; // 析取1byte:0x78
// 短字节的扩展
short s2 = 0x5678; // 小序存放顺序2byte:0x78,0x56
int i2 = (int)s2; // 扩展2byte,高位补0:0x00005678
1byte=8bit,底层都是二进制位串进行移位实现相关操作。标准C++中的<bitset>提供了二进制位串操作接口。以下为打印单字节和通用数据类型二进制位串的程序示例。
typedef unsigned char uchar; // 枚举整数x二进制串中含有多少个1 int enum_filled_bits(int x) { int countx = 0; while (x) { countx++; x = x & (x - 1); } return countx; } // 打印单字节数的二进制位串 void binary_print_byte(uchar c) { for(int i = 0; i < 8; ++i) { if((c << i) & 0x80) // 左移 cout << '1'; else cout << '0'; } cout << ' '; } // 打印通用类型的二进制位串 template <class T> void binary_print_multibytes(T val) { void *f = &val; // 取地址 size_t sz = sizeof(T); uchar *pByte = new uchar[sz]; int i; for(i = 0; i < sz; i++) pByte[i] = *((uchar*)&f + i); #ifdef _BIG_ENDIAN for(i = 0; i != sz; i++) binary_print_byte(pByte[i]); #else // for windoze(Intel X86) for(i = sz; i != 0; i--) binary_print_byte(pByte[i-1]); #endif delete[] pByte; cout << endl; }
参考:
《Pointers in C》
《Pointers》
《字节那些事儿》