如何判断任一内存地址是堆上的还是栈上,若是堆上的返回该内存长度

2024-04-26 13:32

本文主要是介绍如何判断任一内存地址是堆上的还是栈上,若是堆上的返回该内存长度,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

很早以前就想过这个问题:看到一个内存地址,如果判断这个地址是不是堆上的,若是,new出来的长度是多少字节?深入了解了new和delete的源码后,终于把这个方法找到了,在此分享给大家。

每个进程启动时候会有4G的虚拟内存,分为堆区、栈区、静态存储区、常量区、代码段、数据段和内核空间,而对每个线程,默认分配给其1MB空间。计算机一般采用的是小端模式存储,栈是向低地址生长,堆是向高地址生长。处于Ring3的应用程序是不可以访问内核空间(2G-64KB)的。

下面说一下new,新分配的内存前48字节为结构体_CrtMemBlockHeader(最后一个成员gap[4]=fdfdfdfd),后4字节为fdfdfdfd;这52字节时new的内存前后标示,若被破坏,则释放时会检查报错。于是可用下买呢方法来确定该地址是否在堆上,并计算出其长度:

//<<判断任一内存地址是否是堆内存,若是则返回内存长度,若不是返回-1

inline int CalcMemLen(void *pSrc, longfindRange = 10000)

{

         //crt头文件不能包含,这里定义下

         typedefstruct _CrtMemBlockHeader

         {

                   struct_CrtMemBlockHeader * pBlockHeaderNext;

                   struct_CrtMemBlockHeader * pBlockHeaderPrev;

                   char*                      szFileName;

                   int                         nLine;

#if defined (_WIN64)|| defined (WIN64)

                   int                         nBlockUse;

                   size_t                      nDataSize;

#else  /* _WIN64 */

                   size_t                      nDataSize;

                   int                         nBlockUse;

#endif  /* _WIN64 */

                   long                        lRequest;

                   unsignedchar               gap[4];

         } _CrtMemBlockHeader;

 

#define pbData(pblock) ((unsignedchar *)((_CrtMemBlockHeader *)pblock + 1))

#define pHdr(pbData) (((_CrtMemBlockHeader *)pbData)-1)

#define _BLOCK_TYPE_IS_VALID(use) (_BLOCK_TYPE(use) ==_CLIENT_BLOCK || \

         (use) == _NORMAL_BLOCK || \

         _BLOCK_TYPE(use) == _CRT_BLOCK    || \

         (use) == _IGNORE_BLOCK)

 

         static unsigned char_bNoMansLandFill = 0xFD;

         unsignedchar *pc = (unsignedchar*)pSrc; //char*的话, -3不等于fd啊?

         _CrtMemBlockHeader *pHead = NULL;

         unsignedchar *pStart=NULL, *pEnd=NULL;

         inti=0;

 

         //找头部标示

         for(i=0; i<findRange; i++)

         {

                   if(pc[-i-4]==_bNoMansLandFill && pc[-i-3]==_bNoMansLandFill &&pc[-i-2]==_bNoMansLandFill && pc[-i-1]==_bNoMansLandFill)

                   {

                            pStart =&pc[-i];

                            pHead =pHdr(pStart);

                            break;

                   }

         }

 

         if(i>=findRange || !_BLOCK_TYPE_IS_VALID(pHead->nBlockUse))

         {//指定范围内没有找到头部标示;或者找到了但不是有效的

                   return-1;

         }

 

         //找尾部标示

         for(i=0; i<findRange; i++)

         {

                   if(pc[i]==0xfd && pc[i+1]==0xfd && pc[i+2]==0xfd &&pc[i+3]==0xfd && pc[i+4] !=0xfd)//0xfd fd fdfd

                   {

                            pEnd = pc+i;

                            break;

                   }

         }

         if(i>=findRange)

         {//指定范围内没有找到尾部标示;

                   return-1;

         }

 

         intlength = int(pEnd - pStart);

         if(pHead->nDataSize == length)

         {

                   if(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse))

                   {

                            if (_CrtIsValidHeapPointer(pStart))

                            {

                                     return pHead->nDataSize;

                            }

                   }

         }

 

         return-1;

}


