接下来,如果我们没有通过-fno-exception禁止异常机制,C++运行时将为我们建立起异常处理所用的函数声明。在C++中,当抛出一个异常但程序没有捕捉它,那么在全局名字空间,运行时将会捕捉它,并调用std::terminate()来终止程序。这整个功能由定义在文件gcc-3.4.6/libstdc++-v3/libstdsupc++/eh_personality.cc中的运行时函数__cxa_call_unexpected来提供。
而在解析throw及catch语句时,编译器需要建立由exception对象携带的相关的上下文,及为这个上下文调用异常处理逻辑的代码,__cxa_call_unexpected,__gxx_personality_v0或__gxx_personality_sj0都是这个上下文所引用的处理函数。整个异常处理逻辑及其上下文的建立非常复杂,这里我们不准备深入。在看完后续关于复杂类型的解析及处理后,这部分代码并不难理解。
60 void
61 init_exception_processing (void) in expt.c
62 {
63 tree tmp;
64
65 /* void std::terminate (); */
66 push_namespace (std_identifier);
67 tmp = build_function_type (void_type_node, void_list_node);
68 terminate_node = build_cp_library_fn_ptr ("terminate", tmp);
69 TREE_THIS_VOLATILE (terminate_node) = 1;
70 TREE_NOTHROW (terminate_node) = 1;
71 pop_namespace ();
72
73 /* void __cxa_call_unexpected(void *); */
74 tmp = tree_cons (NULL_TREE, ptr_type_node, void_list_node);
75 tmp = build_function_type (void_type_node, tmp);
76 call_unexpected_node
77 = push_throw_library_fn (get_identifier ("__cxa_call_unexpected"), tmp);
78
79 eh_personality_libfunc = init_one_libfunc (USING_SJLJ_EXCEPTIONS
80 ? "__gxx_personality_sj0"
81 : "__gxx_personality_v0");
82
83 lang_eh_runtime_type = build_eh_type_type;
84 lang_protect_cleanup_actions = &cp_protect_cleanup_actions;
85 }
因为std::terminate是运行时库例程,其声明需由build_cp_library_fn_ptr来创建。记住build_cp_library_fn将找出(如果找不到就创建它)声明的具有修饰名的标识符节点,并把它设置为声明节点的汇编名。那么在链接阶段,链接器将在库空间,通过这个汇编名找到这个函数。
3350 tree
3351 build_cp_library_fn_ptr (const char* name, tree type) in decl.c
3352 {
3353 return build_cp_library_fn (get_identifier (name), ERROR_MARK, type);
3354 }
例程__cxa_call_unexpected是声明为extern “C”,这表示它是一个C形式的函数声明。它的名字将不经过修饰。
3393 tree
3394 push_throw_library_fn (tree name, tree type) in decl.c
3395 {
3396 tree fn = push_library_fn (name, type);
3397 TREE_THIS_VOLATILE (fn) = 1;
3398 TREE_NOTHROW (fn) = 0;
3399 return fn;
3400 }
注意在build_library_fn中,声明的语言被设置为C,而且没有设置汇编名,因为在C里,这个名字没有修饰。
3359 tree
3360 push_library_fn (tree name, tree type) in decl.c
3361 {
3362 tree fn = build_library_fn (name, type);
3363 pushdecl_top_level (fn);
3364 return fn;
3365 }
接下来pushdecl_top_level将把这个声明加入全局名字空间。
3414 tree
3415 pushdecl_top_level (tree x) in name-lookup.c
3416 {
3417 return pushdecl_top_level_1 (x, NULL);
3418 }
下面的push_to_top_level缓存了从当前作用域到全局名字空间的标识符,并停留在全局名字空间。而 pop_from_top_level将恢复这个作用域。
3400 static tree
3401 pushdecl_top_level_1 (tree x, tree *init) in name-lookup.c
3402 {
3403 timevar_push (TV_NAME_LOOKUP);
3404 push_to_top_level ();
3405 x = pushdecl_namespace_level (x);
3406 if (init)
3407 cp_finish_decl (x, *init, NULL_TREE, 0);
3408 pop_from_top_level ();
3409 POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, x);
3410 }
虽然是尝试把对象加入到当前名字空间,我们有可能是在当前名字空间中的一个作用域里(当然,对于我们的情形,我们一定在全局名字空间里,但从pushdecl_namespace_level的角度,这是有可能的)。因此在3232行,获取了当前作用域。其目的下面可以看到。
3229 tree
3230 pushdecl_namespace_level (tree x) in name-lookup.c
3231 {
3232 struct cp_binding_level *b = current_binding_level;
3233 tree t;
3234
3235 timevar_push (TV_NAME_LOOKUP);
3236 t = pushdecl_with_scope (x, NAMESPACE_LEVEL (current_namespace));
对于把对象加入名字空间,pushdecl_with_scope的行为类似于pushdecl,除了它是为指定的名字空间,而不一定是当前作用域,因此为保险起见,总是缓存当前作用域,并随后恢复。
1941 tree
1942 pushdecl_with_scope (tree x, cxx_scope *level) in name-lookup.c
1943 {
1944 struct cp_binding_level *b;
1945 tree function_decl = current_function_decl;
1946
1947 timevar_push (TV_NAME_LOOKUP);
1948 current_function_decl = NULL_TREE;
1949 if (level->kind == sk_class)
1950 {
1951 b = class_binding_level;
1952 class_binding_level = level;
1953 pushdecl_class_level (x);
1954 class_binding_level = b;
1955 }
1956 else
1957 {
1958 b = current_binding_level;
1959 current_binding_level = level;
1960 x = pushdecl (x);
1961 current_binding_level = b;
1962 }
1963 current_function_decl = function_decl;
1964 POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, x);
1965 }
对于每个作用域对象,如果它是一个类作用域,域class_shadowed记录了被绑定的类型声明。而域type_shadowed则是在所有的非名字空间作用域中,记录被该作用域所屏蔽的类型(如果没有屏蔽,则是记录自己)。为什么是非名字空间作用域?因为通过域level_chain,很容易确定指定的标识符,在指定的名字空间中是否可见。这是因为名字空间总是最外层的作用域,并且一直有效。
pushdecl_namespace_level (continue)
3238 /* Now, the type_shadowed stack may screw us. Munge it so it does
3239 what we want. */
3240 if (TREE_CODE (x) == TYPE_DECL)
3241 {
3242 tree name = DECL_NAME (x);
3243 tree newval;
3244 tree *ptr = (tree *)0;
3245 for (; !global_scope_p (b); b = b->level_chain)
3246 {
3247 tree shadowed = b->type_shadowed;
3248 for (; shadowed; shadowed = TREE_CHAIN (shadowed))
3249 if (TREE_PURPOSE (shadowed) == name)
3250 {
3251 ptr = &TREE_VALUE (shadowed);
3252 /* Can't break out of the loop here because sometimes
3253 a binding level will have duplicate bindings for
3254 PT names. It's gross, but I haven't time to fix it. */
3255 }
3256 }
3257 newval = TREE_TYPE (x);
3258 if (ptr == (tree *)0)
3259 {
3260 /* @@ This shouldn't be needed. My test case "zstring.cc" trips
3261 up here if this is changed to an assertion. --KR */
3262 SET_IDENTIFIER_TYPE_VALUE (name, x);
3263 }
3264 else
3265 {
3266 *ptr = newval;
3267 }
3268 }
3269 POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, t);
3270 }
正如我们在前面提及的,当前作用域可能在当前名字空间里。现在往名字空间里加入了新的类型,那么要检查当前作用域到当前名字空间之间,是否会对这个类型构成屏蔽。在3245行,b就是当前作用域,通过level_chain,一直向上攀升至全局名字空间,不过因为名字空间不使用type_shadowed域,因此,3245行的FOR循环实际找到的是,被最接近当前名字空间的内部作用域所屏蔽的类型,该类型在当前名字空间外的名字空间中(该函数的使用保证了这一点)。毫无疑问,这个屏蔽类型应该更新为当前加入的类型(3262行)。如果内层作用域没有这个名字的类型声明,ptr将是0(3258行)。
__gxx_personality_sj0及__gxx_personality_v0都声明为C形式(extern “C”)。它们的声明节点由init_one_libfunc创建,这个声明类似:int __gxx_personality_sj0()。
5126 rtx
5127 init_one_libfunc (const char *name) in optabs.c
5128 {
5129 rtx symbol;
5130
5131 /* Create a FUNCTION_DECL that can be passed to
5132 targetm.encode_section_info. */
5133 /* ??? We don't have any type information except for this is
5134 a function. Pretend this is "int foo()". */
5135 tree decl = build_decl (FUNCTION_DECL, get_identifier (name),
5136 build_function_type (integer_type_node, NULL_TREE));
5137 DECL_ARTIFICIAL (decl) = 1;
5138 DECL_EXTERNAL (decl) = 1;
5139 TREE_PUBLIC (decl) = 1;
5140
5141 symbol = XEXP (DECL_RTL (decl), 0);
5142
5143 /* Zap the nonsensical SYMBOL_REF_DECL for this. What we're left with
5144 are the flags assigned by targetm.encode_section_info. */
5145 SYMBOL_REF_DECL (symbol) = 0;
5146
5147 return symbol;
5148 }
在5141行,DECL_RTL调用make_decl_rtl(其细节,参考为内建函数创建RTX对象)。注意到在该函数的790行,它要获取声明的修饰名,但是我们手上现在没有函数正确的声明(比较unwind-cxx.h中的声明)!
幸运的是,这里我们不需要完全正确的函数声明来得到库函数的入口名。类似__gxx_personality_v0的例程具有C链接性。这样其修饰名与声明名相同。那么只要在运行时提供正确的实参,一切都OK。
GCC支持弱符号(weak symbol)的概念。
同名的2个或以上的全局符号,只有除1个外其他都声明为弱符号时,才不会导致冲突。链接器忽略弱符号的定义,而使用全局符号的定义来解析所有的引用,不过如果没有全局符号,弱符号将被使用。弱符号可以用于命名函数及可被用户代码改写的数据。弱符号亦被称为弱别名(weak alias),或简单地“弱”(weak)。
另外,GCC还能输出下列的汇编器指示(assembler directive)。
linkonce[type]:标记当前段使其只被链接器包含一次,即便在多个模块中出现相同的段。该指示必须在该段的每个实例中出现一次。段通过名字来被选定,因此名字必须唯一。
可选的参数type不使用,将使得重复的段被无声丢弃(默认行为)。如果type是one_only 将每次发现重复时发出一个警告。Type是same_size,则如果重复的段大小不同,将发出警告,如果重复的段。Type是same_contents,则如果重复的段的内容不同,将发出警告。
这一切都是依赖于目标平台的。对于x86,elf格式的输出,support_one_only 返回0,因而设置flag_weak为0。
4549 int
4550 supports_one_only (void) in varasm.c
4551 {
4552 if (SUPPORTS_ONE_ONLY)
4553 return 1;
4554 return SUPPORTS_WEAK;
4555 }
在编程中,我们可以使用__func__,__FUNCTION__,__PRETTY__FUNCTION__来获取当前函数名。函数指针make_fname_decl用于保存相应的处理函数,在3129行,被设置为cp_make_fname_decl。函数start_fname_decls准备保存这些函数名的栈。后面可以再次看到这个函数。
从cxx_init_decl_processing返回,前端的C++的运行时已经建立,接下来设置预处理器。首先,创建我们耳熟能详的null对象。正如我们多次在程序中所见,这家伙实际就是整数0,不过注意它的大小正好是一个指针的大小。
cxx_init (continue)
412 /* Create the built-in __null node. */
413 null_node = build_int_2 (0, 0);
414 TREE_TYPE (null_node) = c_common_type_for_size (POINTER_SIZE, 0);
415 ridpointers[RID_NULL] = null_node;
416
417 interface_unknown = 1;
418
419 if (c_common_init () == false)
420 {
421 pop_srcloc();
422 return false;
423 }
下面的cpp_init_iconv初始化iconv描述符,它用于把源字符集转换为执行字符集。我们跳过这部分。flag_preprocess_only如果非0,表示只做预处理,那么至此,进行预处理的条件已经成熟,为之调用1207行的preprocess_file。并由此返回结束文件的编译处理。
1186 bool
1187 c_common_init (void) in c-opts.c
1188 {
1189 input_line = saved_lineno;
1190
1191 /* Set up preprocessor arithmetic. Must be done after call to
1192 c_common_nodes_and_builtins for type nodes to be good. */
1193 cpp_opts->precision = TYPE_PRECISION (intmax_type_node);
1194 cpp_opts->char_precision = TYPE_PRECISION (char_type_node);
1195 cpp_opts->int_precision = TYPE_PRECISION (integer_type_node);
1196 cpp_opts->wchar_precision = TYPE_PRECISION (wchar_type_node);
1197 cpp_opts->unsigned_wchar = TREE_UNSIGNED (wchar_type_node);
1198 cpp_opts->bytes_big_endian = BYTES_BIG_ENDIAN;
1199
1200 /* This can't happen until after wchar_precision and bytes_big_endian
1201 are known. */
1202 cpp_init_iconv (parse_in);
1203
1204 if (flag_preprocess_only)
1205 {
1206 finish_options ();
1207 preprocess_file (parse_in);
1208 return false;
1209 }
1210
1211 /* Has to wait until now so that cpplib has its hash table. */
1212 init_pragma ();
1213
1214 return true;
1215 }
如果需要完整的编译,那么对于x86 linux目标,C语言只有2个指示“pack”及“weak”可用(它们将出现在__attribute__()块中),因为#pragma指示是依赖于目标平台的。
494 void
495 init_pragma (void) in c-pragma.c
496 {
497 #ifdef HANDLE_PRAGMA_PACK
498 c_register_pragma (0, "pack", handle_pragma_pack);
499 #endif
500 #ifdef HANDLE_PRAGMA_WEAK
501 c_register_pragma (0, "weak", handle_pragma_weak);
502 #endif
503 #ifdef HANDLE_PRAGMA_REDEFINE_EXTNAME
504 c_register_pragma (0, "redefine_extname", handle_pragma_redefine_extname);
505 #endif
506 #ifdef HANDLE_PRAGMA_EXTERN_PREFIX
507 c_register_pragma (0, "extern_prefix", handle_pragma_extern_prefix);
508 #endif
509
510 #ifdef REGISTER_TARGET_PRAGMAS
511 REGISTER_TARGET_PRAGMAS ();
512 #endif
513 }
c_register_pragma把指示的标识符插入哈希表ident_hash并与处理句柄绑定。
486 void
487 c_register_pragma (const char *space, const char *name, in c-pragma.c
488 void (*handler) (struct cpp_reader *))
489 {
490 cpp_register_pragma (parse_in, space, name, handler);
491 }
但C++支持更多的#pragma指示,那么在下面初始化这些指示。
cxx_init (continue)
425 init_cp_pragma ();
426
427 init_repo (main_input_filename);
428
429 pop_srcloc();
430 return true;
431 }
下面的#pragma vtable,gcc不再支持,而#pragma unit,gcc并未实质支持。
368 static void
369 init_cp_pragma (void) in lex.c
370 {
371 c_register_pragma (0, "vtable", handle_pragma_vtable);
372 c_register_pragma (0, "unit", handle_pragma_unit);
373 c_register_pragma (0, "interface", handle_pragma_interface);
374 c_register_pragma (0, "implementation", handle_pragma_implementation);
375 c_register_pragma ("GCC", "interface", handle_pragma_interface);
376 c_register_pragma ("GCC", "implementation", handle_pragma_implementation);
377 c_register_pragma ("GCC", "java_exceptions", handle_pragma_java_exceptions);
378 }
当使用-frepo选项编译代码时,将产生后缀为.rpo的文件,每个文件列出在对应目标文件中所具现的模板。然后调用名为collect2的封装了链接器的工具,使用链接器指令来更新.rpo文件,以在最终的程序里放置模板实例。这个方法唯一的困难在于库的处理——除非提供了相关的.rpo文件,链接库里的模板实例将会失败。
在427行,init_repo为主输入文件创建了.rpo后缀的文件。