请教下动态内存分配

job82824 2010-04-29 11:44:50
我想知道VirtualAlloc的参数的含义,能讲得细一点儿吗?

比如我需要分配一些不同的结构体数组空间,这些数组长度较为庞大(length<=5000),但是又想让这些空间连起来,尽可能省地方,怎么弄?

我看到别人的代码里面有这样的语句:



//创建
hUserBuf = VirtualAlloc(NULL, dwUserBufferSize,MEM_COMMIT, PAGE_READWRITE);
//销毁
VirtualFree( hUserBuf, dwUserBufferSize, MEM_DECOMMIT );
VirtualFree( hUserBuf, 0, MEM_RELEASE);


但是问题是这是分配一块数组呀,而我想分配的是多个数组,于是就不知道怎么抄了:)。

不知道还能这样照抄吗?是不是要点儿改动呢?还有就是VirtualAlloc的参数又该设置成什么呢?如果想尽可能节省空间的话?

如果像下面这样写会不会分配的内存成了很多碎片了呢?能不能把它们连起来哈:)还有释放该怎么写代码呢?

hUserBuf1 = VirtualAlloc(NULL, dwUserBufferSize1,MEM_COMMIT, PAGE_READWRITE);
hUserBuf2 = VirtualAlloc(NULL, dwUserBufferSize2,MEM_COMMIT, PAGE_READWRITE);
...

如果有满意答案的话本人一定再加100分!
...全文
550 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
jingzhongrong 2010-04-29
  • 打赏
  • 举报
回复
分开写没法保证是在连续的地址空间,如果你要连续的地址空间,只能一次性申请。
job82824 2010-04-29
  • 打赏
  • 举报
回复
那如果想分开写的话,该怎么弄?谁能给几行代码,让我以后能反复借鉴的
Snovate 2010-04-29
  • 打赏
  • 举报
回复
学习来了
littlefangMFC 2010-04-29
  • 打赏
  • 举报
回复
一直只用new,学习一下
jingzhongrong 2010-04-29
  • 打赏
  • 举报
回复
#2和#3已经说的很清楚了。如果要一次性分配连续的地址空间,则分配总大小即可,每个数组元素个数*数组元素大小的总和。
使用各个数组的时候需要计算数组的起始地址。

hUserBuf = VirtualAlloc(...);
int* a = (int*)hUserBuf; //20个元素
short* b = (short*)hUserBuf+sizeof(int)*20;
wangli820 2010-04-29
  • 打赏
  • 举报
回复
VC声明
  LPVOID VirtualAlloc(
  LPVOID lpAddress, // region to reserve or commit
  SIZE_T dwSize, // size of region
  DWORD flAllocationType, // type of allocation
  DWORD flProtect // type of access protection
  );
[编辑本段]说明
  该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页
  如果用于内存分配的话,并且分配类型未指定MEM_RESET,则系统将自动设置为0;
[编辑本段]参数表说明
  LPVOID lpAddress, 分配内存区域的地址。当你使用VirtualAlloc来提交一块以前保留的内存块的时候,lpAddress参数可以用来识别以前保留的内存块。如果这个参数是NULL,系统将会决定分配内存区域的位置,并且按64-KB向上取整(roundup)。
  SIZE_T dwSize, 要分配或者保留的区域的大小。这个参数以字节为单位,而不是页,系统会根据这个大小一直分配到下页的边界DWORD
  flAllocationType, 分配类型 ,你可以指定或者合并以下标志:MEM_COMMIT,MEM_AUTO_COMMIT,MEM_RESERVE和MEM_TOP_DOWN。
  DWORD flProtect 指定了被分配区域的访问保护方式
  分配类型 功能
  MEM_COMMIT 在内存或者指定的磁盘页文件(虚拟内存文件)中分配一物理存储区域 函数初始化这个区域为0
  MEM_PHYSICAL 该类型必须和MEM_RESERVE一起使用 分配一块具有读写功能的物理内存区
  MEM_RESERVE 保留虚拟地址空间以便以后提交。
  MEM_RESET
  MEM_TOP_DOWN 告诉系统从最高可允许的虚拟地址开始映射应用程序。
  MEM_WRITE_WATCH
  访问类型
  PAGE_READONLY 该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访问PAGE_READWRITE 区域可被应用程序读写
  PAGE_EXECUTE 区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。
  PAGE_EXECUTE_READ 区域包含可执行代码,应用程序可以读该区域。
  PAGE_EXECUTE_READWRITE 区域包含可执行代码,应用程序可以读写该区域。
  PAGE_GUARD 区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限
  PAGE_NOACCESS 任何访问该区域的操作将被拒绝
  PAGE_NOCACHE RAM中的页映射到该区域时将不会被微处理器缓存(cached)
  注:PAGE_GUARD和PAGE_NOCHACHE标志可以和其他标志合并使用以进一步指定页的特征。PAGE_GUARD标志指定了一个防护页(guard page),即当一个页被提交时会因第一次被访问而产生一个one-shot异常,接着取得指定的访问权限。PAGE_NOCACHE防止当它映射到虚拟页的时候被微处理器缓存。这个标志方便设备驱动使用直接内存访问方式(DMA)来共享内存块。
