这篇文章是讲alloca实现的原理。 alloca我感觉是个很强大的函数,帮了我一个很大的忙。突然觉得我就是个天才,我们知道,构造一个对象,无非就二种方式,一种形式就是动态构造:

class A;

A* a=new A();

对于这种形式,编译器底层可能是这么实现的:

size_t   nClassSize=sizeof(A);
void*    pClassAdress=malloc(nClassSize);
return new(pClassAdress)A();

一种就是静态构造,

class A;

A a;

同样对于这种方式,编译器底层可能是这么实现的:

size_t   nClassSize=sizeof(A);
void*    pClassAdress=alloca(nClassSize);
return new(pClassAdress)A();

这里用到new的一种新用法,new(void* pAddress)Construct();

这句话的作用其实是在pAddress所指向的内存上调用类的构造函数来初始化pAddress指向的内存,构造出指定对象。很多内存池技术会用这种方式来在内存池缓存中构造对象。

上面二种方式中,唯一不同的就是内存分配函数不同。

malloc是在堆上分配内存,其分配,回收都会跟操作系统打交道,一来速度会很慢,二来频繁的调用会导致很多内存碎片,更加导分配性能的下降。对于这种方式分配的内存需要显示调用free函数来回收内存,以防造成内存泄露。

而alloca这个函数的不同之处在于:该函数是在调用堆栈上来分配内存:在调用堆栈上分配内存有什么不一样呢?其实调用堆栈的分配,回收只是CPU寄存器的ESP的移动,基本上只需要几十条机器指令就能实现一次分配:而且不需要人为的释放,不会造成内存泄露:

_alloca_probe_16 proc                   ; 16 byte aligned alloca  
push    ecx  
lea     ecx, [esp] + 8          ; TOS before entering this function  
sub     ecx, eax                ; New TOS  
and     ecx, (16 - 1)           ; Distance from 16 bit align (align down)  
add     eax, ecx                ; Increase allocation size  
sbb     ecx, ecx                ; ecx = 0xFFFFFFFF if size wrapped around  
or      eax, ecx                ; cap allocation size on wraparound  
pop     ecx                     ; Restore ecx  
jmp     _chkstk

更重要的是,函数的调用堆栈是程序启动时,就已经分配好的内存,在调用堆栈上分配内存是不需要再跟操作系统打交道的,性能非常高,而且不会造成内存碎片。

这个函数简直帮了我的大忙,帮我优化了不少性能。我之前设计了一套网络通信库,希望实现拾取到网络消息后,根据包ID,构造出逻辑包,初始化逻辑包的数据,调用逻辑包的处理函数;

class Packet
{
private:
uint32_t    m_peerIP;
int32_t     m_SocketID;
public:
void SetPeerIP(uint32_t IP){m_peerIP=IP;}
void SetSocketID(int32_t Id){m_SocketID=Id;}
uint32_t GetPeerIP(){return m_peerIP;}
        int32_t  GetSocketID(){return m_SocketID;}
Packet():m_peerIP(0),m_SocketID(0){}
virtual ~Packet(){CleanUp();}//这里基类的析构函数一定要用虚函数
        virtual void  CleanUp(){};
virtual bool  Read(CMessage& Ref)=0;
virtual bool  Write(CMessage& Ref)=0;
virtual int  Execute()=0;
virtual PacketID  GetPacketID()=0;
};
class PacketFactory 
{
public :
PacketFactory(){}
virtual ~PacketFactory(){}
        virtual Packet*  CreatePacket(void* pAddress)=0;
        virtual PacketID  GetPacketID()const=0;
virtual int         GetPacketSize()=0;
};

上面二个类Packet是逻辑包基类,PacketFactory是逻辑包工程类,通过这个工厂能够构造出逻辑包,CMessage是个消息包其底层是基于二进制流的。

class PacketMgr :public Singleton<PacketMgr>
{
friend class CMessage;
private:
PacketFactory**     m_Factories;
    unsigned int       m_Size;
bool               b_Inited;//是否已经初始化
public:
PacketMgr():b_Inited(false),m_Size(0),m_Factories(NULL){}
virtual ~PacketMgr()
{
for(unsigned int i=0;i<m_Size;i++)
{
SAFE_DELETE(m_Factories[i]);
}
SAFE_DELETE_ARRAY(m_Factories);
b_Inited=false;
m_Size=0;
}
bool   AddFactory(PacketFactory* pFactory)
{
if(b_Inited==false)
{
assert(false);
return false;
}
if(pFactory==NULL)
{
return false;
}
int nPackIdx=pFactory->GetPacketID();
if(nPackIdx<0||nPackIdx>=m_Size)
{
return false;
}
if( m_Factories[nPackIdx]!=NULL) 
{
assert(false);
return false;
}
m_Factories[nPackIdx]=pFactory;
return true;
}
private://这两个函数仅提供给CMessage对象来用
Packet*  CreatePacket(PacketID nId,void* pAddress)
{
if(nId>=m_Size ||nId<0 || m_Factories[nId]==NULL||pAddress==NULL) 
{
assert(false);
return NULL;
}
Packet* pPacket=m_Factories[nId]->CreatePacket(pAddress);
return pPacket;
}
void     RemovePacket(Packet* pPacket)
{
//pPacket所指向的内存由栈管理,无需delete,仅需调用对象的析构函数
if(pPacket==NULL)
{
assert(false);
return;
}
pPacket->~Packet();//此处析构函数为虚函数,会先调用子类对象的析构函数,然后再调用父类函数的析构函数,防止内存泄露
}
public:
int      GetPacketSize(PacketID nId);
bool     Init(int nMaxPacket)
{
assert(nMaxPacket>0);
m_Size=nMaxPacket;
m_Factories=new PacketFactory*[m_Size];
for(unsigned int i=0;i<m_Size;i++)
{
m_Factories[i]=NULL;
}
b_Inited=true;
return true;
}
};
}
#define S_PACKET_MGR  (PacketMgr::Instance())

