注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

从new T后是否加()说起  

2015-12-23 20:09:33|  分类: Gcc源代码分析 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、C++规范对于这个文档的说明
《ANSI C++ reference》1995 4月28号版本
12.6 Initialization
1 If T is either a class type or an array of class type, an object of type T is default-initialized (8.5) if:
— the object has static storage duration and no initializer is specified in its declaration (see 8.5), or
— the object is created with a new-expression of the form new T() (see 5.3.4), or
— the object is a temporary object created using the functional notation for type conversions T() (see 5.2.3), or
— the object is a subobject, either a base of type T or a member m of type T, of a class object being created by a constructor that specifies a mem-initializer of the form T() or m(), respectively (see 12.6.2).
2 Furthermore, if an object of class type T (or array thereof)
— has automatic storage duration and no initializer is specified in its declaration, or
— is created with a new-expression with an omitted new-initializer (see 5.3.4), or
— is a subobject, either a base of type T or a member m of type T (or array thereof), of a class object created by a constructor that does not specify a mem-initializer for T or m, respectively (see 12.6.2),then that object (or, for an array, each element of the array) shall be initialized by the default constructor for T (and the initialization is ill-formed if T has no accessible default constructor).
这里大致说明了我们比较关心的几种情况:一个是在new操作符之后在类型T的后面添加了一个括号,另一个是临时变量中使用类型名T后面添加括号,这是我们最为常见的两种需要关注的类型。在这种情况下,标准明确说明了要使用default-initialized(注意,这里并不是调用default constructor),而这里的default-initialized通俗的说就是0初始化。对于没有加括号的函数执行new,那么此时调用的就是缺省的构造函数,这个也可能是由编译器生成的,就是空。

在文档的8.5节,对这个0初始化进行了说明:
8.5 Initializers
5 To zero-initialize storage for an object of type T means:
— if T is a scalar or pointer-to-member type, the storage is set to the value of 0 (zero) converted to T;
— if T is a non-union class type, the storage for each nonstatic data member and each base-class subobject
is zero-initialized;
— if T is a union type, the storage for its first nonstatic data member is zero-initialized;
— if T is an array type, the storage for each element is zero-initialized;
— if T is a reference type, no initialization is performed.
To default-initialize an object of type T means:
— if T is a non-POD class type, the default constructor for T is called (and the initialization is ill-formed if
T has no accessible default constructor);
— if T is an array type, each element is default-initialized;
— otherwise, the storage for the object is zero-initialized.
Default-initialization uses the direct-initialization semantics described below.

这里再补充一个有意思的问题,前面文档说明了对于new T()、T()形式的临时变量,以及静态形式的 T t形式的变量的初始化使用default-initialized进行了明确的说明,那么对于我们通常使用的堆栈变量(编译器叫做自动变量,auto),这种形式是否也可以调用default-initialized呢?这其实并不像这个问题本身看起来那么有意思,因为这本身就是一个伪命题。在同样的文档规范中,说明了这种形式是一种非法—更准确的说是另一种语义,在文档的8.5节,说明了这种形式的声明是一种函数声明,而不是对于函数default-initialized的调用:
9 [Note: since () is not permitted by the syntax for initializer,
X a();
is not the declaration of an object of class X, but the declaration of a function taking no argument and returning an X. The form () is permitted in certain other initialization contexts (5.3.4, 5.2.3, 12.6.2). ]
二、简单验证下:
tsecer @harry: cat definit.cpp 
#include <stdio.h>

int main()
{
int *p1 = new int, *p2 = new int();
printf("%d %d\n", *p1, *p2);
}
tsecer @harry: g++ definit.cpp -S
tsecer @harry: cat definit.s | c++filt | head -40
        .file   "definit.cpp"
        .section        .rodata
.LC0:
        .string "%d %d\n"
        .text
        .align 2
.globl main
        .type   main, @function
main:
.LFB2:
        pushq   %rbp
.LCFI0:
        movq    %rsp, %rbp
.LCFI1:
        subq    $16, %rsp