[编辑本段]返回值
  如果调用成功,返回分配的首地址,
  调用失败,返回NULL 你可以通过GetLastError函数来获取错误信息
[编辑本段]笔记
  VirtualAlloc可以通过并行多次调用提交一个区域的部分或全部来保留一个大的内存区域。多重调用提交同一块区域不会引起失败。这使得一个应用程序保留内存后可以随意提交将被写的页。当这种方式不在有效的时候,它会释放应用程序通过检测被保留页的状态看它是否在提交调用之前已经被提交
job82824 2010-04-29
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 visualassist4680 的回复:]

hUserBuf = VirtualAlloc(NULL, dwUserBufferSize * 数组个数,MEM_COMMIT, PAGE_READWRITE);
这样你分配的就是连续的 n 个数组了,每个数组的大小是 dwUserBufferSize

如果你的每个数组大小不一,
那么 申请大小 = sizeof(struct1) * 个数1 + sizeof(struct2) * ……
[/Quote]
这个真好。但如果我想分开写呢?来两句代码如何?
visualassist4680 2010-04-29
  • 打赏
  • 举报
回复
释放的时候写错了,
应该是:
VirtualFree(hUserBuf , 0, MEM_RELEASE)

内存碎片是指反复的申请,释放内存,有可能会造成没有可用的大片连续的内存空间,每一块可用的内存都是大小不一,且不连续
visualassist4680 2010-04-29
  • 打赏
  • 举报
回复
hUserBuf = VirtualAlloc(NULL, dwUserBufferSize * 数组个数,MEM_COMMIT, PAGE_READWRITE);
这样你分配的就是连续的 n 个数组了,每个数组的大小是 dwUserBufferSize

如果你的每个数组大小不一,
那么 申请大小 = sizeof(struct1) * 个数1 + sizeof(struct2) * 个数2 + sizeof(struct3) * 个数3 + ...

hUserBuf = VirtualAlloc(NULL, 申请大小,MEM_COMMIT, PAGE_READWRITE);
也是连续的,但是,使用各个数组的时候,需要自己计算各个数组的首地址
结构数组n 的首地址 = huserbuf + sizeof(struct1) * 个数1 + ... + sizeof(structn-1) * 个数n-1

释放的时候 VirtualFree(hUserBuf , 申请大小, MEM_RELEASE) 就把你申请的全部内存都释放了
尹成 2010-04-29
  • 打赏
  • 举报
回复
内存分配方式和调试机制


M内存分配


内存分配函数

MFCWin32或者C语言的内存分配API,有四种内存分配API可供使用。


Win32的堆分配函数

每一个进程都可以使用堆分配函数创建一个私有的堆──调用进程地址空间的一个或者多个页面。DLL创建的私有堆必定在调用DLL的进程的地址空间内,只能被调用进程访问。

HeapCreate用来创建堆;HeapAlloc用来从堆中分配一定数量的空间,HeapAlloc分配的内存是不能移动的;HeapSize可以确定从堆中分配的空间的大小;HeapFree用来释放从堆中分配的空间;HeapDestroy销毁创建的堆。


Windows传统的全局或者局部内存分配函数

由于Win32采用平面内存结构模式,Win32下的全局和局部内存函数除了名字不同外,其他完全相同。任一函数都可以用来分配任意大小的内存(仅仅受可用物理内存的限制)。用法可以和Win16下基本一样。

Win32下保留这类函数保证了和Win16的兼容。


C语言的标准内存分配函数

C语言的标准内存分配函数包括以下函数:

malloc,calloc,realloc,free,等。

这些函数最后都映射成堆API函数,所以,malloc分配的内存是不能移动的。这些函数的调式版本为

malloc_dbg,calloc_dbg,realloc_dbg,free_dbg,等。


Win32的虚拟内存分配函数