PacketMgr是一个逻辑包管理类,系统初始化时,会调用AddFactory以逻辑包ID为数组下标,将逻辑包工厂类添加到m_Factories数组。

S_PACKET_MGR- AddFactory(new  CGLoginFactory() );

一个简单的逻辑包实现:

class CGLogin : public Packet
{
private:
char          m_Account[MAX_ACCOUNT_LEN];//账号
char          m_Pwd[MAX_PWD_LEN];//密码
uint32_t      m_ClientVersion;//客户端版本
public:
CGLogin();
~CGLogin();
void  SetAccount(const char* szAccout);
const char* GetAccount(){return m_Account;}
void        SetPwd(const char* szPwd);
const char* GetPwd(){return m_Pwd;}
uint32_t    GetClientVersion(){return m_ClientVersion;}
void        SetClientVersion(uint32_t version){m_ClientVersion=version;}
public:
virtual void  CleanUp();
virtual bool  Read(CMessage& Ref)
{
Ref.getEx((void*)m_Account,sizeof(m_Account));
Ref.getEx((void*)m_Pwd,sizeof(m_Pwd));
m_ClientVersion=Ref.getUInt32();
}
virtual bool  Write(CMessage& Ref)
{
Ref.addEx((void*)m_Account,sizeof(m_Account));
Ref.addEx((void*)m_Pwd,sizeof(m_Pwd));
Ref.addUInt32(m_ClientVersion);
}
virtual int  Execute()
{
    return CGLoginHandler::Execute(this);
}
virtual PacketID  GetPacketID(){return CG_Login;}
};
class CGLoginFactory : public PacketFactory 
{
//注意这里new的用法 new(void* p)fun();这里new的作用是在p所指向的内存调用对象的构造函数,不会再申请内存
Packet*  CreatePacket(void* pAddress){ return new(pAddress)CGLogin();}
PacketID  GetPacketID()const{ return CG_Login;}
virtual int         GetPacketSize(){return sizeof(CGLogin);}
};
class CGLoginHandler 
{
public:
static int Execute(CGLogin* pPacket) ;
};
//网络底层拾取屌网络消息,回来调用消息的run方法。
int32_t CMessage::run()
{
uint32_t packet_id = getMsgID();
size_t PacketSize=S_PACKET_MGR->GetPacketSize(packet_id);
if(PacketSize<=0)
{
return 0;
}
//alloca函数用于在栈上申请指定大小的内存空间,其申请的内存空间由栈负责回收
void* pAddress=NULL;
#if  _WIN32
        __try
#else
try
#endif
{
pAddress=alloca(PacketSize);//在栈上申请对象大小的空间,用于构造对象
}
#if  _WIN32
__except(EXCEPTION_EXECUTE_HANDLER)
{
             pAddress=NULL;
}
#else
catch(...)
{
pAddress=NULL;
}
#endif
bool bAllocInStack=false;//是否在栈上分配的内存
if(pAddress==NULL)
{
//如果在栈上分配失败则在堆栈上分配
pAddress=malloc(PacketSize);
bAllocInStack=false;
}
else
{
bAllocInStack=true;
}
if(pAddress==NULL)
{
assert(false);
return 0;
}
memset(pAddress,0,PacketSize);//初始化这块内存
Packet* pPacket=S_PACKET_MGR->CreatePacket(packet_id,pAddress);//在指定的地址上构造包对象
//这里pPacket的地址实际上是pAddress的地址,上面已经判断过pAddress的值,这里无需再判断pPacket是否为空了
pPacket->SetSocketID(m_socketID);
pPacket->SetPeerIP(m_dwIP);
pPacket->Read(*this);//初始化逻辑包
int nRet=pPacket->Execute();//调用逻辑包处理函数
S_PACKET_MGR->RemovePacket(pPacket);//该函数仅会调用类的析构函数,并不会delete pPacket指向的内存,该内存分配在栈上,由系统负责回收
if(bAllocInStack==false)
{
//内存是分配在堆栈上,需要手动释放内存
free(pAddress);
}
return nRet;
}
}
bool SendPacket(Packet* pPacket)
 { if(pPacket==NULL) 
 { 
 return false; 
 } 
 int nPacketId=pPacket->GetPacketID();
 CMessage Msg; 
 Msg.init(nPacketId); 
 pPacket->Write(Msg);//将逻辑包的数据写入消息流 
 //调用网络底层将CMessage发送出去 
 }