GCC-3.4.6源代码学习笔记(38)

来源:互联网 发布:人机界面触摸屏编程 编辑:程序博客网 时间:2024/05/08 12:10

4.1.3.1.2.3.1.1.        #pragma interface#pragma implementation

作为GCC提供的一个C++的扩展,【6】解释了#pragma interface “filename” (op) #pragma implementation “filename” (op)

6.3 模糊链接性(Vague Linkage

C++中有一些构造要求在目标文件中分配空间,但又不明确地绑定于单个编译单元。我们称这些构造具有“模糊链接性”(vague linkage)。典型地,这类结构在所需处被产生,虽然有时我们可以更聪智些。

内联函数

内联函数通常定义在一个会被多个编译单元所包含的头文件中。理想地它们通常可以被内联,但有时一个非内联的拷贝(an out-of-line copy)是必须的,如果函数被取址或内联失败。一般地,如果需要,我们在所有的编译单元中产生一个非内联拷贝。作为一个例外,我们仅通过虚表(vtable)产生内联虚函数,因为总是需要它的一个拷贝。

用在一个内联函数里的局部静态变量及字符串常量,也被视作具有模糊链接性,因为它们必须被函数所有的内联及非内联(out-of-line)实例所共享。

虚表(Vtables

在大多数编译器中,C++虚函数的实现,使用了一个被称为虚表(vtable)的查找表。这个虚表包含了指向一个类所支持的虚函数的指针,并且这个类的每个实例都包含指向它的虚表(或多个虚表,在一些多继承的情况下)的指针。如果该类声明了任意非内联,非纯虚函数,选择第一个作为类的“关键方法”(key method),并且这个虚表仅产生在关键方法被定义的编译单元中。

注意:如果选定的关键方法随后被定义为内联,这个虚表仍然会在每个定义它的编译单元中产生。确保任一内联虚函数在类体内声明为内联,即便它们不在那里定义。

类型信息对象(type info object

为了实现‘dynamic_casttypeid及异常处理,C++要求输出类型的信息。对于多态的类(polymorphic classe)——具有虚函数的类,类型信息对象与虚表写在一起,因此dynamic_cast可以确定运行时一个类对象的动态类型(dynamic type)。对于所有其他类型,当它使用时我们写入类型信息:即当对一个表达式应用typeid,抛出一个对象,或在一个catch条款(catch clause)或异常规范中引用的一个类型。

模板具现(Template Instantiation

在这一节中的一切几乎都可应用于模板具现,不过还是有一些其他的选项。

当在例如GNU/LinuxSolaris 2 ELF系统上,或者在 Microsoft Windows上使用GNU ld版本2.8或更新的版本,在链接时刻这些构造的重复的拷贝将被丢弃。这就是所谓的COMDAT支持。在不支持COMDAT,但支持弱符号(weak symbol)的目标平台上,GCC 将使用弱符号。这样一个拷贝将废除(override)其他拷贝,但在执行文件中未使用的拷贝将仍旧占据空间。

对于不支持COMDAT及弱符号的目标平台,绝大多数具有模糊链接性的实体将被作为局部符号产生,以避免链接器产生重复定义的错误。不过在内联函数中,对于局部静态变量,这不会发生,因为具有多个拷贝几乎总是坏事(break things)。

6.4 #pragma interfaceimplementation

#pragma interface#pragma implementation给用户提供了一个显式指导编译器,在一个特定的编译单元中,产生具有模糊链接性(及调试信息)的实体的途径。

注意:截止GCC 2.7.2,这些#pragmas在大部分情况下没有用处,因为6.3 [模糊链接性]中提到的COMDAT的支持及“关键方法”的启发。

使用它们确实会导致你的程序变大,因为内联函数不必要的out-of-line拷贝。当前(3.4)这些#pragmas仅有的好处是减少了重复的调试信息,而DWARF 2格式通过使用COMDAT组(group)将很快解决这个问题。

#pragma interface

#pragma interface "subdir/objects.h"

在定义了目标类的头文件中使用这个指示,以在大多数使用了这些类的目标文件中节省空间。通常,某些信息的局部拷贝(内联成员函数的备用拷贝,调试信息,及实现虚函数的内部表)必须被保存在每个包含类定义的目标文件中。你可以使用这个pragma来避免这样的重复。当一个包含#pragma interface的头文件被包含入一个编译单元,这个辅助信息不会产生(除非主源文件自己使用了‘#pragma implementation)。相反,目标文件将包含链接时解析(resolve)的引用。

这个指示的第二个形式,对于有多个同名在不同目录下的头文件的情况,是有用的。如果你使用这个形式,你必须对#pragma implementation指定同样的字符串。

#pragma implementation

#pragma implementation "objects.h"

当你希望从包含的头文件产生完整的输出(并使得全局可见),在主输入文件中使用这个pragma。反过来,被包含的头文件应该使用#pragma interface。内联成员函数的备用拷贝,调试信息及实现虚函数的内部表都被产生在实现文件中。

如果使用不带参数的#pragma implementation,它应用到与源文件同名的包含文件中。例如,在allclass.cc中,只给出#pragma implementation等同于#pragma implementation "allclass.h"

GNU C++版本2.6.0之前,每当从allclass.cc包含allclass.h,即便不指明#pragma implementationallclass.h也被作为实现文件来处理。然而这被认为得不偿失,已被禁止。

如果希望单个实现文件从多个头文件包含代码,使用字符串参数。(你必须也使用#include来包含头文件;#pragma implementation仅指明如何使用文件——它不实际包含它)。

没有办法将单个头文件的内容分开入多个实现文件中。

#pragma implementation#pragma interface同样对函数内联有影响。

如果在一个标记了#pragma interface 头文件中定义一个类,对于在这个类中定义的内联函数的影响,与显式的extern声明相似——编译器不为这个函数的独立版本产生任何代码。它的定义仅用于与它的调用者内联时。

相反,当在一个声明为#pragma implementation的主源文件中包含同一个头文件时,编译器为该函数产生代码;这定义了函数的一个可以通过指针来查找的版本(或可被按非内联编译的调用者使用)。如果对这个函数的所有调用都可以被内联,可以通过编译选项-fno-implement-inlines来避免生成这个函数。但如果任一调用不能内联,将会产生链接器错误。

因此在GCC中,interface_only如果非0,表示我们处在编译器的一个“接口”段(section)。而interface_unknown如果非0,表示我们不能相信interface_only的值。

词法分析器相应的处理句柄可以帮助我们理解,这些句柄分别处理#pragma interface#pragma implementation

 

526    static void

527    handle_pragma_interface (cpp_reader* dfile ATTRIBUTE_UNUSED )                in lex.c

528    {

529      tree fname = parse_strconst_pragma ("interface", 1);

530      struct c_fileinfo *finfo;

531      const char *main_filename;

532   

533      if (fname == (tree)-1)

534        return;

535      else if (fname == 0)

536        main_filename = lbasename (input_filename);

537      else

538        main_filename = TREE_STRING_POINTER (fname);

539   

540      finfo = get_fileinfo (input_filename);

541   

542      if (impl_file_chain == 0)

543      {

544        /* If this is zero at this point, then we are

545          auto-implementing.  */

546        if (main_input_filename == 0)

547          main_input_filename = input_filename;

548      }

549   

550      interface_only = interface_strcmp (main_filename);

551    #ifdef MULTIPLE_SYMBOL_SPACES

552      if (! interface_only)

553    #endif

554        interface_unknown = 0;

555   

556      finfo->interface_only = interface_only;

557      finfo->interface_unknown = interface_unknown;

558    }

 

我们已经看到,GCC使用splay树来为每个文件保存它的打开路径,并且树中伴随这个路径的值具有以下类型c_fileinfo

 

1284 struct c_fileinfo                                                                                  in c-common.h

1285 {

1286   int time;    /* Time spent in the file.  */

1287   short interface_only;        /* Flags - used only by C++ */

1288   short interface_unknown;

1289 };

 

注意下面125126行的interface_onlyinterface_unknown

 

113     struct c_fileinfo *

114     get_fileinfo (const char *name)                                                                              in lex.c

115     {

116       splay_tree_node n;

117       struct c_fileinfo *fi;

118    

119       n = splay_tree_lookup (file_info_tree, (splay_tree_key) name);

120      if (n)

121        return (struct c_fileinfo *) n->value;

122   

123      fi = xmalloc (sizeof (struct c_fileinfo));

124      fi->time = 0;

125      fi->interface_only = 0;

126      fi->interface_unknown = 1;

127      splay_tree_insert (file_info_tree, (splay_tree_key) name,

128                     (splay_tree_value) fi);

129      return fi;

130    }

 

接着在550行,interface_strcmp返回0,如果同一个名字亦被声明为实现。事实上, interface_strcmpimpl_file_chain中查找,由其名字所暗示这是个实现文件组成的链。这个链无疑是由#pragma implementation的处理句柄所构建的。

 

568    static void

569    handle_pragma_implementation (cpp_reader* dfile ATTRIBUTE_UNUSED )       in lex.c

570    {

571      tree fname = parse_strconst_pragma ("implementation", 1);

572      const char *main_filename;

573      struct impl_files *ifiles = impl_file_chain;

574   

575      if (fname == (tree)-1)

576        return;

577   

578      if (fname == 0)

579      {

580        if (main_input_filename)

581          main_filename = main_input_filename;

582        else

583          main_filename = input_filename;

584        main_filename = lbasename (main_filename);

585      }

586      else

587      {

588        main_filename = TREE_STRING_POINTER (fname);

589        if (cpp_included (parse_in, main_filename))

590          warning ("#pragma implementation for %s appears after file is included",

591                  main_filename);

592      }

593   

594      for (; ifiles; ifiles = ifiles->next)

595      {

596        if (! strcmp (ifiles->filename, main_filename))

597          break;

598      }

599      if (ifiles == 0)

600      {

601        ifiles = xmalloc (sizeof (struct impl_files));

602        ifiles->filename = main_filename;

603        ifiles->next = impl_file_chain;

604        impl_file_chain = ifiles;

605      }

606    }

 

那么看回handle_pragma_interface。因为调用interface_strcmp来确定文件是否仅为接口,这意味着当声明一个文件为实现部分时,#pragma implementation需在头文件中出现在#include指示前。同样看handle_pragma_implementation590行,在#pragma implementation出现在#include指示后的情况下,将给出警告。

这些pragmas在读入源文件的同时得到处理。因此,当完成文件读入时,可以从文件对应的c_fileinfo结构中,通过extract_interface_info提前接口有关的信息。

 

436    void

437    extract_interface_info (void)                                                                           in lex.c

438    {

439      struct c_fileinfo *finfo;

440   

441      finfo = get_fileinfo (input_filename);

442      interface_only = finfo->interface_only;

443     interface_unknown = finfo->interface_unknown;

444    }

 

回到cb_file_change,现在我们为主文件创建了ENTER块,1509行的条件不满足。

 

原创粉丝点击