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_cast,typeid及异常处理,C++要求输出类型的信息。对于多态的类(polymorphic classe)——具有虚函数的类,类型信息对象与虚表写在一起,因此dynamic_cast可以确定运行时一个类对象的动态类型(dynamic type)。对于所有其他类型,当它使用时我们写入类型信息:即当对一个表达式应用typeid,抛出一个对象,或在一个catch条款(catch clause)或异常规范中引用的一个类型。
模板具现(Template Instantiation)
在这一节中的一切几乎都可应用于模板具现,不过还是有一些其他的选项。
当在例如GNU/Linux或Solaris 2的 ELF系统上,或者在 Microsoft Windows上使用GNU ld版本2.8或更新的版本,在链接时刻这些构造的重复的拷贝将被丢弃。这就是所谓的COMDAT支持。在不支持COMDAT,但支持弱符号(weak symbol)的目标平台上,GCC 将使用弱符号。这样一个拷贝将废除(override)其他拷贝,但在执行文件中未使用的拷贝将仍旧占据空间。
对于不支持COMDAT及弱符号的目标平台,绝大多数具有模糊链接性的实体将被作为局部符号产生,以避免链接器产生重复定义的错误。不过在内联函数中,对于局部静态变量,这不会发生,因为具有多个拷贝几乎总是坏事(break things)。
6.4 #pragma interface及implementation
#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 implementation,allclass.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 };
注意下面125及126行的interface_only和interface_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_strcmp在impl_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_implementation的590行,在#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行的条件不满足。