虚拟内存API是其他API的基础。虚拟内存API以页为最小分配单位,X86上页长度为4KB,可以用GetSystemInfo函数提取页长度。虚拟内存分配函数包括以下函数:


LPVOID VirtualAlloc(LPVOID lpvAddress,

DWORD cbSize,

DWORD fdwAllocationType,

DWORD fdwProtect);

该函数用来分配一定范围的虚拟页。参数1指定起始地址;参数2指定分配内存的长度;参数3指定分配方式,取值MEM_COMMINT或者MEM_RESERVE;参数4指定控制访问本次分配的内存的标识,取值为PAGE_READONLY、PAGE_READWRITE或者PAGE_NOACCESS。


LPVOID VirtualAllocEx(HANDLE process,

LPVOID lpvAddress,

DWORD cbSize,

DWORD fdwAllocationType,

DWORD fdwProtect);

该函数功能类似于VirtualAlloc,但是允许指定进程process。VirtaulFree、VirtualProtect、VirtualQuery都有对应的扩展函数。


BOOL VirtualFree(LPVOID lpvAddress,

DWORD dwSize,

DWORD dwFreeType);

该函数用来回收或者释放分配的虚拟内存。参数1指定希望回收或者释放内存的基地址;如果是回收,参数2可以指向虚拟地址范围内的任何地方,如果是释放,参数2必须是VirtualAlloc返回的地址;参数3指定是否释放或者回收内存,取值为MEM_DECOMMINT或者MEM_RELEASE。


BOOL VirtualProtect(LPVOID lpvAddress,

DWORD cbSize,

DWORD fdwNewProtect,

PDWORD pfdwOldProtect);

该函数用来把已经分配的页改变成保护页。参数1指定分配页的基地址;参数2指定保护页的长度;参数3指定页的保护属性,取值PAGE_READ、PAGE_WRITE、PAGE_READWRITE等等;参数4用来返回原来的保护属性。


DWORD VirtualQuery(LPCVOID lpAddress,

PMEMORY_BASIC_INFORMATION lpBuffer,

DWORD dwLength

);

该函数用来查询内存中指定页的特性。参数1指向希望查询的虚拟地址;参数2是指向内存基本信息结构的指针;参数3指定查询的长度。



BOOL VirtualLock(LPVOID lpAddress,DWORD dwSize);

该函数用来锁定内存,锁定的内存页不能交换到页文件。参数1指定要锁定内存的起始地址;参数2指定锁定的长度。


BOOL VirtualUnLock(LPVOID lpAddress,DWORD dwSize);

参数1指定要解锁的内存的起始地址;参数2指定要解锁的内存的长度。
job82824 2010-04-29
  • 打赏
  • 举报
回复


//创建
hUserBuf = VirtualAlloc(NULL, dwUserBufferSize,MEM_COMMIT, PAGE_READWRITE);
//销毁
VirtualFree( hUserBuf, dwUserBufferSize, MEM_DECOMMIT );
VirtualFree( hUserBuf, 0, MEM_RELEASE);


这段代码本身是没有任何错误的,但我关心的是如果多处调用VirtualAlloc,是否需要优化一下代码,更改一下里面的参数?
finder_zhang 2010-04-29
  • 打赏
  • 举报
回复

LPBYTE pD1 = (LPBYTE)VirtualAlloc(0,10,MEM_COMMIT,PAGE_READWRITE);
//虽然只分配了10字节,但其实是分配了4K物理内存,因为CPU页是4K
pD1[4095] = 5; //所以可以读写第4096字节
// pData[4096] = 1; //读写4K+1处,就报错了

//pD2只占虚拟究竟不占物理空间
LPBYTE pD2 = (LPBYTE)VirtualAlloc(0,10,MEM_RESERVE,PAGE_READWRITE);
TRACE("pD1 addr = %08x\n",UINT(pD1));//因为分配粒度是64K
TRACE("pD2 addr = %08x\n",UINT(pD2));//第二次分配的地址,没什么意外的话,会是pD1+64K

//只保留虚地址未申请物理地址的,读写会报错
// pD2[0] = 4;
//真正申请物理地址,才可读写
VirtualAlloc(pD2,10,MEM_COMMIT,PAGE_READWRITE);
pD2[0] = 4;

//释放P1的物理与虚拟内存
VirtualFree(pD1,0,MEM_RELEASE);
// pD1[4095] = 5; //释放后再读写则报错

