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

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

相关文章

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变

从基础到进阶详解Python条件判断的实用指南

《从基础到进阶详解Python条件判断的实用指南》本文将通过15个实战案例,带你大家掌握条件判断的核心技巧,并从基础语法到高级应用一网打尽,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录​引言:条件判断为何如此重要一、基础语法:三行代码构建决策系统二、多条件分支:elif的魔法三、

Django HTTPResponse响应体中返回openpyxl生成的文件过程

《DjangoHTTPResponse响应体中返回openpyxl生成的文件过程》Django返回文件流时需通过Content-Disposition头指定编码后的文件名,使用openpyxl的sa... 目录Django返回文件流时使用指定文件名Django HTTPResponse响应体中返回openp

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

java内存泄漏排查过程及解决

《java内存泄漏排查过程及解决》公司某服务内存持续增长,疑似内存泄漏,未触发OOM,排查方法包括检查JVM配置、分析GC执行状态、导出堆内存快照并用IDEAProfiler工具定位大对象及代码... 目录内存泄漏内存问题排查1.查看JVM内存配置2.分析gc是否正常执行3.导出 dump 各种工具分析4.

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

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

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

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

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