面对现实,超越自己
逆水行舟,不进则退
posts - 269,comments - 32,trackbacks - 0

   解释以下语句的含义:
         1
new A;
         2
new A();    
   也许很多人包括我自己,都可以马上给出第一种情况的答案:在堆上为A类分配内存,然后调用A的构造函数。这种说法被大家所熟知,因为包括《STL源码剖析》等大作在内也都是这么写的(但是你认为这种说法完全正确吗?
其实不尽然,答案后面揭晓)
    第二种情况,对象构造的时候初始化列表为空会和第一种有什么不同呢?对于这种在实际工程中很少使用的情况,我一时还真给不出确切的答案。

   网上搜了一下,看到CSDN里面还有专门针对这个问题的一个帖子(原帖链接 http://bbs.csdn.net/topics/320161716)。

   好像最终也没有可以信服的答案,认同度比较高的是这样的说法:
加括号调用没有参数的构造函数,不加括号调用默认构造函数或唯一的构造函数,看需求peakflys注:这种说法是错误的,答案后面揭晓)
   既然没有特别靠谱的答案,不如自己动手找出答案。

   构造以下示例:

/**
 *\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函数的汇编代码(编译器: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>         //调用new
  4005d6:   48 89 45 f0             mov    %rax,-0x10(%rbp)           //rax寄存器内容赋给指针pa(rax寄存器里是new调用产生的A对象堆内存地址)
    A *paa = new A();
  4005da:   bf 04 00 00 00          mov    $0x4,%edi
  4005df:   e8 e4 fe ff ff          callq  4004c8 <_Znwm@plt>         //调用new
  4005e4:   48 89 c2                mov    %rax,%rdx                      //rax的内容放入rdx,执行之后,rdx里存放的即是通过new A()产生的内存地址
  4005e7:   c7 02 00 00 00 00       movl   $0x0,(%rdx)                 //把rdx内存指向的内容赋为0值,即把A::a赋值为0
  4005ed:   48 89 45 f8             mov    %rax,-0x8(%rbp)             //rax寄存器内容赋给指针paa(rax寄存器里是new()调用产生的A对象堆内存地址)
     return 0;
  4005f1:   b8 00 00 00 00          mov    $0x0,%eax
}
  4005f6:   c9                      leaveq 
  4005f7:   c3                      retq
    通过上面产生的汇编代码(AT&T汇编不熟悉的可以看注释)可以很容易看出,new A()的执行,在调用完operator new分配内存后,马上对新分配内存中的对象使用0值初始化,而new A 仅仅是调用了operator new分配内存!
   是不是这样就可以下结论 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;
}

   这种情况是类显示提供含默认值的构造函数。
   查看汇编实现如下:

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 

   上面的汇编代码就不在添加注释了,因为两种操作产生的汇编代码是一样的,都是先调用operator new分配内存,然后调用构造函数。
   上面的情况在VS2010下验证是一样的情况,有兴趣的朋友可以自己去看,这里就不再贴出VS2010下的汇编代码了。

   通过上面的分析,对于
new A new A() 的区别,我们可以得出下面的结论:
      
1、类体含有显示适合地默认构造函数时,new Anew A()的作用一致,都是首先调用operator new分配内存,然后调用默认构造函数初始化对象。
     2、类体无显示构造函数时,new A()首先调用operator new来为对象分配内存,然后使用空值初始化对象成员变量,而new A仅仅是调用operator new分配内存,对象的成员变量是无意义的随机值!  peakflys注:对于基本数据类型,如int 适用此条)
   注意到,现在很多书籍对new操作符的说明都存在纰漏,例如《STL源码剖析》中2.2.2节中有以下的描述:


事实证明,new Foo的操作是否有构造函数的调用是不确定的,具体要看Foo类体里是否有显示构造函数的出现。

/*****************************************华丽分割线**************************************
补充:刚才发现,在C++Primer第四版5.11节中,已经有了对于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()的行为是一致的。

/***************************************再次华丽分割线************************************
鉴于上面的结论是通过GCCVS2010得出的,而且有朋友也提出同样的质疑,为了确定这种结果是否是编译器相关的,刚才特意查看了一下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);

所以可以确定,这种情况完全是编译器无关的(当然那些不完全按照标准实现的编译器除外)
但是通过上面标准化文档的描述,我们可以看出文中对new A在无显示构造函数时的总结并不是特别准确,鉴于很多公司都有这道面试题(撇去这些题目的实际考察意义不说),我们有必要再补充一下: 对于new A: 这样的语句,再调用完operator new分配内存之后,如果A类体内含有POD类型,则POD类型的成员变量处于未定义状态,如果含有非POD类型则调用该类型的默认构造函数。而 new A()在这些情况下都会初始化。
   PS:估计很多公司的正确答案“ 也不一定正确吧。

 

本文转自:http://www.cppblog.com/peakflys/archive/2013/04/08/199208.html

 

posted on 2013-04-18 11:03 王海光 阅读(438) 评论(0)  编辑 收藏 引用 所属分类: 其他

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理