GCC 下 C++ 中 new int[] 記憶體的額外資訊在哪裡?RednaxelaFX2015-06-26 09:44:30

嗯題主想像的“額外資訊”不是在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

GCC 下 C++ 中 new int[] 記憶體的額外資訊在哪裡?Jim Liu2015-06-26 11:29:49

大學學C++的時候做過簡單的表象研究。

似乎用GCC的話,它把new的陣列長度存在了指標

之前

1個int的位置。

所以猜想實現的方式是在new的時候多申請幾個位元組,用來放陣列長度(也許還有更多,用來放單位的size?因為delete[]會逐一呼叫解構函式,所以不僅需要知道陣列的byte length還需要知道單位個數),然後最終返回的時候就偏移一下。

GCC 下 C++ 中 new int[] 記憶體的額外資訊在哪裡?wcy1232015-06-27 20:55:57

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]那裡,但是我不確定這個是標準,高度懷疑不是標準,是編譯器實現相關。

GCC 下 C++ 中 new int[] 記憶體的額外資訊在哪裡?夏紀年2018-06-08 00:34:32

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期望對你有所協助!

GCC 下 C++ 中 new int[] 記憶體的額外資訊在哪裡?Loki2018-06-08 00:53:30

函式庫一般在/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函式給二維變長陣列拓荒記憶體空間,初始化就正確了。你把你的程式碼貼出來了,我在電腦上運轉一下。