嗯題主想像的“額外資訊”不是在operator new[] (size_t sz)裡記錄的,而是編譯器在外面看情況額外生成了程式碼。
原因很簡單:全域性的operator new (size_t sz)和operator new[] (size_t sz)並不知道要給什麼型別的物件分配空間,所以自然也不知道它們所需要記錄的額外元資料要些什麼。
而這些資訊從呼叫者的一側是知道的,所以就由編譯器在呼叫者的一側生成程式碼來做這些事情。
===================================================================
得把回答順序調整一下免得誤導了讀者⋯
先看倆例子。現在在我的老MacBook Air 2011上,用的編譯器是llvm-gcc-4。2。1,平臺是Mac OS X 10。7。5 x86-64。
第一個例子沒有虛解構函式:
#include
using
namespace
std
;
class
A
{
int
x
,
y
;
};
// sizeof(A) == 8, x: 4, y: 4
int
main
()
{
A
*
as
=
new
A
[
10
];
delete
[]
as
;
return
0
;
}
這個版本里main()被該版本的GCC編譯為類似下面虛擬碼:
int
main
()
{
void
*
_tmp
=
::
operator
new
[](
sizeof
(
A
)
*
10
);
A
*
as
=
reinterpret_cast
<
A
*>
(
_tmp
);
::
operator
delete
[](
as
);
return
0
;
}
沒有留下任何C++層面上的“額外資訊”。
第二個例子則使用虛解構函式:
#include
using
namespace
std
;
class
A
{
int
x
,
y
;
public
:
~
A
()
{
}
};
// sizeof(A) == 16, vptr: 8, x: 4, y: 4
// sizeof(size_t) == 8
int
main
()
{
A
*
as
=
new
A
[
10
];
delete
[]
as
;
return
0
;
}
這個版本的main()則被這個GCC編譯為類似下面虛擬碼:
int
main
()
{
void
*
_tmp
=
::
operator
new
[](
sizeof
(
A
)
*
10
+
sizeof
(
size_t
))
A
*
_tmpA
=
reinterpret_cast
<
A
*>
(
reinterpret_cast
<
size_t
*>
(
_tmp
)
+
1
);
{
// array length recorded at offset: -sizeof(size_t)
*
(
reinterpret_cast
<
size_t
*>
(
_tmpA
)
-
1
)
=
10
;
A
*
_cur
=
_tmpA
;
for
(
int
_i
=
9
;
_i
!=
-
1
;
_cur
++
,
i
——
)
{
A
::
A
(
_cur
);
// call ctor: initialize vptr, etc
}
}
A
*
as
=
_tmpA
;
if
(
as
!=
nullptr
)
{
size_t
_len
=
*
(
reinterpret_cast
<
size_t
*>
(
as
)
-
1
);
A
*
_cur
=
as
+
_len
;
// call dtor from last to first element in the array
while
(
_cur
!=
as
)
{
_cur
=
_cur
-
1
;
// call virtual dtor through vtable slot 0
auto
_vptr
=
reinterpret_cast
<
void
(
***
)(
A
*
)
>
(
_cur
)[
0
];
_vptr
[
0
](
_cur
);
}
// call operator delete[](size_t sz) with the original location
::
operator
delete
[](
reinterpret_cast
<
size_t
*>
(
as
)
-
1
);
}
return
0
;
}
這個版本里,在new[]時GCC額外給陣列分配了1個size_t大小的隱藏欄位來記錄陣列元素的個數,並且在new[]之後會按順序呼叫建構函式;而在delete[]之前逆序會迴圈呼叫解構函式,然後再呼叫全域性的operator delete[] (size_t sz)。
在GCC中,貫穿於C++的operator new[] (size_t sz)的實現程式碼中的“關鍵點”是“ VEC_NEW_EXPR”。用這個詞搜尋GCC的程式碼就可以找到關鍵的實現程式碼。
例如說,重點之一在這裡:
gcc/init。c at 374fac5db1e3c3a2622705ae097f8685af34d1a4 · gcc-mirror/gcc · GitHub
/* Generate code for a new-expression, including calling the “operator
new” function, initializing the object, and, if an exception occurs
during construction, cleaning up。 The arguments are as for
build_raw_new_expr。 This may change PLACEMENT and INIT。 */
static
tree
build_new_1
(
vec
<
tree
,
va_gc
>
**
placement
,
tree
type
,
tree
nelts
,
vec
<
tree
,
va_gc
>
**
init
,
bool
globally_qualified_p
,
tsubst_flags_t
complain
)
其中從2621行開始是global operator new的處理,它會檢測該陣列是否需要“cookie”。這個“cookie”就是題主想找的C++語義層面的“額外資訊”。
(TYPE_VEC_NEW_USES_COOKIE):
New macro to indicate when vec new must add a header containing the number of elements in the vector; i。e。 when the elements need to be destroyed or vec delete wants to know the size。
/* Use a global operator new。 */
/* See if a cookie might be required。 */
if
(
!
(
array_p
&&
TYPE_VEC_NEW_USES_COOKIE
(
elt_type
)))
這個宏的定義是:
gcc/cp-tree。h at b594087e1afe41db9d100f08644715702d6cfc1b · gcc-mirror/gcc · GitHub
/* Nonzero if `new NODE[x]‘ should cause the allocation of extra
storage to indicate how many array elements are in use。 */
#define TYPE_VEC_NEW_USES_COOKIE(NODE) \
(CLASS_TYPE_P (NODE) \
&& LANG_TYPE_CLASS_CHECK (NODE)->vec_new_uses_cookie)
===================================================================
關於operator new[] (size_t sz)的實現,參考某個版本的GCC對應的libstdc++ / libsupc++吧。
全域性的operator new[] (size_t sz)在這裡(這個是throw版;nothrow版在對應的
http://
new_opvnt。cc
裡):
gcc/new_opv。cc at master · gcc-mirror/gcc · GitHub
_GLIBCXX_WEAK_DEFINITION
void
*
operator
new
[]
(
std
::
size_t
sz
)
_GLIBCXX_THROW
(
std
::
bad_alloc
)
{
return
::
operator
new
(
sz
);
}
直接轉交給operator new(size_t sz)了。多狡猾 >_<
然後是全域性的operator new (size_t sz)
gcc/new_op。cc at master · gcc-mirror/gcc · GitHub
_GLIBCXX_WEAK_DEFINITION
void
*
operator
new
(
std
::
size_t
sz
)
_GLIBCXX_THROW
(
std
::
bad_alloc
)
{
void
*
p
;
/* malloc (0) is unpredictable; avoid it。 */
if
(
sz
==
0
)
sz
=
1
;
while
(
__builtin_expect
((
p
=
malloc
(
sz
))
==
0
,
false
))
{
new_handler
handler
=
std
::
get_new_handler
();
if
(
!
handler
)
_GLIBCXX_THROW_OR_ABORT
(
bad_alloc
());
handler
();
}
return
p
;
}
正常路徑上本質上就是呼叫了libc的malloc(size_t sz)而已,額外資訊也不是在這裡寫入的。
同理,預設的全域性oeprator delete[] (size_t sz)也是直接轉交給operator delete (size_t sz),後者則轉交給libc的free()。
至於說placement new,預設行為是什麼也不做:
gcc/new at master · gcc-mirror/gcc · GitHub
// Default placement versions of operator new。
inline
void
*
operator
new
(
std
::
size_t
,
void
*
__p
)
_GLIBCXX_USE_NOEXCEPT
{
return
__p
;
}
inline
void
*
operator
new
[](
std
::
size_t
,
void
*
__p
)
_GLIBCXX_USE_NOEXCEPT
{
return
__p
;
}
所以跟儲存相關的“額外資訊”——某個指標所指向的動態申請的儲存空間到底有多大——就要看malloc()的實現了。然而底下的malloc()實現是可以變的,例如說可以動態跟jemalloc、tcmalloc連結上,也可以用glibc自己的malloc。大家未必會用同一種方式來記錄“額外資訊”。
題主可以另外開個問題問malloc()在哪裡記錄“額外資訊”⋯
我用的這個老MacBook Air上的Mac OS X自帶的libc在libSystem裡,其中的malloc叫做magazine_malloc。可以參考這裡的說明:Cocoa with Love: A look at how malloc works on the Mac
大學學C++的時候做過簡單的表象研究。
似乎用GCC的話,它把new的陣列長度存在了指標
之前
1個int的位置。
所以猜想實現的方式是在new的時候多申請幾個位元組,用來放陣列長度(也許還有更多,用來放單位的size?因為delete[]會逐一呼叫解構函式,所以不僅需要知道陣列的byte length還需要知道單位個數),然後最終返回的時候就偏移一下。
using namespace std;
static int c ;
struct A {
A(){
i = c ++;
cerr << __FILE__ << “:” << __LINE__ << “: [” << __FUNCTION__<< “] ”
<< endl;
}
~A(){
cerr << __FILE__ << “:” << __LINE__ << “: [” << __FUNCTION__<< “] ”
<< endl;
}
long long i;
};
void bar(A * p)
{
delete []p;
}
int main(int argc, char *argv[])
{
A * p = new A[3];
bar(p);
return 0;
}
這是一段例子程式碼。編譯之
g++ -fno-inline O3 -ggdb main。c
開始用 gdb 除錯
bash$ gdb ~/tmp/a。out
(gdb) b bar
Breakpoint 1 at 0x400ce0: file nn。cc, line 24。
(gdb) run
Starting program: /home/xxx/tmp/a。out
nn。cc:13: [A]
nn。cc:13: [A]
nn。cc:13: [A]
Breakpoint 1, bar (p=0x602018) at nn。cc:24
24 delete []p;
(gdb) disassemble
Dump of assembler code for function bar(A*):
=> 0x0000000000400c40 <+0>: test %rdi,%rdi
0x0000000000400c43 <+3>: je 0x400c80
0x0000000000400c45 <+5>: push %rbp
0x0000000000400c46 <+6>: push %rbx
0x0000000000400c47 <+7>: mov %rdi,%rbp
0x0000000000400c4a <+10>: sub $0x8,%rsp
0x0000000000400c4e <+14>: mov -0x8(%rdi),%rax 讀取陣列長度到 $rax
0x0000000000400c52 <+18>: lea (%rdi,%rax,8),%rbx 讀取末尾指標位置到 $rbx
0x0000000000400c56 <+22>: cmp %rbx,%rdi 是否到達尾指標
0x0000000000400c59 <+25>: je 0x400c71
0x0000000000400c5b <+27>: nopl 0x0(%rax,%rax,1)
0x0000000000400c60 <+32>: sub $0x8,%rbx 尾指標 —— <-
0x0000000000400c64 <+36>: mov %rbx,%rdi 設定 this 指標 |
0x0000000000400c67 <+39>: callq 0x400d00 呼叫解構函式 |
0x0000000000400c6c <+44>: cmp %rbx,%rbp 是否到頭指標了 |
0x0000000000400c6f <+47>: jne 0x400c60
0x0000000000400c71 <+49>: add $0x8,%rsp
0x0000000000400c75 <+53>: lea -0x8(%rbp),%rdi
0x0000000000400c79 <+57>: pop %rbx
0x0000000000400c7a <+58>: pop %rbp
0x0000000000400c7b <+59>: jmpq 0x400900 <_ZdaPv@plt>
0x0000000000400c80 <+64>: repz retq
End of assembler dump。
(gdb) x /10xg $rdi -8
0x602010: 0x0000000000000003 0x0000000000000000
0x602020: 0x0000000000000001 0x0000000000000002
0x602030: 0x0000000000000000 0x0000000000020fd1
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
我加些註釋
1。 ’-fno-inline 取消 inline 的最佳化,讓彙編出來的 `bar` 函式變短一一些
2。 ‘-O3’ 最佳化過的彙編看起來更加容易
3。 暫存器 `$rdi` 儲存的是第一個引數 p,
4。 用 x 命令檢視記憶體,看到 p 前面有一個8位元組表示長度 0x602010 那裡。
5。 gcc 是倒著呼叫解構函式的。就是先析構 p[2], p[1], 最後析構 p[0]
6。 linux 下 X86 64 引數的傳遞是用暫存器傳遞的, $rdi, $rsi, $rcx, $rdx, $r9, $r10, windows 下的不是這樣的,沒有實驗過。
總結,陣列長度就在 p[-1]那裡,但是我不確定這個是標準,高度懷疑不是標準,是編譯器實現相關。
4有兩種辦法 1。儲存好你編譯的c檔案,假設你的檔名是main。c,這是原始檔,咱們要把它編譯成方針檔案即以。o結束的檔案(gcc -c main。c) ,再運用ll main*就能夠檢視到生成的方針檔案,再將方針檔案生成可履行檔案(gcc -o main main。o) ,這兒用main代替生成的可履行檔案,再運用ll main*就能夠檢視到生成的可履行檔案main,最後就是履行可履行檔案了(sh main),這樣就能夠得到成果了。 2。能夠一步到位,直接生成可履行檔案,gcc -o main main。o,履行辦法如辦法一,用辦法一能夠看到具體的履行程序,主張運用辦法1期望對你有所協助!
函式庫一般在/usr/lib/目錄下
標頭檔案一般在/usr/include/目錄下我也遇到了這樣的問題,你用for句子初始化的結果是正確的麼?for (row = 0; row < n; row ) for (col = 0; col < n; col ) squcer[row][col] = 0;不知你也是用這樣的辦法初始化的麼?我是這樣初始化的,檢查squcer[row][col]值好像是記憶體地址。之後,我就別離界說了兩個變數對應行和列int m, n;int squcer[m][n]scanf(“%d %d”, &m, &n)這樣就能正確初始化了。 ==========================================================================你看這就是我一運轉之後就報錯了。 拜訪違例(段反常)過錯原因是:沒有給二維陣列分配記憶體空間,用樓上朋友的辦法,先界說一個指向指標的指標,然後malloc函式給二維變長陣列拓荒記憶體空間,初始化就正確了。你把你的程式碼貼出來了,我在電腦上運轉一下。