|
在所有說明之前,給大家出一道題目:
int a=256;
printf("%d\n", sizeof(++a));
printf("%d\n", a);
那么到底打印的是多少呢?
應該是4和256,我想第一個答案大家應該已經沒有問題了,但是為什么在++a以后,a的數值還是沒有發生變化呢?因為sizeof()是一個運算符,在其中的所有的運算都是無效的,所以++a根本就沒有運行。
上面的一個例子提醒我們,雖然sizeof看這簡單,但是其中還是有很多的問題值得討論的,呵呵。
一、sizeof的概念 sizeof是C語言的一種單目操作符,如C語言的其他操作符++、--等。它并不是函數。sizeof操作符以字節形式給出了其操作數的存儲大小。操作數可以是一個表達式或括在括號內的類型名。操作數的存儲大小由操作數的類型決定。
二、sizeof的使用方法 1、用于數據類型
sizeof使用形式:sizeof(type)
數據類型必須用括號括住。如sizeof(int)。
2、用于變量
sizeof使用形式:sizeof(var_name)或sizeof var_name
變量名可以不用括號括住。如sizeof (var_name),sizeof var_name等都是正確形式。帶括號的用法更普遍,大多數程序員采用這種形式。
注意:sizeof操作符不能用于函數類型,不完全類型或位字段。不完全類型指具有未知存儲大小的數據類型,如未知存儲大小的數組類型、未知內容的結構或聯合類型、void類型等。
如sizeof(max)若此時變量max定義為int max(),sizeof(char_v) 若此時char_v定義為char char_v [MAX]且MAX未知,sizeof(void)都不是正確形式。
三、sizeof的結果 sizeof操作符的結果類型是size_t,它在頭文件
中typedef為unsigned int類型。該類型保證能容納實現所建立的最大對象的字節大小。
1、若操作數具有類型char、unsigned char或signed char,其結果等于1。
ANSI C正式規定字符類型為1字節。
2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long 、 float、double、long double類型的sizeof 在ANSI C中沒有具體規定,大小依賴于實現,一般可能分別為2、2、2、 2、 4、4、4、8、10。
3、當操作數是指針時,sizeof依賴于編譯器。例如Microsoft C/C++7.0中,near類指針字節數為2,far、huge類指針字節數為4。一般Unix的指針字節數為4。
4、當操作數具有數組類型時,其結果是數組的總字節數。
5、聯合類型操作數的sizeof是其最大字節成員的字節數。結構類型操作數的sizeof是這種類型對象的總字節數,包括任何墊補在內。
讓我們看如下結構:
struct {char b; double x;} a;
在某些機器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。
這是因為編譯器在考慮對齊問題時,在結構中插入空位以控制各成員對象的地址對齊。如double類型的結構成員x要放在被4整除的地址。
6、如果操作數是函數中的數組形參或函數類型的形參,sizeof給出其指針的大小。
四、sizeof與其他操作符的關系 sizeof的優先級為2級,比/、%等3級運算符優先級高。它可以與其他操作符一起組成表達式。如i*sizeof(int);其中i為int類型變量。
五、sizeof的主要用途 1、sizeof操作符的一個主要用途是與存儲分配和I/O系統那樣的例程進行通信。例如:
void *malloc(size_t size),
size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。
2、sizeof的另一個的主要用途是計算數組中元素的個數。例如:
void * memset(void * s,int c,sizeof(s))。
六、建議 由于操作數的字節數在實現時可能出現變化,建議在涉及到操作數字節大小時用sizeof來代替常量計算。
============================================================= 本文主要包括二個部分,第一部分重點介紹在VC中,怎么樣采用sizeof來求結構的大小,以及容易出現的問題,并給出解決問題的方法,第二部分總結出VC中sizeof的主要用法。
1、 sizeof應用在結構上的情況
請看下面的結構:
struct MyStruct
{
double dda1;
char dda;
int type
};
對結構MyStruct采用sizeof會出現什么結果呢?sizeof(MyStruct)為多少呢?也許你會這樣求:
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是當在VC中測試上面結構的大小時,你會發現sizeof(MyStruct)為16。你知道為什么在VC中會得出這樣一個結果嗎?
其實,這是VC對變量存儲的一個特殊處理。為了提高CPU的存儲速度,VC對一些變量的起始地址做了"對齊"處理。在默認情況下,VC規定各成員變量存放的起始地址相對于結構的起始地址的偏移量必須為該變量的類型所占用的字節數的倍數。下面列出常用類型的對齊方式(vc6.0,32位系統)。
類型 對齊方式(變量存放的起始地址相對于結構的起始地址的偏移量)
Char 偏移量必須為sizeof(char)即1的倍數
int 偏移量必須為sizeof(int)即4的倍數
float 偏移量必須為sizeof(float)即4的倍數
double 偏移量必須為sizeof(double)即8的倍數
Short 偏移量必須為sizeof(short)即2的倍數
各成員變量在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的字節VC會自動填充。同時VC為了確保結構的大小為結構的字節邊界數(即該結構中占用最大空間的類型所占用的字節數)的倍數,所以在為最后一個成員變量申請空間后,還會根據需要自動填充空缺的字節。
下面用前面的例子來說明VC到底怎么樣來存放結構的。
struct MyStruct
{
double dda1;
char dda;
int type
};
為上面的結構分配空間的時候,VC根據成員變量出現的順序和對齊方式,先為第一個成員dda1分配空間,其起始地址跟結構的起始地址相同(剛好偏移量0剛好為sizeof(double)的倍數),該成員變量占用sizeof(double)=8個字節;接下來為第二個成員dda分配空間,這時下一個可以分配的地址對于結構的起始地址的偏移量為8,是sizeof(char)的倍數,所以把dda存放在偏移量為8的地方滿足對齊方式,該成員變量占用 sizeof(char)=1個字節;接下來為第三個成員type分配空間,這時下一個可以分配的地址對于結構的起始地址的偏移量為9,不是 sizeof (int)=4的倍數,為了滿足對齊方式對偏移量的約束問題,VC自動填充3個字節(這三個字節沒有放什么東西),這時下一個可以分配的地址對于結構的起始地址的偏移量為12,剛好是sizeof(int)=4的倍數,所以把type存放在偏移量為12的地方,該成員變量占用sizeof (int)=4個字節;這時整個結構的成員變量已經都分配了空間,總的占用的空間大小為:8+1+3+4=16,剛好為結構的字節邊界數(即結構中占用最大空間的類型所占用的字節數sizeof(double)=8)的倍數,所以沒有空缺的字節需要填充。所以整個結構的大小為:sizeof (MyStruct)=8+1+ 3+4=16,其中有3個字節是VC自動填充的,沒有放任何有意義的東西。
下面再舉個例子,交換一下上面的MyStruct的成員變量的位置,使它變成下面的情況:
struct MyStruct
{
char dda;
double dda1;
int type
};
這個結構占用的空間為多大呢?在VC6.0環境下,可以得到sizeof(MyStruc)為24。結合上面提到的分配空間的一些原則,分析下VC怎么樣為上面的結構分配空間的。(簡單說明)
struct MyStruct
{
char dda;//偏移量為0,滿足對齊方式,dda占用1個字節;
double dda1;//下一個可用的地址的偏移量為1,不是sizeof(double)=8
//的倍數,需要補足7個字節才能使偏移量變為8(滿足對齊
//方式),因此VC自動填充7個字節,dda1存放在偏移量為8
//的地址上,它占用8個字節。
int type;//下一個可用的地址的偏移量為16,是sizeof(int)=4的倍
//數,滿足int的對齊方式,所以不需要VC自動填充,type存
//放在偏移量為16的地址上,它占用4個字節。
};//所有成員變量都分配了空間,空間總的大小為1+7+8+4=20,不是結構
//的節邊界數(即結構中占用最大空間的類型所占用的字節數sizeof
//(double)=8)的倍數,所以需要填充4個字節,以滿足結構的大小為
//sizeof(double)=8的倍數。
所以該結構總的大小為:sizeof(MyStruc)為1+7+8+4+4=24。其中總的有7+4=11個字節是VC自動填充的,沒有放任何有意義的東西。
VC對結構的存儲的特殊處理確實提高CPU存儲變量的速度,但是有時候也帶來了一些麻煩,我們也屏蔽掉變量默認的對齊方式,自己可以設定變量的對齊方式。
#pragma pack(n) VC 中提供了#pragma pack(n)來設定變量以n字節對齊方式。n字節對齊就是說變量存放的起始地址的偏移量有兩種情況:第一、如果n大于等于該變量所占用的字節數,那么偏移量必須滿足默認的對齊方式,第二、如果n小于該變量的類型所占用的字節數,那么偏移量為n的倍數,不用滿足默認的對齊方式。結構的總大小也有個約束條件,分下面兩種情況:如果n大于所有成員變量類型所占用的字節數,那么結構的總大小必須為占用空間最大的變量占用的空間數的倍數;
否則必須為n的倍數。下面舉例說明其用法。
#pragma pack(push) //保存對齊狀態
#pragma pack(4)//設定為4字節對齊
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢復對齊狀態
以上結構的大小為16,下面分析其存儲情況,首先為m1分配空間,其偏移量為0,滿足我們自己設定的對齊方式(4字節對齊),m1占用1個字節。接著開始為 m4分配空間,這時其偏移量為1,需要補足3個字節,這樣使偏移量滿足為n=4的倍數(因為sizeof(double)大于n),m4占用8個字節。接著為m3分配空間,這時其偏移量為12,滿足為4的倍數,m3占用4個字節。這時已經為所有成員變量分配了空間,共分配了16個字節,滿足為n的倍數。如果把上面的#pragma pack(4)改為#pragma pack(16),那么我們可以得到結構的大小為24。(請讀者自己分析)
2、 sizeof用法總結
在VC中,sizeof有著許多的用法,而且很容易引起一些錯誤。下面根據sizeof后面的參數對sizeof的用法做個總結。
A. 參數為數據類型或者為一般變量。例如sizeof(int),sizeof(long)等等。這種情況要注意的是不同系統系統或者不同編譯器得到的結果可能是不同的。例如int類型在16位系統中占2個字節,在32位系統中占4個字節。
B. 參數為數組或指針。下面舉例說明.
int a[50]; //sizeof(a)=4*50=200; 求數組所占的空間大小
int *a=new int[50];// sizeof(a)=4; a為一個指針,sizeof(a)是求指針
//的大小,在32位系統中,當然是占4個字節。
C. 參數為結構或類。Sizeof應用在類和結構的處理情況是相同的。但有兩點需要注意,第一、結構或者類中的靜態成員不對結構或者類的大小產生影響,因為靜態變量的存儲位置與結構或者類的實例地址無關。
第二、沒有成員變量的結構或類的大小為1,因為必須保證結構或類的每一
個實例在內存中都有唯一的地址。
下面舉例說明,
Class Test{int a;static double c};//sizeof(Test)=4.
Test *s;//sizeof(s)=4,s為一個指針。
Class test1{ };//sizeof(test1)=1;
D. 參數為其他。下面舉例說明。
int func(char s[5]);
{
cout<<sizeof(s);//這里將輸出4,本來s為一個數組,但由于做為函
//數的參數在傳遞的時候系統處理為一個指針,所
//以sizeof(s)實際上為求指針的大小。
return 1;
}
sizeof(func("1234"))=4//因為func的返回類型為int,所以相當于
//求sizeof(int).
以上為sizeof的基本用法,在實際的使用中要注意分析VC的分配變量的分配策略,這樣的話可以避免一些錯誤。
今天要完成一個項內容,運行另一個應用程序abc.exe,實現它的父進程是explorer.exe。
最開始的思路是獲得explorer.exe的句柄,用ShellExecute啟動abc.exe。但是用explorer.exe的句柄創建的進程的父進程依然是調用和進程,而不是傳入句柄的進程。
看來直接的不行,只能用間接的了。把運行abc.exe的代碼段寫到explorer.exe的內存里面去。然后讓explorer來運行這段代碼。
 static DWORD CALLBACK ThreadProc()...{
::ShellExecute(NULL,"open","abc.exe",NULL,NULL,SW_SHOW);
return TRUE;
}
但是現在就出現問題了,ShellExecute在shell32模塊里,還需要LoadLibrary和GetProcAddress。同時它也
用了兩個字符串常量,這些字串會出現在本進程的內存中,在explorer中運行代碼就會出錯,系統把它關掉。所以改用了WinExec來代替
ShellExecute,同時要把需要的字串和函數指針都寫到explorer的內存區里。
typedef UINT (WINAPI * WINEXEC)(LPCSTR,UINT);

 typedef struct tagTHREADDATA...{
TCHAR fileName[20];
WINEXEC pWinexec;
}THREADDATA, *LPTHREADDATA;

 static DWORD CALLBACK ThreadProc(LPTHREADDATA pData)...{
pData->pWinexec(pData->fileName,SW_SHOW);
return TRUE;
}
獲得explorer進程PID的方法
 DWORD getExplorerPID()...{
HWND startButtonHandle;
DWORD processID;
startButtonHandle = ::FindWindow (TEXT("Shell_TrayWnd"),NULL);
::GetWindowThreadProcessId( startButtonHandle, &processID );
return processID;
}
注入內存的過程:
user32Handle = ::GetModuleHandle(TEXT("kernel32"));
//得到kernel32模塊句柄
processHandle = ::OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,FALSE,getExplorerPID());
//用explorer的PID來打開進程,并得到創建線程和寫的權限。
dataAddr = ::VirtualAllocEx(processHandle,0,sizeof(THREADDATA),MEM_COMMIT,PAGE_EXECUTE_READWRITE);
//在explorer的內存內里申請一塊內存來存所用的數據
 THREADDATA data = ...{TEXT("a.exe"),(WINEXEC)GetProcAddress(user32Handle,"WinExec"),};
