重识new之四
本帖最后由 元始天尊 于 2014-10-14 23:12 编辑_malloca
void* _malloca(size_t size);
MSDN里是这么描述的:在栈上分配内存,是_alloca的安全性增强版本。返回指针是根据对象大小对齐,如果size是0则返回长度0的合法指针。如果地址空间无法分配会抛出一个栈溢出异常,该异常不是C++异常,需要使用SEH。_malloca和_alloca的区别在于_alloc无论大小总是在栈上分配,且无需free释放内存。而_malloca需要使用_freea释放内存,在调试模式下,_malloca总是在堆上分配。在异常处理时显式调用_malloca有一些限制,x86架构处理器异常处理例程会自动控制函数栈帧,在执行操作时并不基于当前闭合函数栈帧,这一点在Windows NT SEH和C++异常处理的catch语句中很常见。因此在以下情况显示调用_malloca,在执行异常处理例程后会产生程序崩溃。
Windows NT SEH异常过滤表达式:__except(_malloca())
Windows NT SEH最终执行表达式:__finally(_malloca())
C++ 异常处理 catch语句
然而_malloca可以从异常处理例程中除上述情况以外的情况下直接调用,或在异常处理所触发的回调函数中调用也是允许的。
先来看一个例子:
#include <windows.h>
#include <stdio.h>
#include <malloc.h>
int main()
{
int size;
int numberRead = 0;
int errcode = 0;
void *p = NULL;
void *pMarker = NULL;
while (numberRead == 0)
{
printf_s("Enter the number of bytes to allocate "
"using _malloca: ");
numberRead = scanf_s("%d", &size);
}
// Do not use try/catch for _malloca,
// use __try/__except, since _malloca throws
// Structured Exceptions, not C++ exceptions.
__try
{
if (size > 0)
{
p =_malloca( size );
}
else
{
printf_s("Size must be a positive number.");
}
_freea( p );
}
// Catch any exceptions that may occur.
__except( GetExceptionCode() == STATUS_STACK_OVERFLOW )
{
printf_s("_malloca failed!\n");
// If the stack overflows, use this function to restore.
errcode = _resetstkoflw();
if (errcode)
{
printf("Could not reset the stack!");
_exit(1);
}
};
}
input:1000
output:Enter the number of bytes to allocate using _malloca: 1000
VS中在_malloca上右键转到定义,发现源码位于malloc.h
#define _ALLOCA_S_THRESHOLD 1024
#define _ALLOCA_S_STACK_MARKER0xCCCC
#define _ALLOCA_S_HEAP_MARKER 0xDDDD
#if defined(_M_IX86)
#define _ALLOCA_S_MARKER_SIZE 8
#elif defined(_M_X64)
#define _ALLOCA_S_MARKER_SIZE 16
#elif defined(_M_ARM)
#define _ALLOCA_S_MARKER_SIZE 8
#elif !defined (RC_INVOKED)
#error Unsupported target platform.
#endif
......
#if !defined(__midl) && !defined(RC_INVOKED)
#pragma warning(push)
#pragma warning(disable:6540)
__inline void *_MarkAllocaS(_Out_opt_ __crt_typefix(unsigned int*) void *_Ptr, unsigned int _Marker)
{
if (_Ptr)
{
*((unsigned int*)_Ptr) = _Marker;
_Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE;
}
return _Ptr;
}
#pragma warning(pop)
#endif
#if defined(_DEBUG)
#if !defined(_CRTDBG_MAP_ALLOC)
#undef _malloca
#define _malloca(size) \
__pragma(warning(suppress: 6255)) \
_MarkAllocaS(malloc((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_HEAP_MARKER)
#endif
#else
#undef _malloca
#define _malloca(size) \
__pragma(warning(suppress: 6255)) \
((((size) + _ALLOCA_S_MARKER_SIZE) <= _ALLOCA_S_THRESHOLD) ? \
_MarkAllocaS(_alloca((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_STACK_MARKER) : \
_MarkAllocaS(malloc((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_HEAP_MARKER))
#endif
可以分析出DEBUG版下,宏调用了malloc进行分配,之后使用_MarkAllocaS对分配内存进行一些处理(后面讨论),而RELEASE版下,宏先判断要分配的内存是否过大,该门限为_ALLOCA_S_THRESHOLD-_ALLOCA_S_MARKER_SIZE=1016,如果超过该值则调用malloc,否则调用_alloca。从字面意思上可以知道_ALLOCA_S_HEAP_MARKER这个标志位说明该内存区是在堆上分配的,而_ALLOCA_S_STACK_MARKER标志是在栈上分配的。在malloc或_alloca分配成功后,总会调用_MarkAllocaS进行调整。结合字面意思和5行C语言代码可知,在执行过内存分配后,返回的指针前sizeof(unigned int*)字节为分配内存类型标志,之后指针调整到空闲位置丢给用户操作。那么所有的问题都落在_alloca和malloc的源码上,下面会进行分析。
_alloca(我第一次见栈上分配内存是在逆向一个易语言程序时,用的是sub esp,而微软这个函数是第一次见)
void* _alloca(size_t size);
该函数只在程序栈中分配字节,而函数退出时该空间会自动释放,因此无需手动释放。用此函数的限制和_malloca相同。
#include <windows.h>
#include <stdio.h>
#include <malloc.h>
int main()
{
int size = 1000;
int errcode = 0;
void *pData = NULL;
// 注意:不要使用try/catch,而要使用__try/__except,因为_alloca抛出SEH而不是C++异常
__try {
// 使用_alloca分配太大的空间很容易崩溃,推荐1024字节以下的空间
if (size > 0 && size < 1024)
{
pData = _alloca( size );
printf_s( "Allocated %d bytes of stack at 0x%p",
size, pData);
}
else
{
printf_s("Tried to allocate too many bytes.\n");
}
}
// 如果溢出
__except( GetExceptionCode() == STATUS_STACK_OVERFLOW )
{
printf_s("_alloca failed!\n");
// 使用下面的函数恢复函数栈
errcode = _resetstkoflw();
if (errcode)
{
printf_s("Could not reset the stack!\n");
_exit(1);
}
};
}
反汇编得到:
.text:004113F0 ; int __cdecl main()
.text:004113F0 _main proc near ; CODE XREF: j__mainj
.text:004113F0
.text:004113F0 pAllocaBase = dword ptr -120h
.text:004113F0 cbSize = dword ptr -11Ch
.text:004113F0 var_114 = dword ptr -114h
.text:004113F0 allocaList = dword ptr -48h
.text:004113F0 pData = dword ptr -3Ch
.text:004113F0 errcode = dword ptr -30h
.text:004113F0 size = dword ptr -24h
.text:004113F0 var_1C = dword ptr -1Ch
.text:004113F0 ms_exc = CPPEH_RECORD ptr -18h
.text:004113F0
.text:004113F0 55 push ebp
.text:004113F1 8B EC mov ebp, esp
.text:004113F3 6A FE push 0FFFFFFFEh
.text:004113F5 68 80 6F 41 00 push offset stru_416F80
.text:004113FA 68 82 10 41 00 push offset j___except_handler4
.text:004113FF 64 A1 00 00 00 00 mov eax, large fs:0
.text:00411405 50 push eax
.text:00411406 81 C4 F0 FE FF FF add esp, 0FFFFFEF0h
.text:0041140C 53 push ebx
.text:0041140D 56 push esi
.text:0041140E 57 push edi
.text:0041140F 8D BD E0 FE FF FF lea edi,
.text:00411415 B9 42 00 00 00 mov ecx, 42h
.text:0041141A B8 CC CC CC CC mov eax, 0CCCCCCCCh
.text:0041141F F3 AB rep stosd
.text:00411421 A1 00 80 41 00 mov eax, ___security_cookie
.text:00411426 31 45 F8 xor , eax
.text:00411429 33 C5 xor eax, ebp
.text:0041142B 89 45 E4 mov , eax
.text:0041142E 50 push eax
.text:0041142F 8D 45 F0 lea eax,
.text:00411432 64 A3 00 00 00 00 mov large fs:0, eax
.text:00411438 89 65 E8 mov , esp
.text:0041143B C7 45 B8 00 00 00 00 mov , 0
.text:00411442 C7 45 DC E8 03 00 00 mov , 3E8h
.text:00411449 C7 45 D0 00 00 00 00 mov , 0
.text:00411450 C7 45 C4 00 00 00 00 mov , 0
.text:00411457 C7 45 FC 00 00 00 00 mov , 0
.text:0041145E 83 7D DC 00 cmp , 0
.text:00411462 7E 6F jle short loc_4114D3
.text:00411464 81 7D DC 00 04 00 00 cmp , 400h
.text:0041146B 7D 66 jge short loc_4114D3
.text:0041146D 8B 45 DC mov eax,
.text:00411470 83 C0 24 add eax, 24h
.text:00411473 89 85 E4 FE FF FF mov , eax
.text:00411479 8B 85 E4 FE FF FF mov eax,
.text:0041147F E8 4E FC FF FF call j___alloca_probe_16
.text:00411484 89 A5 E0 FE FF FF mov , esp
.text:0041148A 89 65 E8 mov , esp
.text:0041148D 8D 4D B8 lea ecx,
.text:00411490 51 push ecx ; pAllocaInfoList
.text:00411491 8B 95 E4 FE FF FF mov edx, ; cbSize
.text:00411497 8B 8D E0 FE FF FF mov ecx, ; pAllocaBase
.text:0041149D E8 90 FB FF FF call j_@_RTC_AllocaHelper@12 ; _RTC_AllocaHelper(x,x,x)
.text:004114A2 83 85 E0 FE FF FF 20 add , 20h
.text:004114A9 8B 95 E0 FE FF FF mov edx,
.text:004114AF 89 55 C4 mov , edx
.text:004114B2 8B F4 mov esi, esp
.text:004114B4 8B 45 C4 mov eax,
.text:004114B7 50 push eax
.text:004114B8 8B 4D DC mov ecx,
.text:004114BB 51 push ecx
.text:004114BC 68 58 58 41 00 push offset Format ; "Allocated %d bytes of stack at 0x%p"
.text:004114C1 FF 15 C0 92 41 00 call ds:__imp__printf_s
.text:004114C7 83 C4 0C add esp, 0Ch
.text:004114CA 3B F4 cmp esi, esp
.text:004114CC E8 79 FC FF FF call j___RTC_CheckEsp
.text:004114D1 EB 17 jmp short loc_4114EA
.text:004114D3 ; ---------------------------------------------------------------------------
.text:004114D3
.text:004114D3 loc_4114D3: ; CODE XREF: _main+72j
.text:004114D3 ; _main+7Bj
.text:004114D3 8B F4 mov esi, esp
.text:004114D5 68 84 58 41 00 push offset aTriedToAllocat ; "Tried to allocate too many bytes.\n"
.text:004114DA FF 15 C0 92 41 00 call ds:__imp__printf_s
.text:004114E0 83 C4 04 add esp, 4
.text:004114E3 3B F4 cmp esi, esp
.text:004114E5 E8 60 FC FF FF call j___RTC_CheckEsp
.text:004114EA
.text:004114EA loc_4114EA: ; CODE XREF: _main+E1j
.text:004114EA C7 45 FC FE FF FF FF mov , 0FFFFFFFEh
.text:004114F1 E9 97 00 00 00 jmp loc_41158D
.text:004114F6 ; ---------------------------------------------------------------------------
.text:004114F6
.text:004114F6 $LN10: ; DATA XREF: .rdata:stru_416F80o
.text:004114F6 8B 45 EC mov eax, ; Exception filter 0 for function 4113F0
.text:004114F9 8B 08 mov ecx,
.text:004114FB 8B 11 mov edx,
.text:004114FD 89 95 EC FE FF FF mov , edx
.text:00411503 81 BD EC FE FF FF FD 00+ cmp , 0C00000FDh
.text:0041150D 75 0C jnz short loc_41151B
.text:0041150F C7 85 E4 FE FF FF 01 00+ mov , 1
.text:00411519 EB 0A jmp short loc_411525
.text:0041151B ; ---------------------------------------------------------------------------
.text:0041151B
.text:0041151B loc_41151B: ; CODE XREF: _main+11Dj
.text:0041151B C7 85 E4 FE FF FF 00 00+ mov , 0
.text:00411525
.text:00411525 loc_411525: ; CODE XREF: _main+129j
.text:00411525 8B 85 E4 FE FF FF mov eax,
.text:0041152B
.text:0041152B $LN12:
.text:0041152B C3 retn
.text:0041152C ; ---------------------------------------------------------------------------
.text:0041152C
.text:0041152C $LN11: ; DATA XREF: .rdata:stru_416F80o
.text:0041152C 8B 65 E8 mov esp, ; Exception handler 0 for function 4113F0
.text:0041152F 8B F4 mov esi, esp
.text:00411531 68 B0 58 41 00 push offset a_allocaFailed ; "_alloca failed!\n"
.text:00411536 FF 15 C0 92 41 00 call ds:__imp__printf_s
.text:0041153C 83 C4 04 add esp, 4
.text:0041153F 3B F4 cmp esi, esp
.text:00411541 E8 04 FC FF FF call j___RTC_CheckEsp
.text:00411546 8B F4 mov esi, esp
.text:00411548 FF 15 BC 92 41 00 call ds:__imp___resetstkoflw
.text:0041154E 3B F4 cmp esi, esp
.text:00411550 E8 F5 FB FF FF call j___RTC_CheckEsp
.text:00411555 89 45 D0 mov , eax
.text:00411558 83 7D D0 00 cmp , 0
.text:0041155C 74 28 jz short loc_411586
.text:0041155E 8B F4 mov esi, esp
.text:00411560 68 C4 58 41 00 push offset aCouldNotResetT ; "Could not reset the stack!\n"
.text:00411565 FF 15 C0 92 41 00 call ds:__imp__printf_s
.text:0041156B 83 C4 04 add esp, 4
.text:0041156E 3B F4 cmp esi, esp
.text:00411570 E8 D5 FB FF FF call j___RTC_CheckEsp
.text:00411575 8B F4 mov esi, esp
.text:00411577 6A 01 push 1 ; Code
.text:00411579 FF 15 C8 92 41 00 call ds:__imp___exit
.text:0041157F ; ---------------------------------------------------------------------------
.text:0041157F 3B F4 cmp esi, esp
.text:00411581 E8 C4 FB FF FF call j___RTC_CheckEsp
.text:00411586
.text:00411586 loc_411586: ; CODE XREF: _main+16Cj
.text:00411586 C7 45 FC FE FF FF FF mov , 0FFFFFFFEh
.text:0041158D
.text:0041158D loc_41158D: ; CODE XREF: _main+101j
.text:0041158D EB 02 jmp short loc_411591
.text:0041158F ; ---------------------------------------------------------------------------
.text:0041158F EB 02 jmp short loc_411593
.text:00411591 ; ---------------------------------------------------------------------------
.text:00411591
.text:00411591 loc_411591: ; CODE XREF: _main:loc_41158Dj
.text:00411591 33 C0 xor eax, eax
.text:00411593
.text:00411593 loc_411593: ; CODE XREF: _main+19Fj
.text:00411593 52 push edx
.text:00411594 8B CD mov ecx, ebp ; frame
.text:00411596 50 push eax
.text:00411597 8D 15 CC 15 41 00 lea edx, v ; v
.text:0041159D FF 75 B8 push ; allocaList
.text:004115A0 E8 9B FB FF FF call j_@_RTC_CheckStackVars2@12 ; _RTC_CheckStackVars2(x,x,x)
.text:004115A5 58 pop eax
.text:004115A6 5A pop edx
.text:004115A7 8D A5 D0 FE FF FF lea esp,
.text:004115AD 8B 4D F0 mov ecx,
.text:004115B0 64 89 0D 00 00 00 00 mov large fs:0, ecx
.text:004115B7 59 pop ecx
.text:004115B8 5F pop edi
.text:004115B9 5E pop esi
.text:004115BA 5B pop ebx
.text:004115BB 8B 4D E4 mov ecx,
.text:004115BE 33 CD xor ecx, ebp ; cookie
.text:004115C0 E8 59 FA FF FF call j_@__security_check_cookie@4 ; __security_check_cookie(x)
.text:004115C5 8B E5 mov esp, ebp
.text:004115C7 5D pop ebp
.text:004115C8 C3 retn
关键代码为:
.text:0041146D 8B 45 DC mov eax,
.text:00411470 83 C0 24 add eax, 24h
.text:00411473 89 85 E4 FE FF FF mov , eax
.text:00411479 8B 85 E4 FE FF FF mov eax,
.text:0041147F E8 4E FC FF FF call j___alloca_probe_16
.text:00411484 89 A5 E0 FE FF FF mov , esp
.text:0041148A 89 65 E8 mov , esp
.text:0041148D 8D 4D B8 lea ecx,
.text:00411490 51 push ecx ; pAllocaInfoList
.text:00411491 8B 95 E4 FE FF FF mov edx, ; cbSize
.text:00411497 8B 8D E0 FE FF FF mov ecx, ; pAllocaBase
.text:0041149D E8 90 FB FF FF call j_@_RTC_AllocaHelper@12 ; _RTC_AllocaHelper(x,x,x)
.text:004114A2 83 85 E0 FE FF FF 20 add , 20h
.text:004114A9 8B 95 E0 FE FF FF mov edx,
.text:004114AF 89 55 C4 mov , edx
很疑惑地,在__alloca_probe_16调用之前,发生了add eax,24h和mov eax,,而在之后发生了mov , esp
那么大胆做出猜测:
1.add eax,24h,说明这24h字节用来实现内存管理或字节对齐之类功能
2.__alloca_probe_16为接受一个参数的函数,该参数通过eax传递,进行的操作是修改esp,因此esp可以看做执行结果
3.另一个函数原型为void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList)
.text:004116A0 ; void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList)
.text:004116A0 @_RTC_AllocaHelper@12 proc near ; CODE XREF: _RTC_AllocaHelper(x,x,x)j
.text:004116A0
.text:004116A0 pAllocaInfoList = dword ptr8
.text:004116A0
.text:004116A0 pAllocaBase = ecx
.text:004116A0 cbSize = edx
.text:004116A0 55 push ebp
.text:004116A1 8B EC mov ebp, esp
.text:004116A3 53 push ebx
.text:004116A4 56 push esi
.text:004116A5 8B F1 mov esi, pAllocaBase
.text:004116A7 8B DA mov ebx, cbSize
.text:004116A9 85 F6 test esi, esi
.text:004116AB 74 1F jz short loc_4116CC
.text:004116AD 85 DB test ebx, ebx
.text:004116AF 74 1B jz short loc_4116CC
.text:004116B1 8B 55 08 mov cbSize,
.text:004116B4 85 D2 test cbSize, cbSize
.text:004116B6 74 14 jz short loc_4116CC
.text:004116B8 57 push edi
.text:004116B9 B0 CC mov al, 0CCh
.text:004116BB 8B FE mov edi, esi
.text:004116BD 8B CB mov pAllocaBase, ebx
.text:004116BF F3 AA rep stosb
.text:004116C1 8B 02 mov eax,
.text:004116C3 89 46 04 mov , eax
.text:004116C6 89 5E 0C mov , ebx
.text:004116C9 89 32 mov , esi
.text:004116CB 5F pop edi
.text:004116CC
.text:004116CC loc_4116CC: ; CODE XREF: _RTC_AllocaHelper(x,x,x)+Bj
.text:004116CC ; _RTC_AllocaHelper(x,x,x)+Fj ...
.text:004116CC 5E pop esi
.text:004116CD 5B pop ebx
.text:004116CE 5D pop ebp
.text:004116CF C2 04 00 retn 4
.text:004116CF @_RTC_AllocaHelper@12 endp
翻译可得:
void main()
{
......
int size=1024,cbsize=size+sizeof(_RTC_ALLOCA_NODE)+4;
_RTC_ALLOCA_NODE* pAllocaBase=__alloca_probe_16(cbsize);
_RTC_AllocaHelper(pAllocaBase,cbsize,NULL);
void* pData=(void*)(pAllocaBase+1);
......
}
//这个结构体来自于reactos
#pragma pack(push,1)
typedef struct _RTC_ALLOCA_NODE
{
__int32 guard1;
struct _RTC_ALLOCA_NODE *next;
#if (defined(_X86_) && !defined(__x86_64))
__int32 dummypad;
#endif
size_t allocaSize;
#if (defined(_X86_) && !defined(__x86_64))
__int32 dummypad2;
#endif
__int32 guard2;
}_RTC_ALLOCA_NODE;
#pragma pack(pop)
void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList)
{//初始化已分配空间,可以用于维护调试版函数栈
if(pAllocaBase && cbSize && pAllocaInfoList)//由于最后一个参数在本例中为0,这个函数实际相当于没有执行
{
memset(pAllocaBase,0xCC,cbSize);//经常在调试版程序栈空间看到0xCC"烫烫烫烫烫"对吧,就是这样的。。。
pAllocaBase->next=*pAllocaInfoList;//链接到前一个结构;
pAllocaBase->allocaSize=cbSize;
*pAllocaInfoList=pAllocaBase;//自此可知,上述结构形成链表,pAllocaInfoList指向当前结构
}
}
//__alloca_probe_16代码下面会进行分析
以上是debug版的情况,如果尝试用release版查看反汇编代码,会发现只有push和call __alloca_probe_16部分,可知add eax,24和AllocaHelper只是调试版本用于内存管理的。所以重点落在该函数的解析上
进入源代码查看,__alloca_probe_16用来按16字节对齐内存,而chkstk子例程进行实际分配操作:
alloca16.asm
; _alloca_probe_16, _alloca_probe_8 - 按8/16字节对齐例程
;输入:EAX = 栈帧大小
;输出:调整EAX,修改esp.
public_alloca_probe_8
_alloca_probe_16 proc ; 16 byte aligned alloca
push ecx
lea ecx, + 8 ; 父函数栈顶(call _alloca_probe_16和push ecx)
sub ecx, eax ;
and ecx, (16 - 1) ; 计算地址低4位未对齐偏移
add eax, ecx ; 增加cbSize使其对齐
sbb ecx, ecx ; 如果cbSize溢出,ecx = 0xFFFFFFFF,否则ecx = 0
or eax, ecx ; 如果溢出,则eax = 0xFFFFFFFF
pop ecx ; 还原ecx
jmp _chkstk ; eax存储修正cbSize,并交给_chkstk处理
_alloca_probe_16 endp
end
public_alloca_probe
_chkstk proc
_alloca_probe =_chkstk
push ecx
lea ecx, + 8 - 4 ; 考虑到之后的ret指令对未来esp的修改
sub ecx, eax ; 分配栈空间,ecx存储更新后的栈位置
sbb eax, eax ; 如果申请空间过大,eax = 0xFFFFFFFF,否则eax = 0
not eax ;
and ecx, eax ; ecx = 0 | ecx = ecx
mov eax, esp ;
and eax, not ( _PAGESIZE_ - 1) ; 得到当前栈位置所处页面地址
cs10:
cmp ecx, eax ;
jb short cs20 ; 如果新的栈位置小于页面地址
mov eax, ecx ;
pop ecx
xchg esp, eax ; 更新esp,原始esp存储在eax中
mov eax, dword ptr ; 当前esp指向返回地址
mov dword ptr , eax ; 修正函数栈帧,使其可以正确返回
ret
cs20:
sub eax, _PAGESIZE_ ; 获取上一个页面
test dword ptr ,eax ; 探测页面权限
jmp short cs10 ; 如果没有产生异常则跳转,如果出现异常,则直接进入父函数的异常处理中
_chkstk endp
end
calloc
void* calloc(size_t num,size_t size);
calloc用来分配数组空间,同样返回指针是根据对象类型对齐的,每个对象都被初始化为0,如果待分配内存超过_HEAP_MAXREQ或分配失败则设置errno为ENOMEM,calloc内部调用了malloc函数使用_set_new_mode函数设置回调模式,该回调用于处理分配失败情况,默认情况下,分配失败后malloc不会调用新回调分配内存,然后我们可以通过提前调用_set_new_mode(1)或者链接NEWMODE.OBJ修改这种默认行为.calloc用法如下:
// crt_calloc.c
// This program uses calloc to allocate space for
// 40 long integers. It initializes each element to zero.
#include <stdio.h>
#include <malloc.h>
int main( void )
{
long *buffer;
buffer = (long *)calloc( 40, sizeof( long ) );
if( buffer != NULL )
printf( "Allocated 40 long integers\n" );
else
printf( "Can't allocate memory\n" );
free( buffer );
}
其源码分别位于calloc.c和calloc_impl.c:
void * __cdecl _calloc_base (size_t num, size_t size)
{
int errno_tmp = 0;
void * pv = _calloc_impl(num, size, &errno_tmp);
if ( pv == NULL && errno_tmp != 0 && _errno())
{
errno = errno_tmp; // recall, #define errno *_errno()
}
return pv;
}
void * __cdecl _calloc_impl (size_t num, size_t size, int * errno_tmp)
{
size_tsize_orig;
void *pvReturn;
/* ensure that (size * num) does not overflow */
if (num > 0)
{
_VALIDATE_RETURN_NOEXC((_HEAP_MAXREQ / num) >= size, ENOMEM, NULL);
}
size_orig = size = size * num;
/* force nonzero size */
if (size == 0)
size = 1;
for (;;)
{
pvReturn = NULL;
if (size <= _HEAP_MAXREQ)
{
if (pvReturn == NULL)
pvReturn = HeapAlloc(_crtheap, HEAP_ZERO_MEMORY, size);
}
if (pvReturn || _newmode == 0)
{
RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, size_orig, 0));
if (pvReturn == NULL)
{
if ( errno_tmp )
*errno_tmp = ENOMEM;
}
return pvReturn;
}
/* call installed new handler */
if (!_callnewh(size))
{
if ( errno_tmp )
*errno_tmp = ENOMEM;
return NULL;
}
/* new handler was successful -- try to allocate again */
}
}
现在来分析_calloc_impl执行流程:
1.先检查申请大小是否超出门限,若申请大小为0则强制为1
2.使用HeapAlloc分配内存并清零。如果成功则返回,否则执行_callnewh,即定义的失败处理函数,如果该回调函数返回0则原函数返回0退出,如果该回调函数返回非0,则原函数重复执行2直到成功。(_callnewh最终调用了NtQueryInformationProcess 0x24)
可见calloc并没有像MSDN说的那样调用了malloc。。。另外,没看到有异常处理机制。
_expand
用于扩展或缩小已分配内存
用于改变已分配内存区大小
void* _expand(void* memblock,size_t newsize);
该函数会检测地址的内存权限,如果不通过移动内存无法得到足够的空间,该函数会返回空,该函数不会分配小于请求大小的内存区。该函数不通过移动内存块的方式增缩已分配堆内存,在64位下该函数不会缩小内存区,大小小于16k的内存块都是在低碎片堆中分配的,在这种情况下_expand不会对内存块做任何变动直接返回memblock
同样该函数会检测参数合法性,且size不能超过门限值_HEAP_MAXREQ。
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
int main( void )
{
char *bufchar;
printf( "Allocate a 512 element buffer\n" );
if( (bufchar = (char *)calloc( 512, sizeof( char ) )) == NULL )
exit( 1 );
printf( "Allocated %d bytes at %Fp\n",
_msize( bufchar ), (void *)bufchar );
if( (bufchar = (char *)_expand( bufchar, 1024 )) == NULL )
printf( "Can't expand" );
else
printf( "Expanded block to %d bytes at %Fp\n",
_msize( bufchar ), (void *)bufchar );
// Free memory
free( bufchar );
exit( 0 );
}
查看_expand源码为,分析过程和前面类似:
void * __cdecl _expand_base (void * pBlock, size_t newsize)
{
void * pvReturn;
size_t oldsize;
/* validation section */
_VALIDATE_RETURN(pBlock != NULL, EINVAL, NULL);
if (newsize > _HEAP_MAXREQ) {
errno = ENOMEM;
return NULL;
}
if (newsize == 0)
{
newsize = 1;
}
oldsize = (size_t)HeapSize(_crtheap, 0, pBlock);
pvReturn = HeapReAlloc(_crtheap, HEAP_REALLOC_IN_PLACE_ONLY, pBlock, newsize);
if (pvReturn == NULL)
{
/* 如果使用了低碎片堆则返回原指针. */
if (oldsize <= 0x4000 /* 低碎片堆最多申请16KB内存 */
&& newsize <= oldsize && _is_LFH_enabled())
pvReturn = pBlock;
else
errno = _get_errno_from_oserr(GetLastError());
}
if (pvReturn)
{
RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, newsize, 0));
}
return pvReturn;
}
可见_expand函数最终调用了HeapReAlloc函数
一个小插曲:其实这些内存管理的库函数普遍使用debug和release两个版本,debug源码在dbg*.c(pp)中可以找到,同时调试的时候是可以直接定位进去的,而release版函数通常在函数名所在文件,比如delete在delete.cpp中;而debug版源码内存管理函数通常会有一种_CrtMemBlockHeader的结构体,这些都需要自己摸索。
delete
//release版
void operator delete( void * p )
{
RTCCALLBACK(_RTC_Free_hook, (p, 0));
free( p );
}
//debug版
void operator delete( void *pUserData )
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK);/* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK);/* release other threads */
__END_TRY_FINALLY
return;
}
可见delete->free
realloc
分析方法和前面相同
void * __cdecl _realloc_base (void * pBlock, size_t newsize)
{
void * pvReturn;
size_t origSize = newsize;
//if ptr is NULL, call malloc
if (pBlock == NULL)
return(_malloc_base(newsize));
//if ptr is nonNULL and size is zero, call free and return NULL
if (newsize == 0)
{
_free_base(pBlock);
return(NULL);
}
for (;;) {
pvReturn = NULL;
if (newsize <= _HEAP_MAXREQ)
{
if (newsize == 0)
newsize = 1;
pvReturn = HeapReAlloc(_crtheap, 0, pBlock, newsize);
}
else
{
_callnewh(newsize);
errno = ENOMEM;
return NULL;
}
if ( pvReturn || _newmode == 0)
{
if (pvReturn)
{
RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, newsize, 0));
}
else
{
errno = _get_errno_from_oserr(GetLastError());
}
return pvReturn;
}
//call installed new handler
if (!_callnewh(newsize))
{
errno = _get_errno_from_oserr(GetLastError());
return NULL;
}
//new handler was successful -- try to allocate again
}
}
得到realloc->HeapReAlloc
malloc
void * __cdecl _malloc_base (size_t size)
{
void *res = NULL;
//validate size
if (size <= _HEAP_MAXREQ) {
for (;;) {
//allocate memory block
res = _heap_alloc(size);
//if successful allocation, return pointer to memory
//if new handling turned off altogether, return NULL
if (res != NULL)
{
break;
}
if (_newmode == 0)
{
errno = ENOMEM;
break;
}
//call installed new handler
if (!_callnewh(size))
break;
//new handler was successful -- try to allocate again
}
} else {
_callnewh(size);
errno = ENOMEM;
return NULL;
}
RTCCALLBACK(_RTC_Allocate_hook, (res, size, 0));
if (res == NULL)
{
errno = ENOMEM;
}
return res;
}
__forceinline void * __cdecl _heap_alloc (size_t size)
{
if (_crtheap == 0) {
_FF_MSGBANNER(); /* write run-time error banner */
_NMSG_WRITE(_RT_CRT_NOTINIT);/* write message */
__crtExitProcess(255);/* normally _exit(255) */
}
return HeapAlloc(_crtheap, 0, size ? size : 1);
}
得到malloc->_heap_alloc->HeapAlloc
free
void __cdecl _free_base (void * pBlock)
{
int retval = 0;
if (pBlock == NULL)
return;
RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
retval = HeapFree(_crtheap, 0, pBlock);
if (retval == 0)
{
errno = _get_errno_from_oserr(GetLastError());
}
}
得到free->HeapFree
现在所有问题都集中在了HeapAlloc HeapFree HeapReAlloc上
支持!虽然你的技术文档并不能在发表之后就立即有人看到,但是一定会被百度收录,将来就会被很多人看到。
页:
[1]