POINTER_64、POINTER_32定義成員及內存對齊理解

先看下下面這個結構體的定義

C++下面的定義:

//#pragma pack(4)
typedef struct _KERNEL_DATA // 按8個字節對齊的話
{
	unsigned long PortNumber; // 佔4個字節  
	union
	{
		unsigned long LongData;
		unsigned short ShortData;
		unsigned char CharData;
	}; //佔4個字節 
	
	unsigned long ulPhysicalAddr; // 佔8個字節,原本4個字節
	unsigned char* puchVirtualAddr;// 佔8個字節
	unsigned long* __ptr64 pulVirtualAddr;// 佔8個字節
	unsigned long ulNumberOfBytes;//佔8個字節,原本4個字節

}KERNEL_DATA, *PKERNEL_DATA;

int main()
{
	KERNEL_DATA data;
	data.PortNumber = 1;
	data.LongData = 2;
	data.ulPhysicalAddr = 0xFF000003;
	data.ulNumberOfBytes = 0x20212223;
	data.puchVirtualAddr = nullptr;
	data.pulVirtualAddr = nullptr;
	
	
	std::cout << "size of void* __ptr32 is " << sizeof(void* __ptr32) << std::endl;
	std::cout << "size of void* __ptr64 is " << sizeof(void* __ptr64) << std::endl;
}

C#對應的定義:

    [StructLayout(LayoutKind.Explicit, Size = 32)]
    public struct _KERNEL_DATA // 在32位系統上
    {
        [FieldOffset(0)]
	UInt32 PortNumber; // 
 
        [FieldOffset(4)]
        UInt32 LongData;
        [FieldOffset(4)]
        UInt16 ShortData;
        [FieldOffset(4)]
        Byte CharData;

	[FieldOffset(8)]
	UInt32 ulPhysicalAddr; // 
        [FieldOffset(12)]
	IntPtr puchVirtualAddr;// 
        [FieldOffset(20)]
	UIntPtr pulVirtualAddr;// 
        [FieldOffset(28)]
	UInt32 ulNumberOfBytes;//

    };

 

簡單說下這個問題背景:

     看到手機微信裏以前的大學同學,也是宿舍舍友,畢業後同事從事軟件開發行業。看到給我微信上發了這個問題,說C#調用C++這個結構體總是出錯。看了下粗略的問題,我也有較長時間沒怎麼用過C++了,但從我瞭解這個結構體的所佔字節數,一直認爲上面結構體sizeof()佔用了24個字節(在32位系統上),在64位系統上佔用32個字節。我老同學通過調試和對比,發現在64位系統上確實都是32字節,看起來應該沒啥問題。可以C#從C++中獲取到的數據總是不對,或者直接程序崩潰。

那如何去分析這個問題呢?

     當結構體都確定下來,但對於結構體定義的變量爲什麼從表面看起來都是對的,但實際卻在使用中數據總是不對,甚至程序運行不對呢?

     我第一個時間想到是的看C++上傳數據的內存分佈(C#中查看方式也是一樣的),下面簡說明如何在調試下查看變量的內存存儲的具體形式和數值。

調試模式下打開內存查看示圖:

如下圖所示:

 

從上圖可以看出在同學所說的在64位系統上是32個字節(按8字節對齊)。現在我們一個個來看內存與變量對應位置。

typedef struct _KERNEL_DATA // 在32位系統上
{
    unsigned long PortNumber; // 佔4個字節  
    union
    {
        unsigned long LongData;
        unsigned short ShortData;
        unsigned char CharData;
    }; //佔4個字節
    
    unsigned long ulPhysicalAddr; // 佔4個字節
    unsigned char* puchVirtualAddr;// 佔4個字節
    unsigned long* __ptr64 pulVirtualAddr;// 佔8個字節
    unsigned long ulNumberOfBytes;//佔8個字節,原本4個字節

}KERNEL_DATA, *PKERNEL_DATA;

現在我們看出原因了,發現unsigned long* __ptr64 pulVirtualAddr;// 佔8個字節,常理unsigned long* 是佔4個字節的。看來__ptr64起了指定佔字節數的作用。關於__ptr64的說明參見:https://docs.microsoft.com/zh-cn/previous-versions/aa985900(v=vs.110)?redirectedfrom=MSDN

文章中提及到

這個時候我們基本上發現了原因,分析並理解了問題,並解決了問題。

 

接下來我們把C#結構體偏移做下修改:

namespace WindowsFormsApplication3
{
    [StructLayout(LayoutKind.Explicit, Size = 32)]
    public struct _KERNEL_DATA // 在32位系統上
    {
        [FieldOffset(0)]
	UInt32 PortNumber; // 
 
        [FieldOffset(4)]
        UInt32 LongData;
        [FieldOffset(4)]
        UInt16 ShortData;
        [FieldOffset(4)]
        Byte CharData;

	[FieldOffset(8)]
        UInt32 ulPhysicalAddr; // 佔4個字節
        [FieldOffset(12)]
	IntPtr puchVirtualAddr;// 佔4個字節
        [FieldOffset(16)]
	UIntPtr pulVirtualAddr;// 佔8個字節
        [FieldOffset(24)]
	UInt32 ulNumberOfBytes;//佔4個字節

    };
}

這裏就對齊簡單說明下:

這個時候我們再看下內存分佈:

內存的對齊是根據其自身的所佔字節數去取地址,比方unsigned long* __ptr64 pulVirtualAddr;// 佔8個字節,那它地址位必須是8*N(N爲非負整數,比如0,16,24,32,...)。所有上圖unsigned long ulNumberOfBytes;起始地址爲【16,20),由於它下一個成員變量爲8個字節,所地需在其後補齊4個字節。

這裏如果覺得unsigned long ulNumberOfBytes;本身4個字節,卻使用了8個字節。可以採用結構體字節對齊,指定對齊的字節數。

採用#pragma pack(4),這裏就不展開說了。

 

到這裏基本都解釋清楚了,這個在說明一點:

__ptr32,__ptr64 就是C++類型,和int類型一樣,是個關鍵字。

basetsd.h 中有其相關的信息

#if !defined(_MAC) && (defined(_M_MRX000) || defined(_M_AMD64) || defined(_M_IA64)) && (_MSC_VER >= 1100) && !(defined(MIDL_PASS) || defined(RC_INVOKED))
 #define POINTER_64 __ptr64
 typedef unsigned __int64 POINTER_64_INT;
 #if defined(_WIN64)
  #define POINTER_32 __ptr32
 #else
  #define POINTER_32
 #endif
#else
 #if defined(_MAC) && defined(_MAC_INT_64)
  #define POINTER_64 __ptr64
  typedef unsigned __int64 POINTER_64_INT;
 #else
  #if (_MSC_VER >= 1300) && !(defined(MIDL_PASS) || defined(RC_INVOKED))
   #define POINTER_64 __ptr64
  #else
   #define POINTER_64
  #endif
  typedef unsigned long POINTER_64_INT;
 #endif
 #define POINTER_32
#endif

有問題並不可怕,需要我們靜下心來一一去分析原因,尋根知底。

 

友情提示:

後續的幾篇文章對其應用作了更新和封裝,可以在專欄裏查看。

如果感覺對你有幫助,請支持肯定下博主的努力!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章