WriteProcessMemory(processHandle,dataAddr,&data,sizeof(THREADDATA),&byteWrited);
//把數據寫到申請的內存中
codeAddr = ::VirtualAllocEx(processHandle,0,sizeOfThreadProc,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
//申請代碼的內存區
WriteProcessMemory(processHandle,codeAddr,&ThreadProc,sizeOfThreadProc,&byteWrited);
//把代碼寫進去,這時我們己經把我們要用的代碼和數據都準備好了。
threadHandle = CreateRemoteThread(processHandle,NULL,0, LPTHREAD_START_ROUTINE)codeAddr,dataAddr,0,(LPDWORD)threadID);
//在explorer中創建一個線程,來執行啟動abc.exe的代碼。所需的數據都己經在explorer的內存塊中,所以不會出問題。
WaitForSingleObject(threadHandle, INFINITE);
VirtualFreeEx(processHandle,dataAddr,0,MEM_RELEASE);
VirtualFreeEx(processHandle,codeAddr,0,MEM_RELEASE);
CloseHandle(threadHandle);
CloseHandle(processHandle);
//等待執行完畢,釋放內存,關閉句柄。
這就完成了代碼的注入與執行。
行囊跟裝備是一樣的,也是占8個字節。自然在內存中的地址也是緊挨著的,可以自己到內存中的對應位置去查看。看到一大串的 FF FF FF FF 就是了。
行囊是有個數限制的,英雄也是人嘛,東西多了背不動。
一個英雄的行囊中最多可以放置32樣寶物,也就是說行囊總共占8×32=256個字節。
其后有一個字節用來保存行囊中寶物的個數,相當于一個校驗位。
除了寶物,魔法自然是玩家最為關注的。
寶物每個占8個字節,魔法這么重要卻只占1個字節,真是太不公平了。
不好意思跑題了,發表一下個人意見而已,管他占幾個字節,繼續繼續。
來到行囊對應的地址,顯示如下:
FF FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00
00 00 00 00 00 00 00 03 02 01
01
第一行的第一個 00 是行囊中的寶物個數
往后數92格也就是第七行的第七個字節開始就是英雄的魔法了。
魔法總共有70種,也就是總共占70個字節。
這70個字節依次對應的魔法分別是:
"召船術", "摧毀船只", "透視之眼", "透視大地", "偽裝大法",
"透視大氣", "飛行奇術", "凌波微步", "異次元之門", "時空之門",
"流沙陷阱", "埋設地雷", "大力神盾", "烈火魔墻", "地動山搖",
"魔法神箭", "霹靂寒冰", "霹靂閃電", "雷鳴炮彈", "連鎖閃電",
"寒冰魔環", "連珠火球", "地獄烈焰", "流星火雨", "死亡波紋",
"亡靈殺手", "末日審判", "護體神盾", "大氣神盾", "烈火神盾",
"御氣奇術", "御火奇術", "御水奇術", "御土奇術", "抗魔大法",
"驅魔大法", "魔法神鏡", "療傷", "轉世重生", "聚靈奇術",
"犧牲", "圣靈佑佐", "惡咒附身", "嗜血奇術", "百發百中",
"虛弱無力", "護體石膚", "毀滅之光", "祈禱", "歡欣鼓舞",
"悲痛欲絕", "幸運之神", "大難臨頭", "攻擊加速", "遲緩大法",
"屠戮成性", "孤注一擲", "泰坦之箭", "反戈一擊", "喪心病狂",
"蠱惑人心", "失憶大法", "雙目失明", "瞬間移動", "驅除障礙",
"鏡像大法", "召喚火元素", "召喚土元素", "召喚水元素", "召喚氣元素",
要使英雄學會某種魔法只需在對應的字節處將值改成 01 就OK了。
最后一行的 03 02 01 01 相信不說也都猜到了。
對了,就是英雄的四項基本技能了,很奇怪,既然是基本技能為什么放到最后面呢,呵呵,管他呢。
一個英雄在內存中總共就占1170個字節,到此為止關鍵的數據我們都已經知道在什么位置了,剩下的數據不知道也罷,當然如有興趣可以去研究一下,記得到時候別忘了跟我分享一下。
其實寫程序相對于到內存中去分析數據要簡單的多。
像這款游戲的數據都沒經過加密的,找起來也不費力,用來練手還是不錯的。
一個個字敲還真是挺累的~~~~~~~~
上回講到英雄的裝備。
先說一說寶物吧,每個寶物占8個字節,前面4個字節表示是什么寶物,后面4個字節是寶物的屬性
并不是每樣寶物都有屬性,相反,我就發現 魔法卷軸 用到了后面這4個字節,用來表示魔法卷軸上的魔法,其余寶物屬性均為-1 即 FF FF FF FF。
00 00 00 00 FF FF FF FF 表示魔法書
01 00 00 00 13 00 00 00 表示 連鎖閃電卷軸
02 00 00 00 FF FF FF FF 表示 神器
依次類推,分別為
"卷軸", "神器", "投石車", "弩車", "補給車",
"急救帳篷", "人馬戰斧", "黑魔劍", "狼人連枷", "惡魔之棒",
"火神劍", "泰坦之劍", "矮人王盾", "亡靈盾", "狼人王盾",
"狂魔盾", "邪盾",
"守護神之盾", "神獸之冠",
"骷髏冠",
"混沌之冠", "智慧之冠", "地獄王冠", "雷神之盔", "藤木甲",
"骨質胸甲", "大蛇神胸甲", "巨人戰甲", "黃金甲", "泰坦戰甲",
"神奇戰甲", "圣靴", "天使項鏈", "獅王盾", "先知劍",
"神諭之冠", "龍眼戒", "赤龍劍", "龍盾", "龍甲",
"龍骨脛甲", "龍翼袍", "龍牙項鏈", "龍牙冠", "龍眼指環",
"幸運三葉草", "預言卡", "幸運鳥", "勇氣胸章", "勇士胸章",
"勇士令", "窺鏡",
"望遠鏡", "亡靈護身符",
"吸血鬼披風",
"死神靴", "抗魔鏈", "抗魔披風", "抗魔靴", "樹精靈之弓",
"神獸之鬃", "天羽箭", "神目鳥", "火眼人", "真理徽章",
"政治家勛章", "禮儀之戒", "天使勛帶", "旅行者之戒", "騎士手套",
"海神項鏈", "熾天之翼", "魔力護符", "魔法護符", "魔力球",
"魔力項圈", "魔戒", "魔法披風", "氣靈球", "土靈球",
"火靈球", "水靈球", "禁魔披風", "禁錮之靈", "惡運沙漏",
"火系魔法書", "氣系魔法書", "水系魔法書", "土系魔法書", "水神靴",
"黃金弓", "永恒之球", "毀滅之球", "活力之戒", "生命之戒",
"活力圣瓶", "極速項鏈", "神行靴", "極速披風", "冷靜掛件",
"光明掛件", "神圣掛件", "生命掛件", "死神掛件", "自由掛件",
"電神掛件", "清醒掛件", "勇氣掛件", "水晶披風", "寶石戒指",
"水銀瓶", "礦石車", "硫磺指環", "木材車", "黃金囊",
"黃金袋", "黃金包", "天賜神足", "天賜神胯", "天賜神軀",
"天賜神臂", "天賜神首", "航海家之帽", "魔法師之帽", "戰爭枷鎖",
"禁魔球", "龍之血瓶", "末日之刃", "天使聯盟", "鬼王斗篷",
"神圣血瓶", "詛咒鎧甲", "天賜神兵", "龍王神力", "泰坦之箭",
"海洋之帽", "幻影神弓", "魔力源泉", "法師之戒", "豐收之角",
"潘多拉之盒",
英雄身上的寶物分兩種,裝備在身上的(以下稱裝備)和放在行囊中的(以下稱行囊)。
裝備能增加英雄的屬性(攻、防、力量、知識及其它輔助效果)
行囊在沒有裝備到英雄身上的時候是不會增加英雄的屬性的。
英雄身上的裝備總共為19格,每一格占8個字節表示所放置的寶物
依次為:
頭盔 —— 披肩 —— 項鏈 —— 右手 —— 左手 ——
軀體 —— 右腕 —— 左腕 —— 腳 —— 雜物1——
雜物2 —— 雜物3 —— 雜物4 —— 弩車 —— 帳篷 ——
補給車 —— 投石車 —— 魔法書 —— 雜物5
要修改的話在相應的位置添上相應的寶物即可。
裝備接下來的15個字節也是很有用的,
第一個字節不知道什么用處,后面14個字節分別表示英雄裝備對應的位置能否放置寶物
依次表示:
頭——披肩——項鏈——右手——左手——
軀體——手腕——腳——雜物——補給車——
弩車——帳篷——投石車——魔法書
如該位置為 00 表示可以放置寶物,如為 01 則表示不能放置寶物,裝備對應的位置上會出現一把鎖。
其中手腕和雜物比較特殊,
手腕由于有左手腕和右手腕,所以該位置可為 00 、01 、02三個值
雜物有五處,所以對應的位置可以是 00 、01 、02 、03 、04 、05六個值。
行囊就放到下回再說吧
忙了一天了,該好好休息休息了。
|