.LCFI2:
        movl    $4, %edi
        call    operator new(unsigned long)
        movq    %rax, -16(%rbp)
        movl    $4, %edi
        call    operator new(unsigned long)
        movl    $0, (%rax)  注意这个地方进行了0初始化,而前面的new函数之后没有
        movq    %rax, -8(%rbp)
        movq    -8(%rbp), %rax
        movl    (%rax), %edx
        movq    -16(%rbp), %rax
        movl    (%rax), %esi
        movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
        movl    $0, %eax
        leave
        ret
这里要说的是,即时两个都输出0,也没有任何说明的实质性意义,因为libc通过mmap从操作系统分配空间,而这些分配的内存默认都是0初始化的。
三、gcc对于这个的简单处理
static tree
build_new_1 (tree exp)
{
……
 if (init == void_zero_node)
   init = build_default_init (full_type, nelts);
……
}
在解析的时候如果没有指明初始化表达式,表达式列表为void_zero_node,
static tree
cp_parser_new_initializer (cp_parser* parser)
{
  tree expression_list;

  expression_list = (cp_parser_parenthesized_expression_list
    (parser, false, /*cast_p=*/false,
     /*non_constant_p=*/NULL));
  if (!expression_list)
    expression_list = void_zero_node;

  return expression_list;
}
其它情况下
static tree
cp_parser_new_expression (cp_parser* parser)
……
  /* If the next token is a `(', then we have a new-initializer.  */
  if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_PAREN))
    initializer = cp_parser_new_initializer (parser);
  else
    initializer = NULL_TREE;
四、再复杂一点的结构
tsecer@harry: cat definit.cpp 
#include <string.h>
#include <stdio.h>
struct Base
{
        int i;
        Base():i(0x22222222)
        {
        }
};

struct Cont
{
        Base b;
        int i;
        void *operator new(size_t s, void * paddr)
        {
                return paddr;
        }
};

int main()
{
        Cont stC;
        memset(&stC, 0x11, sizeof(stC));
        Cont *pc = new ((void*)&stC)Cont;
        printf("pc->i %X pc->b.i %X\n", pc->i, pc->b.i);
        pc->i = 0x12345678;
        pc=new ((void*)&stC)Cont();
        printf("pc->i %X pc->b.i %X\n", pc->i, pc->b.i);
}
tsecer@harry: g++ definit.cpp 
tsecer@harry: ./a.out 
pc->i 11111111 pc->b.i 22222222
pc->i 12345678 pc->b.i 22222222 这个输出用不同的编译器输出结构可能不同(即有的编译器输出可能为0),但是gcc version 4.1.2 20070115 (prerelease) (SUSE Linux)这个版本可以看到并没有主动0初始化POD成员。
tsecer@harry: 
标准说明,大致说来,就是说系统提供的隐式构造函数,它只会调用有构造函数的基类或者成员类的构造函数,其它的POD类型并不会被作任何初始化。或者说:隐式定义缺省构造函数会且仅会调用有自定义构造函数的基类或者派生类的缺省构造函数。
Implicitly-defined default constructor
If the implicitly-declared default constructor is not defined as deleted, it is defined (that is, a function body is generated and compiled) by the compiler, and it has exactly the same effect as a user-defined constructor with empty body and empty initializer list. That is, it calls the default constructors of the bases and of the non-static members of this class.
If some user-defined constructors are present, the user may still force the automatic generation of a default constructor by the compiler that would be implicitly-declared otherwise with the keyword default.

五、在什么时候这些问题有意义
通常在模版类(例如C++的stl库),或者框架类中这些问题才有意义。记得之前使用过一个map,大致key是一个类型id,value包含了了所有属于该类型的元素及元素的和,例如
struct kind
{
int sum;
int arr[10];
};
希望在插入的第一个kind元素的时候这个sum为0,这个文档里对于这个的说明
23.3.1.5 map element access
T& operator[](const key_type& x);
Returns: (*((m.insert(make_pair( x , T()))).first)).second.
这里的T()就是使用了缺省初始化,也就是可以保证这些值都是0;
大家是不是感觉今天有get到一个没用的技能了 :-) ?
  评论这张
 
阅读(106)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017