C|按位、位元組級、字長級資料處理

構成電子計算機基本邏輯單元的電晶體可以表示兩種狀態,用二進位制描述就是0或1,稱為一個二進位制位(bit),多個電晶體的組合可以實現邏輯電路,資料和指令都可以以二進位制的序列來表示。

通常以8個二進位制位組成一個位元組(byte),以位元組為單位進行編址。

CPU在單位時間內能一次處理的二進位制數的位數叫字長(word size)。字長指明指標資料的標稱大小(nominal size)。對於一個字長為n位的機器而言,虛擬地址的範圍為0-2^n-1,程式最多可以訪問2^n個位元組。如32位機器的虛擬地址範圍為0x~0xFFFFFFFF。

32位CPU表示該CPU處理的字長為32位,即一次處理4個位元組。32位作業系統表示支援32位的CPU。

64位CPU —指的是該CPU處理的字長為64位,即一次處理8個位元組。64位作業系統表示支援64位的CPU,作業系統通常向後相容,所以也支援32位作業系統。

使用PAE36技術的32位CPU是36根地址線,使用PAE40技術的Intel x86-64 CPU是40根地址線,使用PAE52技術的AMD x86-64 CPU是52根地址線。

C語言支援位級(bitwise)操作,其資料型別型別有一個位元組長度的char型別,一個字長的int型別,指標本身的儲存也使用一個字長。

1 位級(bitwise)處理

C語言支援按位與、或、非操作,也支援移位操作。位操作的一個顯著用途就是節省儲存空間。

如在公曆轉農曆的程式中,可以使用一個unsigned int來儲存一個年份中的諸多資訊:

如2019年的資訊用0x0A9345表示,其二進位制位為0000 1010 1001 00110 10 00101。

(16進位制解析,每一個16進位制位(0-f)是4個二進位制位:0000-1111)

20-23位,其十進位制值表示閏月月份,值為0 表示無閏月(第23位代表最高位,最左邊)

7到19位,分別代表農曆每月(在閏年有13個月)的大小,每一位代表一個月份。

(1表示大月為30天,0表示小月29天)

5到6位,其十進位制值表示春節所在公曆月份(此處是2月)

0到4位,其十進位制值表示春節所在公曆日期(此處是5日)

解析出不同的位組便可以得到該年不同的資訊。

不同的年份可以儲存到一個數組中:

unsigned int LunarCalendarTable[199] ={0x069349,0x7729BD,0x06AA51,0x0AD546,0x54DABA,0x04B64E,0x0A5743,0x452738,0x0D264A,0x8E933E,/*2081-2090*/0x0D5252,0x0DAA47,0x66B53B,0x056D4F,0x04AE45,0x4A4EB9,0x0A4D4C,0x0D1541,0x2D92B5 /*2091-2099*/};

點陣數字和字元資訊也可以用字元陣列儲存和處理:

/*輸出點陣數字:8個char即可儲存64個位的資料,例如3:a[3]({0x00,0x1e,0x30,0x30,0x1c,0x30,0x30,0x1e}, //3包括有8個十六進位制的數,每行一個十六進位制數,並且換成二進位制的表示,會是什麼樣的呢?00000000 //0x0000011110 //0x1e00110000 //0x3000110000 //0x3000011100 //0x1c00110000 //0x3000110000 //0x3000011110 //0x1e請看1出現的地方,可以藉著滑鼠按1出現的軌跡跟著劃一劃,不就是 數字3字型的輪廓嗎?只不過,耳朵狀的3是反著的(這自有道理,看完程式1自會明白)。————————————————*/#include using namespace std;char a[10][8]={ {0x00,0x18,0x24,0x24,0x24,0x24,0x24,0x18}, //0 {0x00,0x18,0x1c,0x18,0x18,0x18,0x18,0x18}, //1 {0x00,0x1e,0x30,0x30,0x1c,0x06,0x06,0x3e}, //2 {0x00,0x1e,0x30,0x30,0x1c,0x30,0x30,0x1e}, //3 {0x00,0x30,0x38,0x34,0x32,0x3e,0x30,0x30}, //4 {0x00,0x1e,0x02,0x1e,0x30,0x30,0x30,0x1e}, //5 {0x00,0x1c,0x06,0x1e,0x36,0x36,0x36,0x1c}, //6 {0x00,0x3f,0x30,0x18,0x18,0x0c,0x0c,0x0c}, //7 {0x00,0x1c,0x36,0x36,0x1c,0x36,0x36,0x1c}, //8 {0x00,0x1c,0x36,0x36,0x36,0x3c,0x30,0x1c}, //9};int main(){ int n=0,i,j,k,m,x; cout<<“請輸入需要顯示的數字:”; int c[8]; cin>>n; for(k=0; n&&k<8; k++) //c陣列將分離出n中的各位數,不過是倒著的,例n=123,c中儲存3 2 1 { c[k]=n%10; n/=10; } //迴圈結束,將由k記住n是幾位數,此處限最多8位數 for(i=0; i<8; i++) //一共要顯示8行,不是依次顯示k個數字,而是依次顯示k個數字中對應的每一行 { for(m=k-1; m>=0; m——) //要顯示n=123, c中是倒著儲存各位數的,所以m由大到小 { x=a[c[m]][i]; //現在要顯示的數字是c[m],所以取a陣列中的第c[m]行,第i列資料 for(j=0; j<8; j++) { if(x%2) cout<<‘*’; else cout<<‘ ’; x=x/2; } } cout<