//只释放物理内存,未释放虚拟内存
VirtualFree(pD2,0,MEM_DECOMMIT);
// pD2[0] = 1; //读写释放了物理内存的,会报错.
//重新分配物理内存
VirtualAlloc(pD2,1,MEM_COMMIT,PAGE_READWRITE);
pD2[100] = 4; //重新分配物理内存后,又可以读写了

//真正物理与虚地址都释放
VirtualFree(pD2,0,MEM_RELEASE);
// pD2[100] = 5; //不能再读写了


我也是新手,这个函数的使用与参数的,百度上,WINDOWS核心编程里,都有说明,什么CPU页,分配粒度的,看着难理解,我自己写了些测试的程序,来查查看到底怎么回事.
还有一些参数的使用未测试,等其他用得熟的老手来作个全面的总结.
finder_zhang 2010-04-29
  • 打赏
  • 举报
回复
VirtualAlloc 与 new 的区别,就是VirtualAlloc 与他的名字一样,虚的分配.
可以先占虚拟的地址,却暂不申请物理内存,到有需要时,再申请物理内存,
申请后,可以又再释放,但却仍然占着虚地址.

而new就是直接申请物理地址,没有VirtualAlloc那么灵活.

如果一申请就马上用,没有先占着以后再用的意图的话,new与va都达到同样功能.
洗洗睡去 2010-04-29
  • 打赏
  • 举报
回复
看来我太老土了 还在new delete阶段……
finder_zhang 2010-04-29
  • 打赏
  • 举报
回复
不用VirtualAlloc也行.
PBYTE pData = new BYTE[3*1024*1024];

int* pInt = (PINT)pData; //如果是一般的数据,直接指向即可使用
for (i=0;i<1024;i++){
pInt[i] = i+1;
}

//如果是类,不能简单指向,还要执行构造函数,就要定位new,C++Primer上有教
MyCls* pMyCls = new(pData+4*1024) MyCls[10]; //主要是这个定位new
这样new出空间,再在空间里面放也行,不知道是不是楼主想要的.
finder_zhang 2010-04-29
  • 打赏
  • 举报
回复
楼主,我不知道你是不是想要这种效果.


class MyCls{
public:
MyCls(){iVal = 10;}
private:
int iVal;
};

int main()
{
int i;

PBYTE pData = (PBYTE)VirtualAlloc(0, //让系统自动选空闲地址
3 * 1024 * 1024, // size of region
MEM_COMMIT, //申请物理内存
PAGE_READWRITE); //保护模式

if (NULL == pData) {
TRACE("申请内存失败!"); return 0;
}

int* pInt = (PINT)pData; //如果是一般的数据,直接指向即可使用
for (i=0;i<1024;i++)
{
pInt[i] = i+1;
}

//如果是类,不能简单指向,还要执行构造函数,就要定位new,C++Primer上有教
MyCls* pMyCls = new(pData+4*1024) MyCls[10]; //主要是这个定位new


这样就把自己的数据,自己想怎样排就怎样排了.
finder_zhang 2010-04-29
  • 打赏
  • 举报
回复
楼主的意思是不是要先分配一大块区,然后在这块区内,再按照自己的控制,在什么地址放什么样的数据,这样?
visualassist4680 2010-04-29
  • 打赏
  • 举报
回复
hUserBuf1 = VirtualAlloc(NULL, dwUserBufferSize1,MEM_COMMIT, PAGE_READWRITE);
hUserBuf2 = VirtualAlloc(NULL, dwUserBufferSize2,MEM_COMMIT, PAGE_READWRITE);
...
...
...
hUserBufn = VirtualAlloc(NULL, dwUserBufferSizen,MEM_COMMIT, PAGE_READWRITE);
分开写的
jingzhongrong 2010-04-29
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 job82824 的回复:]

引用 10 楼 jingzhongrong 的回复:

分开写没法保证是在连续的地址空间,如果你要连续的地址空间,只能一次性申请。

我的意思是如果分开写,一般代码是怎么写的呢?主要是里面的参数是咋弄的,不一定要很连续的那种?
[/Quote]

指定第二个参数,为数组的大小即可。
hUserBuf = VirtualAlloc(NULL, count*sizeof(int),MEM_COMMIT, PAGE_READWRITE);
hUserBuf2 = VirtualAlloc(NULL, count2*sizeof(short),MEM_COMMIT, PAGE_READWRITE);
int* a = (int*)hUserBuf;
short* b = (short*)hUserBuf2;
Eleven 2010-04-29
  • 打赏
  • 举报
回复
内存池技术。。。
加载更多回复(2)

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