??xml version="1.0" encoding="utf-8" standalone="yes"?>
如果对声明数l的语句不太明白的话Q请参阅我前D|间脓出的文章
<<如何理解c和c++的复杂类型声?gt;>。?br />数组的数l名其实可以看作一个指针。看下例Q?br />例八Q?br />
int array[10]={0,1,2,3,4,5,6,7,8,9}
,value;
狼注:上次也写了一个简单的内存池,是用STL的deque来管理的Q实现v来比较简单,不过有个~点是只能固定分配大小Q所以需要根据经验g先分配内存大,然后把内存再ơ划分ؓ{大的小的内存供使用?br />概述
内存池(MemPoolQ技术备受推崇。我用google搜烦了下Q没有找到比较详l的原理性的文章Q故此补充一个。另外,补充了boost::poollg与经典MemPool的差异。同时也描述了MemPool在sgi-stl/stlport中的q用?br />
l典的内存池技?br />
l典的内存池QMemPoolQ技术,是一U用于分配大量大相同的对象的技术。通过该技术可以极大加快内存分?释放q程。下面我们详l解释其中的奥妙?br />
l典的内存池只涉及两个常量:MemBlockSize、ItemSizeQ小对象的大,但不能小于指针的大小Q在32位^C是不能于4字节Q,以及两个指针变量MemBlockHeader、FreeNodeHeader。开始,q两个指针均为空?br />
class MemPool
{
private:
const int m_nMemBlockSize;
const int m_nItemSize;
struct _FreeNode {
_FreeNode* pPrev;
BYTE data[m_nItemSize - sizeof(_FreeNode*)];
};
struct _MemBlock {
_MemBlock* pPrev;
_FreeNode data[m_nMemBlockSize/m_nItemSize];
};
_MemBlock* m_pMemBlockHeader;
_FreeNode* m_pFreeNodeHeader;
public:
MemPool(int nItemSize, int nMemBlockSize = 2048)
: m_nItemSize(nItemSize), m_nMemBlockSize(nMemBlockSize),
m_pMemBlockHeader(NULL), m_pFreeNodeHeader(NULL)
{
}
};
其中指针变量MemBlockHeader是把所有申L内存块(MemBlockQ串成一个链表,以便通过它可以释放所有申L内存。FreeNodeHeader变量则是把所有自由内存结点(FreeNodeQ串成一个链?br />
q?
D话涉及两个关键概念Q内存块QMemBlockQ和自由内存l点QFreeNodeQ。内存块大小一般固定ؓMemBlockSize字节Q除ȝ以徏
立链表的指针外)。内存块在申请之初就被划分ؓ多个内存l点QNodeQ,每个Node大小为 ItemSizeQ小对象的大)Q计
MemBlockSize/ItemSize个。这MemBlockSize/ItemSize个内存结点刚开始全部是自由的,他们被串成链表。我们看?
甌/释放内存q程Q就很容易明白这样做的目的?br />
甌内存q程
代码如下Q?br />void* MemPool::malloc() // 没有参数
{
if (m_pFreeNodeHeader == NULL)
{
const int nCount = m_nMemBlockSize/m_nItemSize;
_MemBlock* pNewBlock = new _MemBlock;
pNewBlock->data[0].pPrev = NULL;
for (int i = 1; i < nCount; ++i)
pNewBlock->data[i].pPrev = &pNewBlock->data[i-1];
m_pFreeNodeHeader = &pNewBlock->data[nCount-1];
pNewBlock->pPrev = m_pMemBlock;
m_pMemBlock = pNewBlock;
}
void* pFreeNode = m_pFreeNodeHeader;
m_pFreeNodeHeader = m_pFreeNodeHeader->pPrev;
return pFreeNode;
}
内存甌q程分ؓ两种情况Q?br />
* 在自由内存结炚w表(FreeNodeListQ非I?br /> 在此情况下,Allocq程只是从链表中摘下一个结点的q程?br />
* 否则Q意味着需要一个新的内存块(MemBlock)?br /> q个q程需要将新申LMemBlock切割成多个NodeQƈ把它们串h?br /> MemPool技术的开销主要在这?br />
释放内存q程
代码如下Q?br />void MemPool::free(void* p)
{
_FreeNode* pNode = (_FreeNode*)p;
pNode->pPrev = m_pFreeNodeHeader;
m_pFreeNodeHeader = pNode;
}
释放q程极其单,只是把要释放的结Ҏ到自由内存链表(FreeNodeListQ的开头即可?br />
性能分析
MemPool技术申请内?释放内存均极其快Q比AutoFreeAlloc慢)。其内存分配q程多数情况下复杂度为O(1)Q主要开销在FreeNodeList为空需要生成新的MemBlock时。内存释放过E复杂度为O(1)?br />
boost::pool
boost::pool是内存池技术的变种。主要的变化如下Q?br />
* MemBlock改ؓ非固定长?MemBlockSize)Q而是Q第1ơ申h
m_nItemSize*32Q第2ơ申h m_nItemSize*64Q第3ơ申hm_nItemSize*128Q以此类推。不采用
固定的MemBlockSizeQ而采用这U做法预模型(是的Q这是一U用户内存需求的预测模型Q其实std::vector的内存增长亦采用了该?
型)Q是一个细节上的改良?br />
* 增加了ordered_free(void* p) 函数?br />
ordered_free区别于free的是Qfree把要释放的结Ҏ到自由内存链?
QFreeNodeListQ的开_ordered_free则假设FreeNodeList是有序的Q因此会遍历FreeNodeList把要释放?
l点插入到合适的位置?br />
我们已经看到Qfree的复杂度是O
(1)Q非常快。但h意ordered_free是比较费的操作,其复杂度是O(N)。这里N是FreeNodeList的大。对于一个频J释??
LpȝQ这个N很可能是个大数。这个boost描述得很清楚Qhttp:
//www.boost.org/libs/pool/doc/interfaces/pool.html
注意Q不要认为boost提供ordered_free是多此一举。后文我们会在讨论boost::object_pool时解释这一炏V?br />
Z内存池技术的通用内存分配lg
sgi-stl把内存池QMemPoolQ技术进行发扬光大,用它来实现其最Ҏ的allocator?br />
?
大体的思想是,建立16个MemPoolQ?lt;=8字节的内存申L0号MemPool分配Q?lt;=16字节的内存申L1号?
MemPool分配Q?lt;=24字节的内存有2号MemPool分配Q以此类推。最后,>128字节的内存申L普通的malloc分配?br />注意
以上代码属于伪代码(struct _FreeNode、_MemBlock~译通不q)Qƈ且去除了出错处理?img src ="http://www.shnenglu.com/zzh/aggbug/19616.html" width = "1" height = "1" />
]]>
例十七:
chars='a';
int*ptr;
ptr=(int*)&s;
*ptr=1298Q?br />?
针ptr是一个int*cd的指针,它指向的cd是int。它指向的地址是s的首地址。在32位程序中Qs占一个字节,intcd占四个字节。最后一?
语句不但改变了s所占的一个字节,q把和s怏的高地址方向的三个字节也改变了。这三个字节是干什么的Q只有编译程序知道,而写E序的h是不太可能知?
的。也许这三个字节里存储了非常重要的数据,也许q三个字节里正好是程序的一条代码,而由于你Ҏ针的马虎应用Q这三个字节的D改变了!q会造成崩溃?
的错误。?br />让我们再来看一例:
例十八:
1。chara;
2。int*ptr=&a;
...
...
3。ptr++;
4?ptr=115;
?
例子完全可以通过~译Qƈ能执行。但是看到没有?W?句对指针ptrq行自加1q算后,ptr指向了和整Ş变量a盔R的高地址方向的一块存储区。这块存?
区里是什么?我们不知道。有可能它是一个非帔R要的数据Q甚臛_能是一条代码。而第4句竟然往q片存储区里写入一个数据!q是严重的错误。所以在使用指针
ӞE序员心里必非常清楚:我的指针I竟指向了哪里。在用指针访问数l的时候,也要注意不要出数组的低端和高端界限Q否则也会造成cM的错误。?br />?
指针的强制类型{换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类?大于sizeof(ptr1的类?Q那么在使用指针
ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类?于sizeof(ptr1的类?Q那么在使用指针ptr1来访
问ptr2所指向的存储区时是不安全的。至于ؓ什么,读者结合例十七来想一惻I应该会明白的?/font>
]]>
intfun1(char*,int);
int(*pfun1)(char*,int);
pfun1=fun1;
....
....
inta=(*pfun1)("abcdefg",7);//通过函数指针调用函数。?br />可以把指针作为函数的形参。在函数调用语句中,可以用指针表辑ּ来作为实参。?br />例十三:
intfun(char*);
inta;
charstr[]="abcdefghijklmn";
a=fun(str);
...
...
intfun(char*s)
{
intnum=0;
for(inti=0;i{
num+=*s;s++;
}
returnnum;
}
q个例子中的函数funl计一个字W串中各个字W的ASCII码g和。前面说了,数组的名字也是一个指针?/font>
在函数调用中Q当?span lang="EN-US">str作ؓ实参传递给形参s后,实际是把str的g递给了sQs所指向的地址和str所指向的地址一_但是str和s各自占用各自的存储空间?/span>
在函C内对sq行自加1q算Qƈ不意味着同时对strq行了自?q算?/span>
]]>
例十四:
1。floatf=12.3;
2。float*fptr=&f;
3。int*p;
在上面的例子中,假如我们惌指针p指向实数fQ应该怎么搞?是用下面的语句吗Q?br />p=&f;
?
寏V因为指针p的类型是int*Q它指向的类型是int。表辑ּ&f的结果是一个指针,指针的类型是float*,它指向的cd是float。两
者不一_直接赋值的Ҏ是不行的。至在我的MSVC++6.0上,Ҏ针的赋D句要求赋值号两边的类型一_所指向的类型也一_其它的编译器上我
没试q,大家可以试试。ؓ了实现我们的目的Q需要进?强制cd转换"Q?br />p=(int*)&f;
如果有一个指?/font>
pQ我们需要把它的cd和所指向的类型改为TYEP* 和TYPEQ?br />那么语法格式是:
(TYPE*)pQ?/font>
q样强制cd转换的结果是一个新指针Q该新指针的cd?span lang="EN-US">TYPE*Q它指向的类型是TYPEQ它指向的地址是原指针指向的地址。而原来的指针p的一切属性都没有被修攏V?/span>
一个函数如果用了指针作ؓ形参Q那么在函数调用语句的实参和形参的结合过E中Q也会发生指针类型的转换。?br />例十五:
voidfun(char*);
inta=125,b;
fun((char*)&a);
...
...
voidfun(char*s)
{
charc;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
}
?
意这是一?2位程序,故intcd占了四个字节Qcharcd占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗Q在函数
调用语句中,实参&a的结果是一个指针,它的cd是int*Q它指向的类型是int。Ş参这个指针的cd是char*Q它指向的类型是char?
q样Q在实参和Ş参的l合q程中,我们必须q行一ơ从int*cd到char*cd的{换。结合这个例子,我们可以q样来想象编译器q行转换的过E:~译
器先构造一个时指针char*tempQ 然后执行temp=(char*)&aQ最后再把temp的g递给s。所以最后的l果是:s的类?
是char*,它指向的cd是charQ它指向的地址是a的首地址。?br />我们已经知道Q?/span>
指针的值就是指针指向的地址Q在32位程序中Q指针的值其实是一?2位整数?/span>
那可不可以把一个整数当作指针的值直接赋l指针呢Q就象下面的语句Q?/font>
unsignedinta;
TYPE*ptr;//TYPE是intQchar或结构类型等{类型。?br />...
...
a=20345686;
ptr=20345686;//我们的目的是要指针ptr指向地址20345686Q十q制
Q?br />ptr=a;//我们的目的是要指针ptr指向地址20345686Q十q制Q?br />~译一下吧。结果发现后面两条语句全是错的。那么我们的目的׃能达C吗?不,q有办法Q?br />unsignedinta;
TYPE*ptr;//TYPE是intQchar或结构类型等{类型。?br />...
...
a=某个敎ͼq个数必M表一个合法的地址Q?br />ptr=(TYPE*)aQ?/呵呵Q这可以了。?br />严格说来q里?TYPE*)和指针类型{换中?TYPE*)q不一栗这里的(TYP
E*)的意思是把无W号整数a的值当作一个地址来看待。上面强调了a的值必M表一个合法的地址Q否则的话,在你使用ptr的时候,׃出现非法操作错误。?br />x能不能反q来Q把指针指向的地址x针的值当作一个整数取出来。完全可以。下面的例子演示了把一个指针的值当作一个整数取出来Q然后再把这个整数当作一个地址赋给一个指针:
例十六:
inta=123,b;
int*ptr=&a;
char*str;
b=(int)ptr;//把指针ptr的值当作一个整数取出来。?br />str=(char*)b;//把这个整数的值当作一个地址赋给指针str。?br />好了Q现在我们已l知道了Q可以把指针的值当作一个整数取出来Q也可以把一个整数值当作地址赋给一个指针?/font>
]]>
例十一Q?br />struct MyStruct
{
inta;
intb;
intc;
}
MyStruct ss={20,30,40};//声明了结构对象ssQƈ把ss的三个成员初始?br />化ؓ20Q?0?0。?br />MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型是
MyStruct*,它指向的cd是MyStruct。?br />
int*pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的?br />cd和它指向的类型和ptr是不同的?/font>
请问怎样通过指针ptr来访问ss的三个成员变量?
{案Q?br />ptr->a;
ptr->b;
ptr->c;
又请问怎样通过指针pstr来访问ss的三个成员变量?
{案Q?br />*pstrQ?/讉K了ss的成员a。?br />*(pstr+1);//讉K了ss的成员b。?br />*(pstr+2)//讉K了ss的成员c。?br />呵呵Q虽然我在我的MSVC++6.0上调式过上述代码Q但是要知道Q这样用pstr来访问结构成员是不正规的Qؓ了说明ؓ什么不正规Q让我们看看怎样通过指针来访问数l的各个单元Q?br />例十二:
int array[3]={35,56,37};
int*pa=array;
通过指针pa讉K数组array的三个单元的Ҏ是:
*pa;//讉K了第0号单元?br />*(pa+1);//讉K了第1号单元?br />*(pa+2);//讉K了第2号单元?br />从格式上看倒是与通过指针讉Kl构成员的不正规Ҏ的格式一栗?br />
所有的C/C++~译?/span>
在排列数l的单元ӞL把各个数l单元存攑֜q箋的存储区里,单元和单元之间没有空隙?/span>
但在存放l构对象的各个成员时Q在某种~译环境下,可能会需要字寚w或双字对齐或者是别的什么对齐,需要在盔R两个成员之间加若q个"填充字节"Q这导致各个成员之间可能会有若q个字节的空隙?/span>
所
以,在例十二中,即*pstr讉KCl构对象ss的第一个成员变量aQ也不能保证*(pstr+1)׃定能讉K到结构成员b。因为成员a和成员b?
间可能会有若q填充字节,说不?(pstr+1)正好访问到了这些填充字节呢。这也证明了指针的灵zL。要是你的目的就是想看看各个l构成员之间到底
有没有填充字节,嘿,q倒是个不错的Ҏ。?br />通过指针讉Kl构成员的正方法应该是象例十二中用指针ptr的方法?/font>
]]>
...
...
value=array[0];//也可写成Qvalue=*array;
value=array[3];//也可写成Qvalue=*(array+3);
value=array[4];//也可写成Qvalue=*(array+4);
上例中,一般而言数组名array代表数组本nQ类型是int[10]Q但
如果?span lang="EN-US">array看做指针的话Q它指向数组的第0个单元,cd是int*Q所指向的类型是数组单元的类型即int?/span>
因此
*array{于0׃点也不奇怪了。同理,array+3是一个指向数l第3个单元的指针Q所?(array+3){于3。其它依此类推。?br />例九Q?br />例九Q?br />
char*str[3]={
"Hello,thisisasample!",
"Hi,goodmorning.",
"Helloworld"
};
chars[80]Q?br />strcpy(s,str[0]);//也可写成strcpy(s,*str);
strcpy(s,str[1]);//也可写成strcpy(s,*(str+1));
strcpy(s,str[2]);//也可写成strcpy(s,*(str+2));
上例中,
str是一个三单元的数l,该数l的每个单元都是一个指针,q些指?br />针各指向一个字W串。把指针数组名str当作一个指针的话,它指向数l的W?号单元,它的cd是char**Q它指向的类型是char*?/font>
*str也是一个指针,它的cd是char*Q它所指向的类型是charQ它指向的地址是字W串"Hello,thisisasample!"的第一个字W的地址Q即'H'的地址?/span>
str+1也是一个指针,它指向数l的W?号单元,它的cd是char**Q它指向的类型是char*。?/span>
*(str+1)也是一个指针,它的cd是char*Q它所指向的类型是charQ它指向 "Hi,goodmorning."的第一个字W?H'Q等{。?/font>
下面ȝ一下数l的数组名的问题?/font>
声明了一个数l?span lang="EN-US">TYPE
array[n]Q则数组名称array有了两重含义:W一Q它代表整个数组Q它的类型是TYPE[n]Q第二 ,它是一个指针,该指针的cd?
TYPE*Q该指针指向的类型是TYPEQ也是数组单元的类型,该指针指向的内存区就是数l第0号单元,该指针自己占有单独的内存区,注意它和数组W?
号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表辑ּ是错误的?/span>
在不同的表达式中数组名array可以扮演不同的角艌Ӏ?br />
在表辑ּsizeof(array)中,数组名array代表数组本nQ故q时sizeof函数出的是整个数组的大?/span>
在表辑ּ*array中,array扮演的是指针Q因此这个表辑ּ的结果就是数l第0号单元的倹{sizeof(*array)出的是数组单元的大?/span>
表达?/font>
array+nQ其中n=0Q?Q?Q?...。)中,array扮演的是指针Q故array+n的结果是一个指针,它的cd是TYPE*Q它指向的类型是TYPEQ它指向数组Wn号单元。故sizeof(array+n)出的是指针cd的大。?br />
例十Q?/font>
int array[10];
int(*ptr)[10];
ptr=&array;
上例中ptr是一个指针,它的cd是int(*)[10]Q他指向的类型是int[10] Q我们用整个数组的首地址来初始化它。在语句ptr=&array中,array代表数组本n。?br />本节中提C函数sizeof()Q那么我来问一问,
sizeof(指针名称)出?/span>
I竟
是指针自w类型的大小
呢还是指针所指向的类型的大小Q答案是前者?/span>
例如Q?/font>
int(*ptr)[10];
则在32位程序中Q有Q?br />sizeof(int(*)[10])==4
sizeof(int[10])==40
sizeof(ptr)==4
实际上,
sizeof(对象)出的都是对象自w的cd的大?/span>
Q而不是别的什?/font>
cd的大?/font>
]]>
下面是一些指针表辑ּ的例子:
例六Q?br />inta,b;
intarray[10];
int*pa;
pa=&a;//&a是一个指针表辑ּ。?br />int**ptr=&pa;//&pa也是一个指针表辑ּ。?br />*ptr=&b;//*ptr?amp;b都是指针表达式。?br />pa=array;
pa++;//q也是指针表辑ּ。?br />例七Q?br />char*arr[20];
char**parr=arr;//如果把arr看作指针的话Qarr也是指针表达式?br />char*str;
str=*parr;//*parr是指针表辑ּ
str=*(parr+1);//*(parr+1)是指针表辑ּ
str=*(parr+2);//*(parr+2)是指针表辑ּ