浮點編碼可以透過位域來解析:

#include void floatNumber_1(){ struct FF{ // 小端模式模擬double型別編碼 unsigned l:32; // 剩下的小數位 unsigned m:15; // 剩下的小數位 unsigned k:5; // 取5位小數 unsigned j:11; // 階碼 unsigned i:1; // 符號位 }; union UN { double dd; FF ff; }; UN un; un。dd = -15。75; // -1111。11 printf(“%d\n”,un。ff。i); // 1 printf(“%d\n”,un。ff。j); // 1023+3 printf(“%d\n”,un。ff。k); // 31 也就是二進位制的11111}void floatNumber_2(){ struct FF{ // 小端模式模擬double型別編碼 unsigned l:32; // 剩下的小數位 unsigned m:15; // 剩下的小數位 unsigned k:5; // 取5位小數 unsigned j:11; // 階碼 unsigned i:1; // 符號位 }; union UN { double dd; FF ff; }; UN un; un。ff。i = 1; un。ff。j = 1023+3; un。ff。k = 31; // 二進位制的11111 un。ff。m = 0; un。ff。l = 0; printf(“%。2lf\n”,un。dd); //un。dd = -15。75;// -1111。11}int main(){ floatNumber_1(); floatNumber_2(); while(1); return 0;}/*1102631-15。75*/

位域、共用體和可以解析漢字的GBK或GB2312編碼:

void cngb(){ union{ struct { unsigned int i:4; unsigned int j:4; unsigned int k:4; unsigned int L:4; unsigned int m:4; unsigned int n:4; }; char hanzi[3]; }hz; fflush(stdin); puts(“查詢gb2312碼,請輸入一個漢字:”); gets(hz。hanzi); //strcpy(hz。hanzi,“中”); printf(“%X%X%X%X\n”,hz。j,hz。i,hz。L,hz。k);}

2 位元組級(byte)處理

典型型別:char,char的長度是一個位元組。

定義了一個宏:CHAR_BIT。

typedef unsigned char byte;

顯示double的位元組編碼:

void showBytes(unsigned char* start, int len){ for(int i=0;i=0;i——) printf(“ %。2x”,start[i]); printf(“\n”); }}

memmove()函式實現:

memmove()由src所指定的記憶體區域賦值count個字元到dst所指定的記憶體區域。 src和dst所指記憶體區域可以重疊,但複製後src的內容會被更改。函式返回指向dst的指標。

void * my_memmove(void * dst,const void * src,int count){ void * ret = dst; if(dst <= src || (char *)dst >= ((char *)src + count)) { while(count——) { *(char *)dst = *(char *)src; dst = (char *)dst + 1; src = (char *)src + 1; } } else { dst = (char *)dst + count - 1; src = (char *)src + count - 1; while(count——) { *(char *)dst = *(char *)src; dst = (char *)dst - 1; src = (char *)src - 1; } } return(ret);}int main(){ char a[12]; puts((char *)my_memmove(a,“ammana_babi”,16)); system(“pause”); return 0;}

二進位制檔案瀏覽:

#include #include#include #includeusing namespace std; int main( ){ char c[16]; char f[100]; cout<<“請輸入檔名:”; cin>>f; ifstream infile(f,ios::in|ios::binary); if(!infile) { cerr<<“open error!”; exit(1); } while(!infile。eof()) { infile。read(c,16); if(!infile。eof()) { for(int i=0; i<16; ++i) cout<

位級與位元組級結合處理的例項:

考慮用一個short型別儲存一個有效日期(年份取末兩位):

/* 考慮用一個short型別儲存一個有效日期(年份取末兩位):Year(0-99) 7 bitsMonth(1-12)4 bitsDay(l-31) 5 bits如2021/11/22 1234567890123456 00000000000000000000000000010101 // year左移9位留下7位有效位 0000000000001011 // Month左移5位留給Day 0000000000010110*/// 向整數中壓縮資料 #include #include using namespace std;unsigned short dateShort(short year,short mon,short day){ unsigned short date; date = (year << 16-7) | (mon << 5) | day; return date;}void datePrint(unsigned short date){ struct Date{ unsigned day:5; unsigned mon:4; unsigned year:7; }; Date *d = (Date*)&date; printf(“20%d/%d/%d”,d->year,d->mon,d->day);}int main(){ unsigned short date = dateShort(21,11,22); datePrint(date); // 2021/12/22 getchar(); return 0;}

按16進位制顯示資料:

#include void hexPrint(int n){ if(n==0) printf(“00 ”); char str[5] = {0}; int len = 0; while(n) { int m = n%16; n /= 16; if(m<10) str[len++] = m + ‘0’; else str[len++] = m + ‘A’-10; } ——len; if(len%2==0) printf(“0”); while(len>=0) { printf(“%c”,str[len——]); if(len%2) printf(“ ”); }}hexPrint2(int n){ char str[5] = {0}; sprintf(str,“%X”,n); printf(“%s ”,str);}void bitsPrint(void *type,unsigned size){ unsigned char*p = (unsigned char*)type; int endian = 1; if(*(char*)&endian) printf(“小端位元組序:”); for(unsigned i=0;i

3 字級(word)處理

典型型別:int,int的長度是一個字長,32位CPU或作業系統是4個位元組,64位是8個位元組。

typedef unsigned int word;

3.1 暫存器的長度是一個字長

當讀寫double資料型別時,需要兩條mov指令:

10: double dd = 15。751;00401044 mov dword ptr [ebp-18h],126E978Dh0040104B mov dword ptr [ebp-14h],402F8083h

當讀寫一個字長或以下的資料時,只需要一個暫存器,一條mov指令。

同樣的,當返回值是一個字長或以下的資料時,可用暫存器返回。如果是double,則用浮點棧返回,如果是複合型別,則需要壓入一個儲存返回值的起始地址,將返回值返回到這個起始地址標識的被調函式的棧幀空間。

3。2 指標的標度是一個字長

printf(“%d\n”,sizeof(void*)); // 4,32位系統

3.3 棧按一個字長對齊

其根源還是在於暫存器的長度是一個字長,一次訪問一個字長的記憶體空間,如果不對齊,有可能就需要更多次的訪問,適當的浪費一點記憶體空間來換取效率(以空間換時間)是可取的。

#include void bufferOverflow(){ char ch = ‘a’; // 棧對齊為4個位元組 int base = 0; char buf[5] = {0}; // 棧對齊為8個位元組 puts(“輸入你構造的字串,模擬緩衝區溢位:”); gets(buf); if(base==0x64636261){ puts(“緩衝區溢位改寫了鄰近區記憶體!”); }}int main(){ bufferOverflow(); // 輸出12345678abcd會執行puts(),678用於棧對齊, // abcd給到了base,‘\0’給到了ch return 0;}/*output:輸入你構造的字串,模擬緩衝區溢位:12345678abcd緩衝區溢位改寫了鄰近區記憶體!*/

看函式的棧幀:

C|按位、位元組級、字長級資料處理

棧幀圖示:

C|按位、位元組級、字長級資料處理

如果輸入超過15個字元(其中有‘\0’),則會破壞ebp,引發執行錯誤。

結構體也需要同樣的對齊(包括成員的對齊及整體的對齊):

#include struct Align{ char ch; int base; char buf[5];};int main(){ Align align = {‘a’,1,“abcd”}; getchar(); return 0;}

函式記憶體映像:

C|按位、位元組級、字長級資料處理

-End-