peakflys原創(chuàng)作品,轉(zhuǎn)載請保留原作者和源鏈接!
上午一個師弟在QQ上問我一道筆試題,是他前兩天去KONAMI面試時做的,這道題大致是這樣的:
解釋以下語句的含義:
1、new A;
2、new A(); 也許很多人包括我自己,都可以馬上給出第一種情況的答案:在堆上為A類分配內(nèi)存,然后調(diào)用A的構(gòu)造函數(shù)。這種說法被大家所熟知,因為包括《STL源碼剖析》等大作在內(nèi)也都是這么寫的(但是你認為這種說法完全正確嗎?
其實不盡然,答案后面揭曉) 第二種情況,對象構(gòu)造的時候初始化列表為空會和第一種有什么不同呢?對于這種在實際工程中很少使用的情況,我一時還真給不出確切的答案。
網(wǎng)上搜了一下,看到CSDN里面還有專門針對這個問題的一個帖子(原帖鏈接 http://bbs.csdn.net/topics/320161716)。
好像最終也沒有可以信服的答案,認同度比較高的是這樣的說法:“
加括號調(diào)用沒有參數(shù)的構(gòu)造函數(shù),不加括號調(diào)用默認構(gòu)造函數(shù)或唯一的構(gòu)造函數(shù),看需求” (peakflys注:這種說法是錯誤的,答案后面揭曉)
既然沒有特別靠譜的答案,不如自己動手找出答案。
構(gòu)造以下示例:
/**
*\brief example1 difference between new and new()
*\author peakflys
*\data 12:10:24 Monday, April 08, 2013
*/
class A
{
public:
int a;
};
int main()
{
A *pa = new A;
A *paa = new A();
return 0;
}
查看main函數(shù)的匯編代碼(編譯器:gcc (GCC) 4.4.6 20120305 (Red Hat 4.4.6-4) )
int main()
{
4005c4: 55 push %rbp
4005c5: 48 89 e5 mov %rsp,%rbp
4005c8: 48 83 ec 10 sub $0x10,%rsp
A *pa = new A;
4005cc: bf 04 00 00 00 mov $0x4,%edi
4005d1: e8 f2 fe ff ff callq 4004c8 <_Znwm@plt> //調(diào)用new
4005d6: 48 89 45 f0 mov %rax,-0x10(%rbp) //rax寄存器內(nèi)容賦給指針pa(rax寄存器里是new調(diào)用產(chǎn)生的A對象堆內(nèi)存地址)
A *paa = new A();
4005da: bf 04 00 00 00 mov $0x4,%edi
4005df: e8 e4 fe ff ff callq 4004c8 <_Znwm@plt> //調(diào)用new
4005e4: 48 89 c2 mov %rax,%rdx //rax的內(nèi)容放入rdx,執(zhí)行之后,rdx里存放的即是通過new A()產(chǎn)生的內(nèi)存地址
4005e7: c7 02 00 00 00 00 movl $0x0,(%rdx) //把rdx內(nèi)存指向的內(nèi)容賦為0值,即把A::a賦值為0
4005ed: 48 89 45 f8 mov %rax,-0x8(%rbp) //rax寄存器內(nèi)容賦給指針paa(rax寄存器里是new()調(diào)用產(chǎn)生的A對象堆內(nèi)存地址)
return 0;
4005f1: b8 00 00 00 00 mov $0x0,%eax
}
4005f6: c9 leaveq
4005f7: c3 retq
通過上面產(chǎn)生的匯編代碼(對AT&T匯編不熟悉的可以看注釋)可以很容易看出,
new A()的執(zhí)行,在調(diào)用完operator new分配內(nèi)存后,馬上對新分配內(nèi)存中的對象使用0值初始化,而new A 僅僅是調(diào)用了operator new分配內(nèi)存!
是不是這樣就可以下結(jié)論 new A()比new A多了一步,即初始化對象的步驟呢?
我們再看看下面這種情況:
/**
*\brief example2 difference between new and new()
*\author peakflys
*\data 12:23:20 Monday, April 08, 2013
*/
class A
{
public:
A(){a = 10;}
int a;
};
int main()
{
A *pa = new A;
A *paa = new A();
return 0;
}
這種情況是類顯示提供含默認值的構(gòu)造函數(shù)。
查看匯編實現(xiàn)如下:
int main()
{
4005c4: 55 push %rbp
4005c5: 48 89 e5 mov %rsp,%rbp
4005c8: 53 push %rbx
4005c9: 48 83 ec 18 sub $0x18,%rsp
A *pa = new A;
4005cd: bf 04 00 00 00 mov $0x4,%edi
4005d2: e8 f1 fe ff ff callq 4004c8 <_Znwm@plt>
4005d7: 48 89 c3 mov %rax,%rbx
4005da: 48 89 d8 mov %rbx,%rax
4005dd: 48 89 c7 mov %rax,%rdi
4005e0: e8 2d 00 00 00 callq 400612 <_ZN1AC1Ev>
4005e5: 48 89 5d e0 mov %rbx,-0x20(%rbp)
A *paa = new A();
4005e9: bf 04 00 00 00 mov $0x4,%edi
4005ee: e8 d5 fe ff ff callq 4004c8 <_Znwm@plt>
4005f3: 48 89 c3 mov %rax,%rbx
4005f6: 48 89 d8 mov %rbx,%rax
4005f9: 48 89 c7 mov %rax,%rdi
4005fc: e8 11 00 00 00 callq 400612 <_ZN1AC1Ev>
400601: 48 89 5d e8 mov %rbx,-0x18(%rbp)
return 0;
400605: b8 00 00 00 00 mov $0x0,%eax
}
40060a: 48 83 c4 18 add $0x18,%rsp
40060e: 5b pop %rbx
40060f: c9 leaveq
400610: c3 retq
上面的匯編代碼就不在添加注釋了,因為兩種操作產(chǎn)生的匯編代碼是一樣的,都是先調(diào)用operator new分配內(nèi)存,然后調(diào)用構(gòu)造函數(shù)。
上面的情況在VS2010下驗證是一樣的情況,有興趣的朋友可以自己去看,這里就不再貼出VS2010下的匯編代碼了。
通過上面的分析,對于
new A和 new A() 的區(qū)別,我們可以得出下面的結(jié)論:
1、類體含有顯示適合地默認構(gòu)造函數(shù)時,new A和new A()的作用一致,都是首先調(diào)用operator new分配內(nèi)存,然后調(diào)用默認構(gòu)造函數(shù)初始化對象。 2、類體無顯示構(gòu)造函數(shù)時,new A()首先調(diào)用operator new來為對象分配內(nèi)存,然后使用空值初始化對象成員變量,而new A僅僅是調(diào)用operator new分配內(nèi)存,對象的成員變量是無意義的隨機值! (peakflys注:對于基本數(shù)據(jù)類型,如int等 適用此條)
注意到,現(xiàn)在很多書籍對new操作符的說明都存在紕漏,例如《STL源碼剖析》中2.2.2節(jié)中有以下的描述:

事實證明,new Foo的操作是否有構(gòu)造函數(shù)的調(diào)用是不確定的,具體要看Foo類體里是否有顯示構(gòu)造函數(shù)的出現(xiàn)。
by peakflys 13:40:00 Monday, April 08, 2013
/*****************************************華麗分割線**************************************
補充:剛才發(fā)現(xiàn),在C++Primer第四版5.11節(jié)中,已經(jīng)有了對于new A()的說明:
We indicate that we want to value-initialize the newly allocated object by following the type nameby a pair of empty parentheses. The empty parentheses signal that we want initialization but arenot supplying a specific initial value. In the case of class types (such as string) that define their own constructors, requesting value-initialization is of no consequence: The object is initialized by running the default constructor whether we leave it apparently uninitialized orask for value-initialization. In the case of built-in types or types that do not define any constructors, the difference is significant:
int *pi = new int; // pi points to an uninitialized int
int *pi = new int(); // pi points to an int value-initialized to 0
In the first case, the int is uninitialized; in the second case, the int is initialized to zero.
這里給出的解釋和上面自己分析的new A()的行為是一致的。
/***************************************再次華麗分割線************************************
鑒于上面的結(jié)論是通過GCC和VS2010得出的,而且有朋友也提出同樣的質(zhì)疑,為了確定這種結(jié)果是否是編譯器相關(guān)的,剛才特意查看了一下C++的標準化文檔。
摘自:ISO/IEC 14882:2003(E) 5.3.4 - 15
— If the new-initializer is omitted:
— If T is a (possibly cv-qualified) non-POD class type (or array thereof), the object is default-initialized(8.5). If T is a const-qualified type, the underlying class type shall have a user-declared default constructor.
— Otherwise, the object created has indeterminate value. If T is a const-qualified type, or a (possibly cv-qualified) POD class type (or array thereof) containing (directly or indirectly) a member of const-qualified type, the program is ill-formed;
— If the new-initializer is of the form (), the item is value-initialized (8.5);
所以可以確定,這種情況完全是編譯器無關(guān)的(當然那些不完全按照標準實現(xiàn)的編譯器除外)。
但是通過上面標準化文檔的描述,我們可以看出文中對new A在無顯示構(gòu)造函數(shù)時的總結(jié)并不是特別全面,鑒于很多公司都有這道面試題(撇去這些題目的實際考察意義不說),我們有必要再補充一下: 對于new A: 這樣的語句,再調(diào)用完operator new分配內(nèi)存之后,如果A類體內(nèi)含有POD類型,則POD類型的成員變量處于未定義狀態(tài),如果含有非POD類型則調(diào)用該類型的默認構(gòu)造函數(shù)。而 new A()在這些情況下都會初始化。
PS:估計很多公司的“正確答案“ 也不一定正確吧。