構成電子計算機基本邏輯單元的晶體管可以表示兩種狀态,用二進制描述就是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 //0x00
00011110 //0x1e
00110000 //0x30
00110000 //0x30
00011100 //0x1c
00110000 //0x30
00110000 //0x30
00011110 //0x1e
請看1出現的地方,可以借着鼠标按1出現的軌迹跟着劃一劃,不就是 數字3字型的輪廓嗎?
隻不過,耳朵狀的3是反着的(這自有道理,看完程序1自會明白)。
————————————————
*/
#include <iostream>
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;
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<<endl;
}
while(1);
return 0;
}
/*
請輸入需要顯示的數字:68
*** ***
** ** **
**** ** **
** ** ***
** ** ** **
** ** ** **
*** ***
*/
浮點編碼可以通過位域來解析:
#include <stdio.h>
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;
}
/*
1
1026
31
-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);
}
典型類型:char,char的長度是一個字節。
<limits.h>定義了一個宏:CHAR_BIT。
typedef unsigned char byte;
顯示double的字節編碼:
void showBytes(unsigned char* start, int len)
{
for(int i=0;i<len;i )
printf(" %.2x",start[i]);
printf("\n");
}
void showDoubleByte(double x)
{
showBytes((unsigned char*)&x,sizeof(double));
}
void showBytesByBig(unsigned char* start, int len)
{
unsigned int i = 0x00000001;
unsigned char* c = (unsigned char*)&i;
if(*c==1)// 小端返回true,大端返回0
{
for(int i=len-1;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<iostream>
#include<iomanip>
#include <fstream>
#include<cstdlib>
using 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<<setfill('0')<<setw(2)<<hex<<int((unsigned char)(c[i]))<<" ";
cout<<'\t';
for(int i=0; i<16; i)
cout<<(c[i]?c[i]:'.');
cout<<endl;
}
}
return 0;
}
位級與字節級結合處理的實例:
考慮用一個short類型存儲一個有效日期(年份取末兩位):
/*
考慮用一個short類型存儲一個有效日期(年份取末兩位):
Year(0-99) 7 bits
Month(1-12)4 bits
Day(l-31) 5 bits
如2021/11/22
1234567890123456
0000000000000000
0000000000010101 // year左移9位留下7位有效位
0000000000001011 // Month左移5位留給Day
0000000000010110
*/
// 向整數中壓縮數據
#include <iostream>
#include <iomanip>
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 <stdio.h>
void hexPrint(int n)
{
if(n==0)
printf("00 ");
char str[5] = {0};
int len = 0;
while(n)
{
int m = n;
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<size;i )
//printf("%d ",*p );
hexPrint(*p );
//hexPrint2(*p );
printf("\n");
}
int main()
{
int a = -123456789;
bitsPrint((void*)&a,sizeof a); // 小端字節序:EB 32 A4 F8
double b = -15.75; //
bitsPrint((void*)&b,sizeof b); // 小端字節序:00 00 00 00 00 80 2F C0
getchar();
return 0;
}
典型類型: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],126E978Dh
0040104B mov dword ptr [ebp-14h],402F8083h
當讀寫一個字長或以下的數據時,隻需要一個寄存器,一條mov指令。
同樣的,當返回值是一個字長或以下的數據時,可用寄存器返回。如果是double,則用浮點棧返回,如果是複合類型,則需要壓入一個存儲返回值的起始地址,将返回值返回到這個起始地址标識的被調函數的棧幀空間。
3.2 指針的标度是一個字長
printf("%d\n",sizeof(void*)); // 4,32位系統
3.3 棧按一個字長對齊
其根源還是在于寄存器的長度是一個字長,一次訪問一個字長的内存空間,如果不對齊,有可能就需要更多次的訪問,适當的浪費一點内存空間來換取效率(以空間換時間)是可取的。
#include <stdio.h>
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
緩沖區溢出改寫了鄰近區内存!
*/
看函數的棧幀:
棧幀圖示:
如果輸入超過15個字符(其中有'\0'),則會破壞ebp,引發運行錯誤。
結構體也需要同樣的對齊(包括成員的對齊及整體的對齊):
#include <stdio.h>
struct Align{
char ch;
int base;
char buf[5];
};
int main()
{
Align align = {'a',1,"abcd"};
getchar();
return 0;
}
函數内存映像:
-End-
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!