首页 > 其他分享 >gcc对构造函数的调用生成

gcc对构造函数的调用生成

时间:2023-10-19 21:13:41浏览次数:46  
标签:gcc 调用 name tree binding scope type class 构造函数

identifier

C++的前端对identifier做了扩展,在每个identifier中还包含了两个cxx_binding字段:namespace_bindings和bindings。当通过字符串找到一个identifier的时候,同时顺带获得了两个binding信息。

/* Language-dependent contents of an identifier.  */

struct GTY(()) lang_identifier {
  struct c_common_identifier c_common;
  cxx_binding *namespace_bindings;
  cxx_binding *bindings;
  tree class_template_info;
  tree label_value;
};

事实上,在LANG_HOOKS_INITIALIZER的定义中,第一个字段是语言的名字,而第二个字段就是语言中identifier的大小,可以这个结构还是相当重要的?

/* The whole thing.  The structure is defined in langhooks.h.  */
#define LANG_HOOKS_INITIALIZER { \
  LANG_HOOKS_NAME, \
  LANG_HOOKS_IDENTIFIER_SIZE, \
  LANG_HOOKS_FREE_LANG_DATA, \
  LANG_HOOKS_TREE_SIZE, \

bingding

在cxx_binding结构中,有value和type两个字段,注释也比较贴心,就是这个标识符的类型和值信息。

由于同一个identifier在不同的scope中可能有不同的意义,所以结构中还包含了previous字段将所有scope中同名的标识符串在一起放在链表中。

struct cxx_binding {
  /* Link to chain together various bindings for this name.  */
  cxx_binding *previous;
  /* The non-type entity this name is bound to.  */
  tree value;
  /* The type entity this name is bound to.  */
  tree type;
  /* The scope at which this binding was made.  */
  cp_binding_level *scope;
  unsigned value_is_inherited : 1;
  unsigned is_local : 1;
};

创建

当语法分析到一个变量/类型/函数等声明的时候,会为这个标识符创建一个对应的binding。在push_binding函数中会记录这个标识符的声明(decl),并把它放在链表的最开始(And put it on the front of the list of bindings for ID)。

/* Make DECL the innermost binding for ID.  The LEVEL is the binding
   level at which this declaration is being bound.  */

void
push_binding (tree id, tree decl, cp_binding_level* level)
{
  cxx_binding *binding;

  if (level != class_binding_level)
    {
      binding = cxx_binding_make (decl, NULL_TREE);
      binding->scope = level;
    }
  else
    binding = new_class_binding (id, decl, /*type=*/NULL_TREE, level);

  /* Now, fill in the binding information.  */
  binding->previous = IDENTIFIER_BINDING (id);
  INHERITED_VALUE_BINDING_P (binding) = 0;
  LOCAL_BINDING_P (binding) = (level != class_binding_level);

  /* And put it on the front of the list of bindings for ID.  */
  IDENTIFIER_BINDING (id) = binding;
}

查找

在语法解析的时候,如果遇到一个新的标识符,首先会根据这个标识符的字符串找到对应的结构,然后从当前scope逐层向外搜索这个标识符的信息。 实现上看主要就是从查找到的identifier的binding信息逐层向外找到最近的标识符声明。

/* Look up NAME in the current binding level and its superiors in the
   namespace of variables, functions and typedefs.  Return a ..._DECL
   node of some kind representing its definition if there is only one
   such declaration, or return a TREE_LIST with all the overloaded
   definitions if there are many, or return 0 if it is undefined.
   Hidden name, either friend declaration or built-in function, are
   not ignored.

   If PREFER_TYPE is > 0, we prefer TYPE_DECLs or namespaces.
   If PREFER_TYPE is > 1, we reject non-type decls (e.g. namespaces).
   Otherwise we prefer non-TYPE_DECLs.

   If NONCLASS is nonzero, bindings in class scopes are ignored.  If
   BLOCK_P is false, bindings in block scopes are ignored.  */

static tree
lookup_name_real_1 (tree name, int prefer_type, int nonclass, bool block_p,
		    int namespaces_only, int flags)
{
///...
  if (block_p || !nonclass)
    for (iter = outer_binding (name, NULL, !nonclass);
	 iter;
	 iter = outer_binding (name, iter, !nonclass))
      {
	tree binding;

	/* Skip entities we don't want.  */
	if (LOCAL_BINDING_P (iter) ? !block_p : nonclass)
	  continue;

	/* If this is the kind of thing we're looking for, we're done.  */
	if (qualify_lookup (iter->value, flags))
	  binding = iter->value;
	else if ((flags & LOOKUP_PREFER_TYPES)
		 && qualify_lookup (iter->type, flags))
	  binding = iter->type;
	else
	  binding = NULL_TREE;

	if (binding)
	  {
	    if (hidden_name_p (binding))
	      {
		/* A non namespace-scope binding can only be hidden in the
		   presence of a local class, due to friend declarations.

		   In particular, consider:

		   struct C;
		   void f() {
		     struct A {
		       friend struct B;
		       friend struct C;
		       void g() {
		         B* b; // error: B is hidden
			 C* c; // OK, finds ::C
		       } 
		     };
		     B *b;  // error: B is hidden
		     C *c;  // OK, finds ::C
		     struct B {};
		     B *bb; // OK
		   }

		   The standard says that "B" is a local class in "f"
		   (but not nested within "A") -- but that name lookup
		   for "B" does not find this declaration until it is
		   declared directly with "f".

		   In particular:

		   [class.friend]

		   If a friend declaration appears in a local class and
		   the name specified is an unqualified name, a prior
		   declaration is looked up without considering scopes
		   that are outside the innermost enclosing non-class
		   scope. For a friend function declaration, if there is
		   no prior declaration, the program is ill-formed. For a
		   friend class declaration, if there is no prior
		   declaration, the class that is specified belongs to the
		   innermost enclosing non-class scope, but if it is
		   subsequently referenced, its name is not found by name
		   lookup until a matching declaration is provided in the
		   innermost enclosing nonclass scope.

		   So just keep looking for a non-hidden binding.
		*/
		gcc_assert (TREE_CODE (binding) == TYPE_DECL);
		continue;
	      }
	    val = binding;
	    break;
	  }
      }
///...
}
/* Return the innermost non-namespace binding for NAME from a scope
   containing BINDING, or, if BINDING is NULL, the current scope.
   Please note that for a given template, the template parameters are
   considered to be in the scope containing the current scope.
   If CLASS_P is false, then class bindings are ignored.  */

cxx_binding *
outer_binding (tree name,
	       cxx_binding *binding,
	       bool class_p)
{
  cxx_binding *outer;
  cp_binding_level *scope;
  cp_binding_level *outer_scope;

  if (binding)
    {
      scope = binding->scope->level_chain;
      outer = binding->previous;
    }
  else
    {
      scope = current_binding_level;
      outer = IDENTIFIER_BINDING (name);
    }
    ///...
}

生效

以C++最为关心的struct/class为例,在解析完成类似于struct Tsecer;这样合法的结构之后,会立即调用xref_tag来创建一个对于这个类型的前置声明。这种实现感觉也是crtp的一个实现基础:在派生类的名字解析完成之后,派生类的tag已经可用,此时可以在基类中把该类型作为模板参数使用。

/* Get the struct, enum or union (TAG_CODE says which) with tag NAME.
   Define the tag as a forward-reference if it is not defined.

   If a declaration is given, process it here, and report an error if
   multiple declarations are not identical.

   SCOPE is TS_CURRENT when this is also a definition.  Only look in
   the current frame for the name (since C++ allows new names in any
   scope.)  It is TS_WITHIN_ENCLOSING_NON_CLASS if this is a friend
   declaration.  Only look beginning from the current scope outward up
   till the nearest non-class scope.  Otherwise it is TS_GLOBAL.

   TEMPLATE_HEADER_P is true when this declaration is preceded by
   a set of template parameters.  */

static tree
xref_tag_1 (enum tag_types tag_code, tree name,
            tag_scope orig_scope, bool template_header_p)
{
///...
	  t = make_class_type (code);
	  TYPE_CONTEXT (t) = context;
	  if (orig_scope == ts_lambda)
	    /* Remember that we're declaring a lambda to avoid bogus errors
	       in push_template_decl.  */
	    CLASSTYPE_LAMBDA_EXPR (t) = error_mark_node;
	  t = pushtag (name, t, scope);
///...
}

构造函数

在cp_finish_decl==>>check_initializer函数中,如果一个变量声明时有Init初始化内容,则会判断是否需要调用构造函数。

/* Verify INIT (the initializer for DECL), and record the
   initialization in DECL_INITIAL, if appropriate.  CLEANUP is as for
   grok_reference_init.

   If the return value is non-NULL, it is an expression that must be
   evaluated dynamically to initialize DECL.  */

static tree
check_initializer (tree decl, tree init, int flags, vec<tree, va_gc> **cleanups)
{
///...
      if (((type_build_ctor_call (type) || CLASS_TYPE_P (type))
	   && !(flags & LOOKUP_ALREADY_DIGESTED)
	   && !(init && BRACE_ENCLOSED_INITIALIZER_P (init)
		&& CP_AGGREGATE_TYPE_P (type)
		&& (CLASS_TYPE_P (type)
		    || !TYPE_NEEDS_CONSTRUCTING (type)
		    || type_has_extended_temps (type))))
	  || (DECL_DECOMPOSITION_P (decl) && TREE_CODE (type) == ARRAY_TYPE))
	{
	  init_code = build_aggr_init_full_exprs (decl, init, flags);

	  /* A constructor call is a non-trivial initializer even if
	     it isn't explicitly written.  */
	  if (TREE_SIDE_EFFECTS (init_code))
	    DECL_NONTRIVIALLY_INITIALIZED_P (decl) = true;
	  ///...
}

check_initializer>>build_aggr_init_full_exprs >>build_aggr_init>>expand_aggr_init_1>>expand_default_init==>>build_special_member_call最终会触发对于构造函数的调用。

static void
expand_default_init (tree binfo, tree true_exp, tree exp, tree init, int flags,
                     tsubst_flags_t complain)
{
///...
      if (true_exp == exp)
	ctor_name = complete_ctor_identifier;
      else
	ctor_name = base_ctor_identifier;
      rval = build_special_member_call (exp, ctor_name, &parms, binfo, flags,
					complain);
///...
}

结论

  • 构造和赋值

构造函数会在变量定义的时候判断是否是需要执行构造函数。同样一个对象,在声明时和之后赋值虽然都是用赋值,但是在此处有不同的语义。
在下面代码中,第19行的内容除了和18行开始的声明不一样之外,赋值符号左右的内容都是相同的,但是因为一个是声明上下文,一个是赋值上下文,编译器执行的动作也大相径庭:一个是拷贝构造,一个赋值。

tsecer@harry: cat -n context.cpp                         
     1  #include <stdio.h>
     2  struct Tsecer
     3  {
     4      Tsecer() = default;
     5
     6      Tsecer(const Tsecer &t)
     7      {
     8          printf("copy constructor\n");
     9      }
    10      Tsecer& operator = (const Tsecer &t)
    11      {
    12          printf("assign operator\n");
    13      }
    14  };
    15
    16  int main(int argc, const char *argv[])
    17  {
    18      Tsecer t1 = Tsecer();
    19      t1 = Tsecer();
    20      return 0;
    21  }
    22
tsecer@harry: g++ -fno-elide-constructors context.cpp            
tsecer@harry: ./a.out 
copy constructor
assign operator
tsecer@harry: 
  • 标识符

gcc对于字符串形式的identifier在不同的scope中具体代表什么意义的实现比较巧妙,但也还算比较直观,只是稍微有一点绕,如果不了解这个实现机制,有些地方代码阅读的时候可能会觉得不容易理解。

标签:gcc,调用,name,tree,binding,scope,type,class,构造函数
From: https://www.cnblogs.com/tsecer/p/17775645.html

相关文章

  • Vue源码学习(十二):列队处理(防抖优化,多次调用,只处理一次)
    好家伙, 本篇讲的是数据更新请求列队处理 1.一些性能问题数据更新的核心方法是watcher.updata方法实际上也就是vm._updata()方法,vm._updata()方法中的patch()方法用于将新的虚拟DOM树与旧的虚拟DOM树进行比较,并将差异更新到实际的DOM树上.这一步是非常消耗性能的 2.......
  • 解决winform调用wpf窗体时原窗体缩小的问题
    在使用winform调用wpf窗体时,原来的winform窗体会缩小,同时分辨率会发生变化,用如下方法来解决这个问题。方法一、首先找到winform项目中的Properties ==>AssemblyInfo.cs,打开该文件,在末尾加入如下代码,之后重新运行即可。[assembly:System.Windows.Media.DisableDpiAwareness]/......
  • python调用企业微信发送消息
    #-*-coding:utf-8-*-importosfromrequestsimportrequestfromloguruimportloggerimportbase64importhashlibimportreclassWechatBot:"""企业微信机器人当前自定义机器人支持文本(text)、markdown(markdown)、图片(image)、图文(news),文件(file)五种消息类型。......
  • 调用Python的openpyxl包对Excel表格进行美化
    Python中运用openpyxl包对Excel表格进行美化,包括字体样式调整、单元格对齐方式调整、单元格边框调整、单元格背景颜色调整、行高和列宽调整。使用的Python中openpyxl包的版本为3.0.5先看实际美化前后的效果对比详细的开发代码如下,代码当中对关键信息进行了说明。复制代码......
  • 前端歌谣-第十柒课-链式调用
    前言我是歌谣最好的种树是十年前其次是现在今天继续给大家带来的是链式调用的讲解环境配置npminit-yyarnaddvite-D修改page.json配置端口{"name":"demo1","version":"1.0.0","description":"","main":"index.js",......
  • P7077 [CSP-S2020] 函数调用
    显然函数之间的调用关系形成了一张拓扑图,预处理出函数\(i\)或其内部所有乘法之积\(mul_i\)。在调用一个加法函数后调用一个乘法函数,等价于先调用这个乘法函数,然后调用这个加法函数乘数次。所以不妨让乘法函数先做,剩下加法函数产生的贡献只取决于加数和调用次数。这里和线段树......
  • 【发现一个问题】macos m2 下无法使用 x86_64-linux-musl-gcc 链接含有 avx512 指令
    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!cnblogs博客zhihuGithub公众号:一本正经的瞎扯一开始是使用golang中的cgo来编译:envCC=cCGO_ENABLED=1GOOS=linuxGOARCH=amd64\CGO_CFLAGS="-mavx-mavx2-mavx512f-mavx512vl-mavx512bw-O2"\gobu......
  • C#调用WPS的COM组件操作
    本应比较简单直接调用,Office6下面的文件件etapi.dll(excel) ksoapi.dll(office)  wpsapi.dll(word)wppapi(powerpoint) 互操作类型-》false结果是管理员启动(Runas时提示Com组件未注册)  类似 检索COM类工厂中CLSID为{000209FF-0000-4B30-A977-D214852036FE}的组......
  • 函数调用栈-执行上下文栈
    一调用栈我们知道栈的特点是,先进后出的。那么函数的执行上下文栈又是怎么样的呢?先看这段代码vara=2functionp2(b,c){returnb+c;}functionp1(b,c){vard=10;result=p2(b,c);returna+result+d;//2+9+10}console.log(p1(3,6));//21由于js代码是运行时编......
  • 如何将没有复制或移动构造函数的对象放入vector容器
    正文直接说答案,这个问题无法实现。原因是因为std::vector容器的插入一定会调用类对象的构造函数或者移动构造函数。说一下为什么会有这个问题,因为不想用指针,我想直接通过类对象本身的RAII机制来实现的资源的控制,智能指针是一个解决方案,不过智能指针是写起来很繁琐,终究比不上值类......