它是实现定义的。Scott meyer 还指出,实际上许多 C++ 编译器会在返回地址之前保存元素的数量。我已经检查过 VC2010 和 g++ 3.4.2(mingw)。两个编译器都以这种方式实现 operator new[]/delete[]:
+--------------------+-------------------------+
| Num of elems(4byte)| Your Object or Array |
+--------------------+-------------------------+
#include <stdio.h>
#include <stdlib.h>
struct X {
int i;
~X() {
puts("a");
}
};
int main()
{
volatile int s = 3;
printf("input a size: ");
fflush(stdout);
scanf("%d", &s);
X * px = reinterpret_cast<X *>(new X[s]);
printf("%d\n", *( (int*)px - 1));
delete[] px;
return 0;
}
我遵循了 VC2010 中的汇编指令,只要您使用调试符号编译代码,这并不难阅读:
cl /MTd /Zi array_test.cpp
请注意 fflush 的目的是确保在您实际输入尺寸并按 Enter 之前将“输入尺寸:”输出到屏幕上。
使用 scanf 获取大小有两个原因: 1. 让您有机会将进程附加到 VS 调试器 2. 确保大小不会被优化为立即值。
你最好输入一个小数字,比如5,这样你在跟进汇编指令时会更轻松,因为你可以验证一些指令的结果是否符合你的期望。
以下是对实际汇编指令的逐行注释:
X * px = reinterpret_cast<X *>(new X[s]); ; assume s = 5
00401070 mov ecx,dword ptr [s] ; the number of element is saved to ecx
00401073 mov dword ptr [ebp-0Ch],ecx ; save it to somewhere on the stack
00401076 xor ecx,ecx
00401078 mov eax,dword ptr [ebp-0Ch] ; trace it! now it's in eax
0040107B mov edx,4 ; because sizeof(X) == 4
00401080 mul eax,edx ; Get the total bytes needed for the whole array
00401082 seto cl ; handle the scenario: big size which overflow
00401085 neg ecx ; typically not, so cl = 0, and ecx = 0
00401087 or ecx,eax ; now ecx = eax = 4 * 5 = 20
00401089 xor eax,eax ; clear eax, now eax = 0
0040108B add ecx,4 ; add 4 to ecx, why 4? for save the overhead array size
0040108E setb al ; set al to 1 if carry flag is set, typically 0
00401091 neg eax ; eax = 0, neg eax also result 0
00401093 or eax,ecx ; eax = ecx = 24
00401095 push eax ;
00401096 call operator new (40122Ch) ; same as scalar new
0040109B add esp,4 ; balance the stack
0040109E mov dword ptr [ebp-10h],eax ; function's return value typically saved in EAX
; [ebp-10h] is somewhere on stack, used to save the
; returned raw memory pointer
004010A1 cmp dword ptr [ebp-10h],0 ; check whether returned NULL pointer
004010A5 je main+8Ah (4010BAh)
004010A7 mov ecx,dword ptr [ebp-10h] ; now ECX point to 24 bytes raw memory
004010AA mov edx,dword ptr [ebp-0Ch] ; Check address 00401073, edx is 5 now
004010AD mov dword ptr [ecx],edx ; !!!! 5 saved to the start of the 24 bytes raw memory
004010AF mov eax,dword ptr [ebp-10h] ; load start address of the 24 raw memory to EAX
004010B2 add eax,4 ; advance the EAX with 4 bytes, now EAX point to the
; start address of your first object in the array
004010B5 mov dword ptr [ebp-1Ch],eax ; Save this address to somewhere on the stack
004010B8 jmp main+91h (4010C1h) ; not NULL pointer, so skip it
004010BA mov dword ptr [ebp-1Ch],0 ; See address 004010A5
004010C1 mov ecx,dword ptr [ebp-1Ch] ; Load the address to ECX
004010C4 mov dword ptr [px],ecx ; Load the address in ECX to px. -The End-
删除 [] 部分:
delete[] px;
004010DC mov ecx,dword ptr [px] ; the address of the first object
004010DF mov dword ptr [ebp-18h],ecx ; save to somewhereon the stack
004010E2 mov edx,dword ptr [ebp-18h] ; save it again to edx
004010E5 mov dword ptr [ebp-14h],edx ; move around
004010E8 cmp dword ptr [ebp-14h],0 ; Check NULL pointer
004010EC je main+0CDh (4010FDh)
004010EE push 3 ; Looks silly, just because I init it to 3?
004010F0 mov ecx,dword ptr [ebp-14h] ; again, ECX is just the address of first object
; [px] -> ecx -> [ebp-18h] -> edx -> [ebp-14h] -> ecx
004010F3 call X::`vector deleting destructor' (401014h) ; A good function name, lets follow it!
X::`vector deleting destructor':
00401014 jmp X::`vector deleting destructor' (401140h)
X::`vector deleting destructor':
00401140 push ebp
00401141 mov ebp,esp
00401143 push ecx ; Address of the first object
00401144 mov dword ptr [ebp-4],ecx ; save it to somewhere on stack
00401147 mov eax,dword ptr [ebp+8] ; See address 004010EE, it's 3
0040114A and eax,2 ; ??
0040114D je X::`vector deleting destructor'+45h (401185h)
0040114F push offset X::~X (401005h) ; (S1) Put address of the descructor to stack
00401154 mov ecx,dword ptr [this] ; Address of first object
00401157 mov edx,dword ptr [ecx-4] ; !! important, ECX - 4 to get the
; address of the 24-bytes raw memory
; The value in it is the number of the elements
; Save it to EDX(=5, see 004010AD)
0040115A push edx ; (S2) Put it on stack
0040115B push 4 ; (S3) Put the sizeof(X) on stack
0040115D mov eax,dword ptr [this] ; save the address of the first object to EAX
00401160 push eax ; (S4) Put it on stack
00401161 call `vector destructor iterator' (40100Ah) ; Good function name, follow it
`vector destructor iterator':
0040100A jmp `vector destructor iterator' (4011F0h)
`vector destructor iterator':
004011F0 push ebp
004011F1 mov ebp,esp
004011F3 mov eax,dword ptr [__s] ; Some tricks here, by inspecting the value and
; some guess work, __s = 4(S3)
004011F6 imul eax,dword ptr [__n] ; __n = 5 (S2)
004011FA add eax,dword ptr [__t] ; __t = (S4), add it to EAX, now point to end
; of the array
004011FD mov dword ptr [__t],eax ; __t point to end of array
00401200 mov ecx,dword ptr [__n] ; loop counter
00401203 sub ecx,1
00401206 mov dword ptr [__n],ecx
00401209 js `vector destructor iterator'+2Ch (40121Ch) ; jump out of loop if value less than 0
0040120B mov edx,dword ptr [__t] ; Load addr: 1-byte passed the end of the array
0040120E sub edx,dword ptr [__s] ; Now point to the address of last element
00401211 mov dword ptr [__t],edx ; Update this address to __t
00401214 mov ecx,dword ptr [__t] ; save the address to ECX
00401217 call dword ptr [__f] ; __f is the address of destructor function
0040121A jmp `vector destructor iterator'+10h (401200h)
0040121C pop ebp
0040121D ret 10h ; Because we have S1, S2, S3, S4
; 4 pushes
struct X {
int i;
~X() {
004011D0 push ebp
004011D1 mov ebp,esp
004011D3 push ecx ; the address of current object: this in C++
004011D4 mov dword ptr [ebp-4],ecx ; save this to [ebp-4], although not used it
puts("a"); ;
004011D7 push offset string "a" (403758h)
004011DC call dword ptr [__imp__puts (406240h)]
004011E2 add esp,4
}
004011E5 mov esp,ebp
004011E7 pop ebp
004011E8 ret