zoukankan      html  css  js  c++  java
  • gcc虚函数表生成时机

    一、虚函数表
    每个包含有虚函数的类都会有一个所有对象共享的虚函数表,既然是所有实例共享,那么就涉及到可能出现“三个和尚没水喝”的情况。这个虚函数表既然是大家都要使用的,那么有谁来生成呢?最为保险但是低效的办法就是每个编译单元都生成一个,变量声明为weak,放入comdat节中,最后由连接器删除重复的冗余内容。但是这样对于一些类的实现可能分散于各个不同编译单元来说,明显是增加了编译器、连接器及磁盘的使用时间,所以gcc并没有使用这种方法。
    根据C++语言的规定,一个类中的所有虚函数都必须有对应的实现(并且只有一个?)。根据这个定义,gcc对虚函数表定义的实现做了优化,只有在一个类的第一个非纯虚、非内联函数的定义所在的编译单元中进行该类的虚函数表存储空间的定义。反过来说,如果一个类的所有虚函数都是内敛的,那么这个类的虚函数定义将会散布在所有包含给类定义的编译单元中。
    对于一个促使一个类实例化自己虚函数表的方法(method),gcc中称这类方法为key_method,如果一个类不包含这样的方法,那么它可能的虚函数可能就要放在所有的编译单元了。
    下面是stackoverflow中关于这个问题的一个讨论http://stackoverflow.com/questions/2182738/debugging-vtable-linker-errors-in-gcc?1347698484,按照惯例,把其内容摘抄一份过来,其中说明了编译器为一个类提供的确认构造函数、析构函数、赋值函数及虚函数表都是在这个函数中完成的。

    From gcc faq:

    When building C++, the linker says my constructors, destructors or virtual tables are undefined, but I defined them

    The ISO C++ Standard specifies that all virtual methods of a class that are not pure-virtual must be defined, but does not require any diagnostic for violations of this rule [class.virtual]/8. Based on this assumption, GCC will only emit the implicitly defined constructors, the assignment operator, the destructor and the virtual table of a class in the translation unit that defines its first such non-inline method.(这里并没有说如果一个类不包含任何虚函数的时候它的缺省构造函数在哪里定义)

    Therefore, if you fail to define this particular method, the linker may complain about the lack of definitions for apparently unrelated symbols. Unfortunately, in order to improve this error message, it might be necessary to change the linker, and this can't always be done.

    The solution is to ensure that all virtual methods that are not pure are defined. Note that a destructor must be defined even if it is declared pure-virtual [class.dtor]/7.

    The solution that I adopt is search the classname and seek virtual methods declaration and check if there is any definition. I didn't found any other solution for this.

    二、当函数中一个方法声明解析结束时(以下基于gcc4.20版本)
    finish_member_declaration
    Breakpoint 10, finish_member_declaration (decl=0xb7da4f50)
        at ../../gcc-4.2.0/gcc/cp/semantics.c:2216
    2216    {
    (gdb) bt
    #0  finish_member_declaration (decl=0xb7da4f50)
        at ../../gcc-4.2.0/gcc/cp/semantics.c:2216
    #1  0x080c13dd in cp_parser_member_declaration (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:14070
    #2  0x080c0a83 in cp_parser_member_specification_opt (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:13684
    #3  0x080bffb2 in cp_parser_class_specifier (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:13148
    #4  0x080bbbf9 in cp_parser_type_specifier (parser=0xb7da916c, 
        flags=CP_PARSER_FLAGS_OPTIONAL, decl_specs=0xbffff154, 
        is_declaration=1 '01', declares_class_or_enum=0xbffff10c, 
        is_cv_qualifier=0xbffff10b "") at ../../gcc-4.2.0/gcc/cp/parser.c:9712
    #5  0x080b934d in cp_parser_decl_specifier_seq (parser=0xb7da916c, 
        flags=CP_PARSER_FLAGS_OPTIONAL, decl_specs=0xbffff154, 
        declares_class_or_enum=0xbffff150) at ../../gcc-4.2.0/gcc/cp/parser.c:7656
    #6  0x080b8e35 in cp_parser_simple_declaration (parser=0xb7da916c, 
        function_definition_allowed_p=1 '01')
        at ../../gcc-4.2.0/gcc/cp/parser.c:7358
    #7  0x080b8ded in cp_parser_block_declaration (parser=0xb7da916c, 
        statement_p=0 '00') at ../../gcc-4.2.0/gcc/cp/parser.c:7319
    #8  0x080b8c39 in cp_parser_declaration (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:7235
    #9  0x080b88e1 in cp_parser_declaration_seq_opt (parser=0xb7da916c)
    ---Type <return> to continue, or q <return> to quit---
        at ../../gcc-4.2.0/gcc/cp/parser.c:7130
    #10 0x080b2cb0 in cp_parser_translation_unit (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:2845
    #11 0x080c9021 in c_parse_file () at ../../gcc-4.2.0/gcc/cp/parser.c:19400
    #12 0x08154229 in c_common_parse_file (set_yydebug=0)
        at ../../gcc-4.2.0/gcc/c-opts.c:1182
    #13 0x08511898 in compile_file () at ../../gcc-4.2.0/gcc/toplev.c:1033
    #14 0x085130ae in do_compile () at ../../gcc-4.2.0/gcc/toplev.c:2006
    #15 0x08513113 in toplev_main (argc=2, argv=0xbffff3c4)
        at ../../gcc-4.2.0/gcc/toplev.c:2038
    #16 0x08160557 in main (argc=Cannot access memory at address 0x4
    ) at ../../gcc-4.2.0/gcc/main.c:35

      /* Put functions on the TYPE_METHODS list and everything else on the
         TYPE_FIELDS list.  Note that these are built up in reverse order.
         We reverse them (to obtain declaration order) in finish_struct.  */
      if (TREE_CODE (decl) == FUNCTION_DECL
          || DECL_FUNCTION_TEMPLATE_P (decl))
        {
          /* We also need to add this function to the
         CLASSTYPE_METHOD_VEC.  */
          if (add_method (current_class_type, decl, NULL_TREE))
        {
          TREE_CHAIN (decl) = TYPE_METHODS (current_class_type);
          TYPE_METHODS (current_class_type) = decl;其中current_class_type是当前正在解析的类的gcc内部表示,decl是刚刚解析出来的类中的方法声明

          maybe_add_class_template_decl_list (current_class_type, decl,
                              /*friend_p=*/0);
        }
        }
    三、主方法的确定
    (gdb) bt
    #0  determine_key_method (type=0xb7da6564) at ../../gcc-4.2.0/gcc/cp/class.c:4940
    #1  0x0809c6dc in finish_struct_1 (t=0xb7da6564)
        at ../../gcc-4.2.0/gcc/cp/class.c:5014
    #2  0x0809cda9 in finish_struct (t=0xb7da6564, attributes=0x0)
        at ../../gcc-4.2.0/gcc/cp/class.c:5227
    #3  0x080c0032 in cp_parser_class_specifier (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:13160
    #4  0x080bbbf9 in cp_parser_type_specifier (parser=0xb7da916c, 
        flags=CP_PARSER_FLAGS_OPTIONAL, decl_specs=0xbffff154, 
        is_declaration=1 '01', declares_class_or_enum=0xbffff10c, 
        is_cv_qualifier=0xbffff10b "") at ../../gcc-4.2.0/gcc/cp/parser.c:9712
    #5  0x080b934d in cp_parser_decl_specifier_seq (parser=0xb7da916c, 
        flags=CP_PARSER_FLAGS_OPTIONAL, decl_specs=0xbffff154, 
        declares_class_or_enum=0xbffff150) at ../../gcc-4.2.0/gcc/cp/parser.c:7656
    #6  0x080b8e35 in cp_parser_simple_declaration (parser=0xb7da916c, 
        function_definition_allowed_p=1 '01')
        at ../../gcc-4.2.0/gcc/cp/parser.c:7358
    #7  0x080b8ded in cp_parser_block_declaration (parser=0xb7da916c, 
        statement_p=0 '00') at ../../gcc-4.2.0/gcc/cp/parser.c:7319
    #8  0x080b8c39 in cp_parser_declaration (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:7235
    #9  0x080b88e1 in cp_parser_declaration_seq_opt (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:7130
    ---Type <return> to continue, or q <return> to quit---
    #10 0x080b2cb0 in cp_parser_translation_unit (parser=0xb7da916c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:2845
    #11 0x080c9021 in c_parse_file () at ../../gcc-4.2.0/gcc/cp/parser.c:19400
    #12 0x08154229 in c_common_parse_file (set_yydebug=0)
        at ../../gcc-4.2.0/gcc/c-opts.c:1182
    #13 0x08511898 in compile_file () at ../../gcc-4.2.0/gcc/toplev.c:1033
    #14 0x085130ae in do_compile () at ../../gcc-4.2.0/gcc/toplev.c:2006
    #15 0x08513113 in toplev_main (argc=2, argv=0xbffff3c4)
        at ../../gcc-4.2.0/gcc/toplev.c:2038
    #16 0x08160557 in main (argc=Cannot access memory at address 0x0


     /* Find the key method.  */
      if (TYPE_CONTAINS_VPTR_P (t))
        {
          /* The Itanium C++ ABI permits the key method to be chosen when
         the class is defined -- even though the key method so
         selected may later turn out to be an inline function.  On
         some systems (such as ARM Symbian OS) the key method cannot
         be determined until the end of the translation unit.  On such
         systems, we leave CLASSTYPE_KEY_METHOD set to NULL, which
         will cause the class to be added to KEYED_CLASSES.  Then, in
         finish_file we will determine the key method.  */
          if (targetm.cxx.key_method_may_be_inline ())
        determine_key_method (t);

          /* If a polymorphic class has no key method, we may emit the vtable
         in every translation unit where the class definition appears.  */
          if (CLASSTYPE_KEY_METHOD (t) == NULL_TREE)如果没有主方法,则整个类成为keyed_class
        keyed_classes = tree_cons (NULL_TREE, t, keyed_classes);
        }
    void
    determine_key_method (tree type)
    {
      tree method;

      if (TYPE_FOR_JAVA (type)
          || processing_template_decl
          || CLASSTYPE_TEMPLATE_INSTANTIATION (type)
          || CLASSTYPE_INTERFACE_KNOWN (type))
        return;

      /* The key method is the first non-pure virtual function that is not
         inline at the point of class definition
    .  On some targets the
         key function may not be inline; those targets should not call
         this function until the end of the translation unit.  */
      for (method = TYPE_METHODS (type); method != NULL_TREE;这里的TYPE_METHOD即是在前面的finish_member_declaration函数中完成添加的
           method = TREE_CHAIN (method))
        if (DECL_VINDEX (method) != NULL_TREE 是虚函数
        && ! DECL_DECLARED_INLINE_P (method) 非内联
        && ! DECL_PURE_VIRTUAL_P (method))非纯虚
          {
        CLASSTYPE_KEY_METHOD (type) = method;
        break;
          }

      return;
    }
    四、当函数定义实现时
    tree
    finish_function (int flags)
    {
      tree fndecl = current_function_decl;
      tree fntype, ctype = NULL_TREE;
      int inclass_inline = (flags & 2) != 0;
      int nested;

      /* When we get some parse errors, we can end up without a
         current_function_decl, so cope.  */
      if (fndecl == NULL_TREE)
        return error_mark_node;

      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
          && DECL_VIRTUAL_P (fndecl)
          && !processing_template_decl)
        {
          tree fnclass = DECL_CONTEXT (fndecl);
          if (fndecl == CLASSTYPE_KEY_METHOD (fnclass))
        keyed_classes = tree_cons (NULL_TREE, fnclass, keyed_classes);如果说该定义是一个函数的key方法,将一个类加入到全局的keyed_class中
        }
    五、如果类不包含虚函数
      1 struct epic
      2 {
      3 int x;
      4 epic():x(-1) {}
      5 };
      6 struct Base
      7 {
      8         int x;
      9         epic e;
     10         int dump();
     11 };
     12 int Base::dump()
     13 {
     14 return x;
     15 
     16 };
     17 int main()
     18 {
     19         Base b,c;
     20         b =c;
     21         return b.dump();
     22 }
    ~                    
    [tsecer@Harry noctor]$ nm noctor.o | c++filt 
    00000000 T Base::dump()
    00000000 W Base::Base()
    00000000 W epic::epic()
             U __gxx_personality_v0
    0000000a T main
    如果取消掉main函数中对于Base类的使用,输出的结果是这样的
    [tsecer@Harry noctor]$ nm noctor.o | c++filt 
    00000000 T Base::dump()
             U __gxx_personality_v0
    0000000a T main
    也就是一个类的缺省构造函数、析构函数及赋值函数是在需要的时候按需生成的,如果没有用到,可以不生成。
    gcc中lookup_fnfields_1 (tree type, tree name)函数实现
      if (COMPLETE_TYPE_P (type))
        {
          if ((name == ctor_identifier
           || name == base_ctor_identifier
           || name == complete_ctor_identifier))
        {
          if (CLASSTYPE_LAZY_DEFAULT_CTOR (type))
            lazily_declare_fn (sfk_constructor, type);
          if (CLASSTYPE_LAZY_COPY_CTOR (type))
            lazily_declare_fn (sfk_copy_constructor, type);
        }
          else if (name == ansi_assopname(NOP_EXPR)
               && CLASSTYPE_LAZY_ASSIGNMENT_OP (type))
        lazily_declare_fn (sfk_assignment_operator, type);
          else if ((name == dtor_identifier
            || name == base_dtor_identifier
            || name == complete_dtor_identifier
            || name == deleting_dtor_identifier)
               && CLASSTYPE_LAZY_DESTRUCTOR (type))
        lazily_declare_fn (sfk_destructor, type);
        }
    六、四种缺省函数的实现(以构造函数为例)
    #0  locate_ctor (type=0xb7da6564, client=0x0)
        at ../../gcc-4.2.0/gcc/cp/method.c:878
    #1  0x080eb8ce in synthesize_exception_spec (type=0xb7da6844, 
        extractor=0x80eb991 <locate_ctor>, client=0x0)
        at ../../gcc-4.2.0/gcc/cp/method.c:855
    #2  0x080ebec2 in implicitly_declare_fn (kind=sfk_constructor, type=0xb7da6844, 
        const_p=0 '00') at ../../gcc-4.2.0/gcc/cp/method.c:1029
    #3  0x080ec226 in lazily_declare_fn (sfk=sfk_constructor, type=0xb7da6844)
        at ../../gcc-4.2.0/gcc/cp/method.c:1129
    #4  0x080ee70f in lookup_fnfields_1 (type=0xb7da6844, name=0xb7d02ac4)
        at ../../gcc-4.2.0/gcc/cp/search.c:1385
    #5  0x080edd1c in lookup_field_r (binfo=0xb7ffe840, data=0xbfffec6c)
        at ../../gcc-4.2.0/gcc/cp/search.c:1057
    #6  0x080eed72 in dfs_walk_all (binfo=0xb7ffe840, 
        pre_fn=0x80edc87 <lookup_field_r>, post_fn=0, data=0xbfffec6c)
        at ../../gcc-4.2.0/gcc/cp/search.c:1551
    #7  0x080ee2b4 in lookup_member (xbasetype=0xb7ffe840, name=0xb7d02ac4, 
        protect=1, want_type=0 '00') at ../../gcc-4.2.0/gcc/cp/search.c:1233
    #8  0x080ee4d7 in lookup_fnfields (xbasetype=0xb7ffe840, name=0xb7d02ac4, 
        protect=1) at ../../gcc-4.2.0/gcc/cp/search.c:1315
    #9  0x080550d2 in build_special_member_call (instance=0xb7cfb420, 
        name=0xb7d02ac4, args=0x0, binfo=0xb7ffe840, flags=3)
        at ../../gcc-4.2.0/gcc/cp/call.c:5219
    ---Type <return> to continue, or q <return> to quit---
    #10 0x080e5bcc in expand_default_init (binfo=0xb7ffe840, true_exp=0xb7cfb420, 
        exp=0xb7cfb420, init=0x0, flags=3) at ../../gcc-4.2.0/gcc/cp/init.c:1221
    #11 0x080e5ced in expand_aggr_init_1 (binfo=0xb7ffe840, true_exp=0xb7cfb420, 
        exp=0xb7cfb420, init=0x0, flags=3) at ../../gcc-4.2.0/gcc/cp/init.c:1274
    #12 0x080e58d1 in build_aggr_init (exp=0xb7cfb420, init=0x0, flags=0)
        at ../../gcc-4.2.0/gcc/cp/init.c:1142
    #13 0x0806327d in check_initializer (decl=0xb7cfb420, init=0x0, flags=0, 
        cleanup=0xbfffee30) at ../../gcc-4.2.0/gcc/cp/decl.c:4863
    #14 0x080641b4 in cp_finish_decl (decl=0xb7cfb420, init=0x0, 
        init_const_expr_p=0 '00', asmspec_tree=0x0, flags=0)
        at ../../gcc-4.2.0/gcc/cp/decl.c:5246
    #15 0x080bdd1b in cp_parser_init_declarator (parser=0xb7da923c, 
        decl_specifiers=0xbfffeef4, checks=0x0, 
        function_definition_allowed_p=0 '00', member_p=0 '00', 
        declares_class_or_enum=0, function_definition_p=0xbfffeeef "")
        at ../../gcc-4.2.0/gcc/cp/parser.c:11344
    #16 0x080b8f84 in cp_parser_simple_declaration (parser=0xb7da923c, 
        function_definition_allowed_p=0 '00')
        at ../../gcc-4.2.0/gcc/cp/parser.c:7419
    #17 0x080b8ded in cp_parser_block_declaration (parser=0xb7da923c, 
        statement_p=1 '01') at ../../gcc-4.2.0/gcc/cp/parser.c:7319
    #18 0x080b8634 in cp_parser_declaration_statement (parser=0xb7da923c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:7004
    ---Type <return> to continue, or q <return> to quit---
    #19 0x080b7664 in cp_parser_statement (parser=0xb7da923c, in_statement_expr=0x0, 
        in_compound=1 '01') at ../../gcc-4.2.0/gcc/cp/parser.c:6331
    #20 0x080b7a07 in cp_parser_statement_seq_opt (parser=0xb7da923c, 
        in_statement_expr=0x0) at ../../gcc-4.2.0/gcc/cp/parser.c:6513
    #21 0x080b797d in cp_parser_compound_statement (parser=0xb7da923c, 
        in_statement_expr=0x0, in_try=0 '00')
        at ../../gcc-4.2.0/gcc/cp/parser.c:6483
    #22 0x080bf50f in cp_parser_function_body (parser=0xb7da923c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:12683
    #23 0x080bf53b in cp_parser_ctor_initializer_opt_and_function_body (
        parser=0xb7da923c) at ../../gcc-4.2.0/gcc/cp/parser.c:12700
    #24 0x080c32f5 in cp_parser_function_definition_after_declarator (
        parser=0xb7da923c, inline_p=0 '00') at ../../gcc-4.2.0/gcc/cp/parser.c:15634
    #25 0x080c31e0 in cp_parser_function_definition_from_specifiers_and_declarator (
        parser=0xb7da923c, decl_specifiers=0xbffff154, attributes=0x0, 
        declarator=0x885bb34) at ../../gcc-4.2.0/gcc/cp/parser.c:15570
    #26 0x080bd95c in cp_parser_init_declarator (parser=0xb7da923c, 
        decl_specifiers=0xbffff154, checks=0x0, 
        function_definition_allowed_p=1 '01', member_p=0 '00', 
        declares_class_or_enum=0, function_definition_p=0xbffff14f "01")
        at ../../gcc-4.2.0/gcc/cp/parser.c:11179
    #27 0x080b8f84 in cp_parser_simple_declaration (parser=0xb7da923c, 
        function_definition_allowed_p=1 '01')
    ---Type <return> to continue, or q <return> to quit---
        at ../../gcc-4.2.0/gcc/cp/parser.c:7419
    #28 0x080b8ded in cp_parser_block_declaration (parser=0xb7da923c, 
        statement_p=0 '00') at ../../gcc-4.2.0/gcc/cp/parser.c:7319
    #29 0x080b8c39 in cp_parser_declaration (parser=0xb7da923c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:7235
    #30 0x080b88e1 in cp_parser_declaration_seq_opt (parser=0xb7da923c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:7130
    #31 0x080b2cb0 in cp_parser_translation_unit (parser=0xb7da923c)
        at ../../gcc-4.2.0/gcc/cp/parser.c:2845
    #32 0x080c9021 in c_parse_file () at ../../gcc-4.2.0/gcc/cp/parser.c:19400
    #33 0x08154229 in c_common_parse_file (set_yydebug=0)
        at ../../gcc-4.2.0/gcc/c-opts.c:1182
    #34 0x08511898 in compile_file () at ../../gcc-4.2.0/gcc/toplev.c:1033
    #35 0x085130ae in do_compile () at ../../gcc-4.2.0/gcc/toplev.c:2006
    #36 0x08513113 in toplev_main (argc=2, argv=0xbffff3c4)
        at ../../gcc-4.2.0/gcc/toplev.c:2038
    #37 0x08160557 in main (argc=0, argv=0x0) at ../../gcc-4.2.0/gcc/main.c:35

    其中最为关键的就是synthesize_exception_spec函数,它会遍历一个类的所有的基类和自己所有的成员类,然后对它们执行函数中传入的参数extractor,而这个函数一般就是提取出各个父类和成员的缺省构造函数
    static tree
    synthesize_exception_spec (tree type, tree (*extractor) (tree, void*),
                   void *client)
    {
      tree raises = empty_except_spec;
      tree fields = TYPE_FIELDS (type);
      tree binfo, base_binfo;
      int i;

      for (binfo = TYPE_BINFO (type), i = 0;
           BINFO_BASE_ITERATE (binfo, i, base_binfo); i++)
        {
          tree fn = (*extractor) (BINFO_TYPE (base_binfo), client);
          if (fn)
        {
          tree fn_raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn));

          raises = merge_exception_specifiers (raises, fn_raises);
        }
        }
      for (; fields; fields = TREE_CHAIN (fields))
        {
          tree type = TREE_TYPE (fields);
          tree fn;

          if (TREE_CODE (fields) != FIELD_DECL || DECL_ARTIFICIAL (fields))
        continue;
          while (TREE_CODE (type) == ARRAY_TYPE)
        type = TREE_TYPE (type);
          if (!CLASS_TYPE_P (type))
        continue;

          fn = (*extractor) (type, client);
          if (fn)
        {
          tree fn_raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fn));

          raises = merge_exception_specifiers (raises, fn_raises);
        }
        }
      return raises;
    }

  • 相关阅读:
    P1410 子序列
    P1395 会议 (树形dp)
    P2580 于是他错误的点名开始了
    LC1127. 用户购买平台
    LC 1308. Running Total for Different Genders
    P1340 兽径管理 (最小生成树)
    P1330 封锁阳光大学 (二分图染色)
    CF1296F Berland Beauty (Tree, dfs/bfs, LCA)
    顺丰的Cookie条款
    服务器判断客户端的用户名和密码(token的身份验证)
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487453.html
Copyright © 2011-2022 走看看