下面在win64平台上测试一下:

class String

{

public:

         /*explicit*/String(const char*str=NULL);//普通构造函数

         String(constString &str);//拷贝构造函数

         String & operator=(const String &str);//赋值函数

         ~String();//析构函数

private:

public:

         char*m_data;//用于保存字符串

         char  m_c;

         unsignedshort  m_i;

};

ps的地址为0x208ee0,起始0-7个字节存的是指针m_data的值,win64指针是8字节,小端模式倒过来就是0x0000000000208f60;第8个字节存储m_c;由于sizeof(usingned short)=2,故m_i要对齐到2的整数倍也就是10了,故第10-11字节8eea-8eeb存储m_i;最后整个类按照pragma pack指定的值8min{类内最长元素(m_data8字节),机器cpu长度(8字节)}对齐,那就是把12补齐到16了,故sizeof(String)==16;如果类前加一句#pragma pack(push,1),则sizeof(String)==11;

由于构造函数中m_data=new char[1],故重新在堆上分配了内存,地址8f30-8f5f为其头部标示,8f61-8f64为其尾部标示。

测试结果:

                int *aa = new int[10];
int allLen = CalcMemLen(aa);//40

                int bb[1];
bb[2] = 9999;
allLen = CalcMemLen(bb);//-1

String *ps = new String;
allLen = CalcMemLen(ps);//16
  allLen = CalcMemLen(&ps->m_i);//16
allLen = CalcMemLen(&ps->m_data);//16
  allLen = CalcMemLen(ps->m_data);//1

char *pAppPath = NULL;
_get_pgmptr(&pAppPath);
allLen = CalcMemLen(pAppPath);//-1
delete pAppPath;//非堆内存,不可释放

2.变量一定要初始化,尤其是类和结构体的成员变量。一般地,对于win32上int变量i,在应用程序首次加载时:Debug模式下初始值为0xcccccccc,Release模式下初始值为0;但随着程序的运行,栈上的空间不断被该进程申请释放,下一次进入时,i可能是其他未预期的值。对于栈上的内存,无法计算其长度,数组越界访问了一般也不会报错(不过尽量保证不要越界,值得提醒一下,如果上面的bb定义在类String内,那么越界后就很容易破坏掉ps指针的边界,导致释放时报错)。




















这篇关于如何判断任一内存地址是堆上的还是栈上,若是堆上的返回该内存长度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/937711

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

Go语言中nil判断的注意事项(最新推荐)

《Go语言中nil判断的注意事项(最新推荐)》本文给大家介绍Go语言中nil判断的注意事项,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.接口变量的特殊行为2.nil的合法类型3.nil值的实用行为4.自定义类型与nil5.反射判断nil6.函数返回的

MySQL 获取字符串长度及注意事项

《MySQL获取字符串长度及注意事项》本文通过实例代码给大家介绍MySQL获取字符串长度及注意事项,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 获取字符串长度详解 核心长度函数对比⚠️ 六大关键注意事项1. 字符编码决定字节长度2

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

SpringBoot中使用Flux实现流式返回的方法小结

《SpringBoot中使用Flux实现流式返回的方法小结》文章介绍流式返回(StreamingResponse)在SpringBoot中通过Flux实现,优势包括提升用户体验、降低内存消耗、支持长连... 目录背景流式返回的核心概念与优势1. 提升用户体验2. 降低内存消耗3. 支持长连接与实时通信在Sp

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

python判断文件是否存在常用的几种方式

《python判断文件是否存在常用的几种方式》在Python中我们在读写文件之前,首先要做的事情就是判断文件是否存在,否则很容易发生错误的情况,:本文主要介绍python判断文件是否存在常用的几种... 目录1. 使用 os.path.exists()2. 使用 os.path.isfile()3. 使用

统一返回JsonResult踩坑的记录

《统一返回JsonResult踩坑的记录》:本文主要介绍统一返回JsonResult踩坑的记录,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录统一返回jsonResult踩坑定义了一个统一返回类在使用时,JsonResult没有get/set方法时响应总结统一返回

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir