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

有问题并不可怕,需要我们静下心来一一去分析原因,寻根知底。

 

友情提示:

后续的几篇文章对其应用作了更新和封装,可以在专栏里查看。

如果感觉对你有帮助,请支持肯定下博主的努力!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值