当我们写出类似: f (a, b); 的代码来调用 f ,编译器需要努力使得这个调用以期望的方式可行。下面的函数泄露了编译器的所作所为。
2420 tree
2421 build_function_call (tree function, tree params) in typeck.c
2422 {
2423 tree fntype, fndecl;
2424 tree coerced_params;
2425 tree result;
2426 tree name = NULL_TREE, assembler_name = NULL_TREE;
2427 int is_method;
2428 tree original = function;
2429
2430 /* build_c_cast puts on a NOP_EXPR to make the result not an lvalue.
2431 Strip such NOP_EXPRs, since FUNCTION is used in non-lvalue context. */
2432 if (TREE_CODE (function) == NOP_EXPR
2433 && TREE_TYPE (function) == TREE_TYPE (TREE_OPERAND (function, 0)))
2434 function = TREE_OPERAND (function, 0);
2435
2436 if (TREE_CODE (function) == FUNCTION_DECL)
2437 {
2438 name = DECL_NAME (function);
2439 assembler_name = DECL_ASSEMBLER_NAME (function);
2440
2441 mark_used (function);
2442 fndecl = function;
2443
2444 /* Convert anything with function type to a pointer-to-function. */
2445 if (pedantic && DECL_MAIN_P (function))
2446 pedwarn ("ISO C++ forbids calling `::main' from within program");
2447
2448 /* Differs from default_conversion by not setting TREE_ADDRESSABLE
2449 (because calling an inline function does not mean the function
2450 needs to be separately compiled). */
2451
2452 if (DECL_INLINE (function))
2453 function = inline_conversion (function);
2454 else
2455 function = build_addr_func (function);
2456 }
2457 else
2458 {
2459 fndecl = NULL_TREE;
2460
2461 function = build_addr_func (function);
2462 }
在调用点内联函数被展开。因此它不能在该编译单元之外分开编译,也不能把它标记为 TREE_ADDRESSABLE 。在这里 ADDR_EXPR 作为这个调用语句的结果被产生出来。
1464 tree
1465 inline_conversion (tree exp) in typeck.c
1466 {
1467 if (TREE_CODE (exp) == FUNCTION_DECL)
1468 exp = build1 (ADDR_EXPR, build_pointer_type (TREE_TYPE (exp)), exp);
1469
1470 return exp;
1471 }
当通过指针来访问内联函数(以形式 x.*p ,其中 x 是可取址的,因而 build_address (它要求可取址的对象作为参数)可以工作),访问性检查是要求的;而对于调用普通函数,需要 decay_conversion 来进行函数到指针的转换。
178 tree
179 build_addr_func (tree function) in tree.c
180 {
181 tree type = TREE_TYPE (function);
182
183 /* We have to do these by hand to avoid real pointer to member
184 functions. */
185 if (TREE_CODE (type) == METHOD_TYPE)
186 {
187 if (TREE_CODE (function) == OFFSET_REF)
188 {
189 tree object = build_address (TREE_OPERAND (function, 0));
190 return get_member_function_from_ptrfunc (&object,
191 TREE_OPERAND (function, 1));
192 }
193 function = build_address (function);
194 }
195 else
196 function = decay_conversion (function);
197
198 return function;
199 }
上面的 OFFSET_REF 被用在两种情形中:
1. 一个具有形式‘ A::m ’的表达式,其中‘ A ’是一个类,而‘ m ’是一个非静态成员。在这个情况下,操作数 0 将是一个 TYPE (对应‘ A ’),而操作数 1 将是一个 FIELD_DECL , BASELINK ,或 TEMPLATE_ID_EXPR (对应‘ m ’)。
如果其地址被获取了,这个表达式就是一个指向成员的指针,但如果其地址没有被获取,则仅表示该对象的一个成员。
这个形式仅被用在解析阶段;一旦语法分析完成,该形式就要被消除。
2. 一个具有形式‘ x.*p ’的表达式。在这个情况下,操作数 0 将是一个对应‘ x ’的表达式,而操作数 1 将是一个具有指向成员的指针类型的表达式。
显然,类方法的指针遵守第二个规则。对于非静态方法的调用,记得还有一个隐含的“ const this* ”,上面的 189 行, object 就是这个 this 指针。另外在 C++ 中,有一个奇特的类型 – 方法类型【 14 】,例如:“ typedef void (C::*Cf) () = &C::f; ” Cf 是 C (假定 C 是一个类)的一个方法类型,它没有参数,并返回 void 。 2324 行的谓词 TYPE_PTRMEMFUNC_P 对这样的类型返回 true 。在 2327 行的 TYPE_PTRMEMFUNC_FN_TYPE 获取关联的方法类型。
2318 tree
2319 get_member_function_from_ptrfunc (tree *instance_ptrptr, tree function) in typeck.c
2320 {
2321 if (TREE_CODE (function) == OFFSET_REF)
2322 function = TREE_OPERAND (function, 1);
2323
2324 if (TYPE_PTRMEMFUNC_P (TREE_TYPE (function)))
2325 {
2326 tree idx, delta, e1, e2, e3, vtbl, basetype;
2327 tree fntype = TYPE_PTRMEMFUNC_FN_TYPE (TREE_TYPE (function));
2328
2329 tree instance_ptr = *instance_ptrptr;
2330 tree instance_save_expr = 0;
2331 if (instance_ptr == error_mark_node)
2332 {
2333 if (TREE_CODE (function) == PTRMEM_CST)
2334 {
2335 /* Extracting the function address from a pmf is only
2336 allowed with -Wno-pmf-conversions. It only works for
2337 pmf constants. */
2338 e1 = build_addr_func (PTRMEM_CST_MEMBER (function));
2339 e1 = convert (fntype, e1);
2340 return e1;
2341 }
2342 else
2343 {
2344 error ("object missing in use of `%E'", function);
2345 return error_mark_node;
2346 }
2347 }
2348
2349 if (TREE_SIDE_EFFECTS (instance_ptr))
2350 instance_ptr = instance_save_expr = save_expr (instance_ptr);
2351
2352 if (TREE_SIDE_EFFECTS (function))
2353 function = save_expr (function);
2354
2355 /* Start by extracting all the information from the PMF itself. */
2356 e3 = PFN_FROM_PTRMEMFUNC (function);
如果 instance_ptr 是 error_mark_node ,在这里 function 应该是一个 PTRMEM_CST (成员指针常量的节点),其中的 PTRMEM_CST_CLASS 是 C 的 RECORD_TYPE ,而 PTRMEM_CST_MEMBER 是对应 f 的 *_DECL (不过我想不出一个会产生这样节点的正常情形,因为上面的 build_address ( 189 行),对于 instance_ptr ,总是成功的,除非传入的节点是 error_mark_node ,这意味着在这个过程中某些东西出错了。即便对于把方法指针转换到函数指针的情形,似乎也不应该到这里来,其细节可以在【 6 】中看到)。
在 C++ 中,成员方法的指针( PMF )使用一个差不多可以处理所有可能调用机制的宽指针来实现; PMF 需要保存关于如何调整‘ this ’指针的信息,并且如果被指向的函数是虚函数,在何处查找 vtable ,及在该 vtable 的何处查找这个成员方法。如果你在一个内部循环中使用 PMF ,你应该三思。如果别无选择,你可以从该指针提取将为给定对象 /PMF 对所调用的函数,并在这个内部循环中直接调用这个函数,以节约一些时间。 注意到你仍将为通过一个函数指针进行的函数调用付出代价;在绝大多数架构上,这样的一个调用会挫败 CPU 的分支预测特性。这对于普通的虚函数调用也是成立的。 这个扩展的语法是 extern A a; extern int (A::*fp)(); typedef int (*fptr)(A *); fptr p = (fptr)(a.*fp); 对于 PMF 常量( 即 , 具有形式 ‘ &Klasse::Member ’ 的表达式 ), 不需要对象来获取该函数的地址。它们可以被直接转换到函数指针: fptr p1 = (fptr)(&A::foo); 你必须指定‘ -Wno-pmf-conversions ’来使用这个扩展。 |
而如果 instance_ptr 不是 error_mark_node ,在 2356 行的 function 是具有成员指针类型的表达式,而 PFN_FROM_PTRMEMFUNC 具有如下定义。
2563 #define PFN_FROM_PTRMEMFUNC (NODE) pfn_from_ptrmemfunc ((NODE))
5637 tree
5638 pfn_from_ptrmemfunc (tree t) in typeck.c
5639 {
5640 if (TREE_CODE (t) == PTRMEM_CST)
5641 {
5642 tree delta;
5643 tree pfn;
5644
5645 expand_ptrmemfunc_cst (t, &delta, &pfn);
5646 if (pfn)
5647 return pfn;
5648 }
5649
5650 return build_ptrmemfunc_access_expr (t, pfn_identifier );
5651 }
上面的函数获取了由这个成员指针类型的表达式或 PTRMEM_CST 结构所引用的方法,并且它需要调整 this 指针,或适当地操控 vtable 。
首先,如果上面的实参 function 具有形如:“ &C::f ”的形式,它是应该 PTRMEM_CST 节点。不过在构建函数调用的上下文中, function 不会是 PTRMEM_CST ;而 function 是 PTRMEM_CST 的一个场景是形如:“ void (B::*pfn)() = &B::f; ”的语句,然后 expand_ptrmemfunc_cst 将被 build_ptrmemfunc 调用来从“ &B::f ”的 PTRMEM_CST 中,得到用于方法指针类型“ void (B::*)() ”的结构(见下面)的 __pfn 及 __delta 。
不管怎么说,这值得我们花一些时间看一下怎样为方法指针确定 __pfn 及 __delta 域。下面在 5587 行的 TYPE_PTRMEMFUNC_OBJECT_TYPE 为形如“ int (A::*)(double) ”的类型返回‘ A ’。
5574 void
5575 expand_ptrmemfunc_cst (tree cst, tree *delta, tree *pfn) in typeck.c
5576 {
5577 tree type = TREE_TYPE (cst);
5578 tree fn = PTRMEM_CST_MEMBER (cst);
5579 tree ptr_class, fn_class;
5580
5581 my_friendly_assert (TREE_CODE (fn) == FUNCTION_DECL, 0);
5582
5583 /* The class that the function belongs to. */
5584 fn_class = DECL_CONTEXT (fn);
5585
5586 /* The class that we're creating a pointer to member of. */
5587 ptr_class = TYPE_PTRMEMFUNC_OBJECT_TYPE (type);
5588
5589 /* First, calculate the adjustment to the function's class. */
5590 *delta = get_delta_difference (fn_class, ptr_class, /*force=*/ 0);
考虑下面的例子:
class A { public : void f (); };
class B: public A {};
void (B::*pfn) () = &B::f;
对于这个例子, fn_class 将是 A (来自“ &B::f ”部分), ptr_class 将是 B (来自“ vod (B::*pfn) () ”部分),而 fn 将是 f 。因此,首先需要知道派生类与定义该函数的基类间的调整量。在下面的函数中,参数 force 如果是 false ,表示不允许反向的转换(从 to 到 from )。
5392 static tree
5393 get_delta_difference (tree from, tree to, int force) in typeck.c
5394 {
5395 tree binfo;
5396 tree virt_binfo;
5397 base_kind kind;
5398
5399 binfo = lookup_base (to, from, ba_check, &kind);
5400 if (kind == bk_inaccessible || kind == bk_ambig)
5401 {
5402 error (" in pointer to member function conversion");
5403 goto error;
5404 }
5405 if (!binfo)
5406 {
5407 if (!force)
5408 {
5409 error_not_base_type (from, to);
5410 error (" in pointer to member conversion");
5411 goto error;
5412 }
5413 binfo = lookup_base (from, to, ba_check, &kind);
5414 if (!binfo)
5415 goto error;
5416 virt_binfo = binfo_from_vbase (binfo);
5417 if (virt_binfo)
5418 {
5419 /* This is a reinterpret cast, we choose to do nothing. */
5420 warning ("pointer to member cast via virtual base `%T'",
5421 BINFO_TYPE (virt_binfo));
5422 goto error;
5423 }
5424 return fold (convert_to_integer (ptrdiff_type_node ,
5425 size_diffop (size_zero_node,
5426 BINFO_OFFSET (binfo))));
5427 }
5428
5429 virt_binfo = binfo_from_vbase (binfo);
5430 if (!virt_binfo)
5431 return fold (convert_to_integer (ptrdiff_type_node , BINFO_OFFSET (binfo)));
5432
5433 /* This is a reinterpret cast, we choose to do nothing. */
5434 if (force)
5435 warning ("pointer to member cast via virtual base `%T'",
5436 BINFO_TYPE (virt_binfo));
5437 else
5438 error ("pointer to member conversion via virtual base `%T'",
5439 BINFO_TYPE (virt_binfo));
5440
5441 error:
5442 return fold (convert_to_integer(ptrdiff_type_node , integer_zero_node));
5443 }
注意到参数 from 及 to 必须具有继承关系。另外,如果转换涉及虚拟基类,这是仅可以被 reinterpret_cast 处理的语法,在这里应该被忽略。并且内部对象 ptrdiff_type_node 对应于我们经常在 C/C++ 程序中看到的 ptrdiff_t 。考虑这个例子:
class base { public : void fb() {} virtual ~base() {} };
class A : public base {};
class B1 : virtual public A {};
class B2 : virtual public A {};
class C: public B1, public B2 {};
int main() {
C c;
void (C::*pfn) () = &base::fb;
(c.*pfn)();
return 0;
}
语句 “ PFN pfn = &base::fb; ” 导致错误 “ error: pointer to member conversion via virtual base ‘A’ ” 。但是如果 PFN 被定义为“ typedef void (base::*PFN) (); ”,这就 OK 。
下面的 binfo_from_vbase 检查指定的 binfo 是否为虚拟基类继承。
2525 tree
2526 binfo_from_vbase (tree binfo) in search.c
2527 {
2528 for (; binfo; binfo = BINFO_INHERITANCE_CHAIN (binfo))
2529 {
2530 if (TREE_VIA_VIRTUAL (binfo))
2531 return binfo;
2532 }
2533 return NULL_TREE;
2534 }
如果 fn 不是应该虚函数,则仅把表达式 fn 转换到方法指针类型。在编译器内部,方法指针的类型看起来就像:
struct {
__P __pfn;
ptrdiff_t __delta;
};
如果 __pfn 是 NULL ,它是一个空的方法指针。(因为 vtable 总是对象的第一部分,我们不需要其偏移量)。如果该函数是虚函数,那么 PFN 是 1 加上 2 倍 vtable 索引;否则,它只是一个指向该函数的指针(不幸地,使用 PFN 的最低位,在不强制要求函数地址对齐,或使用这个最低位,例如区分 ISA ,的架构上,不能工作。对于这样的架构,我们使用 DELTA 的最低位,而不是 PFN 的最低位,并且 DELTA 将被乘以 2 )。
因此 5593 行的转换把由 fn 指定的方法地址转变为对应 METHOD_TYPE 的 POINTER_TYPE 。而 delta 将是用于这个方法的 this 指针的调整量。
而如果 fn 是一个虚函数,考虑下面例子:
class A { virtual void f () {} }; 例 - 1
class B: public A {};
void (B::*pfn) () = &B::f;
class A { virtual void f () {} }; 例 - 2
class B: public A { virtual void f () {} };
void (B::*pfn) () = &B::f;
class A { public : 例 - 3
virtual A* f () { printf ("A::f/n"); return 0; }
virtual A* f1 () { printf ("A::f1/n"); return 0; }
virtual ~A() {}
};
class B1 : virtual public A { public : virtual B1* f1 () { printf ("B1::f1/n"); return 0; } };
class B2 : virtual public A { public : virtual B2* f () { printf ("B2::f/n"); return 0; } };
class C: public B1, public B2 {};
int main() {
C c;
B2* (B2::*pfn) () = &C::f;
(c.*pfn)();
return 0;
}
注意到上面的 get_delta_difference 获得由 PTRMEM_CST 指定的类与定义该方法的类间的偏移量。在上面的例 1 中,由 PTRMEM_CST 指定的类是 B ,而定义 f 的类是 A ;而在例 2 中,两者都是 B 。那么根据前面章节中类布局的知识,显然,对于这两个例子,类 A 总是被布置在类 B 偏移量为 0 的位置;而对于例 3 , delta 则记录了 B2 到 C 的偏移量。
看到下面的 orig_class 与 fn_class 相同,因此在 560 行的 binfo_or_else 总能成功并返回基类的 binfo (看到不是派生类中的基类部分); 5602 行的 BINFO_OFFSET (binfo) 总是 0 。而 5601 行的 delta 保留着由 get_delta_difference 得到的偏移量。
记得 5607 行的 DECL_VINDEX 被虚函数用来记录它在 vtable 中的索引。因此在 5608 行的 pfn 是这个 vtable 中真正的偏移量,并且很有趣的,看到在 5629 行,这个 pfn 被转换到方法指针类型(当然,它不是最后的地址)。
expand_ptrmemfunc_cst (continue)
5592 if (!DECL_VIRTUAL_P (fn))
5593 *pfn = convert (TYPE_PTRMEMFUNC_FN_TYPE (type), build_addr_func (fn));
5594 else
5595 {
5596 /* If we're dealing with a virtual function, we have to adjust 'this'
5597 again, to point to the base which provides the vtable entry for
5598 fn; the call will do the opposite adjustment. */
5599 tree orig_class = DECL_CONTEXT (fn);
5600 tree binfo = binfo_or_else (orig_class, fn_class);
5601 *delta = fold (build (PLUS_EXPR, TREE_TYPE (*delta),
5602 *delta, BINFO_OFFSET (binfo)));
5603
5604 /* We set PFN to the vtable offset at which the function can be
5605 found, plus one (unless ptrmemfunc_vbit_in_delta, in which
5606 case delta is shifted left, and then incremented). */
5607 *pfn = DECL_VINDEX (fn);
5608 *pfn = fold (build (MULT_EXPR, integer_type_node, *pfn,
5609 TYPE_SIZE_UNIT (vtable_entry_type )));
5610
5611 switch (TARGET_PTRMEMFUNC_VBIT_LOCATION)
5612 {
5613 case ptrmemfunc_vbit_in_pfn:
5614 *pfn = fold (build (PLUS_EXPR, integer_type_node, *pfn,
5615 integer_one_node));
5616 break ;
5617
5618 case ptrmemfunc_vbit_in_delta:
5619 *delta = fold (build (LSHIFT_EXPR, TREE_TYPE (*delta),
5620 *delta, integer_one_node));
5621 *delta = fold (build (PLUS_EXPR, TREE_TYPE (*delta),
5622 *delta, integer_one_node));
5623 break ;
5624
5625 default :
5626 abort ();
5627 }
5628
5629 *pfn = fold (build1 (NOP_EXPR, TYPE_PTRMEMFUNC_FN_TYPE (type),
5630 *pfn));
5631 }
5632 }
TARGET_PTRMEMFUNC_VBIT_LOCATION 显示在何处保存表示虚函数方法指针的位。它可能是函数地址的最低位;或者它可能是在 this 调整量中的最低位,这依赖于函数地址是否有足够的对齐量。对于 x86 芯片,它使用函数地址的最低位。因此 5629 行的 pfn 事实上是表达式:“ vindex * sizeof (vtable entry) + 1” (在另一个情况下, delta 将是“ BINFO_OFFSET (base) * 2 + 1” 带有保存这个虚拟比特的额外空间)。
没在这里显示,这里得到的 delta 及 pfn 将被设置入上面给出的用于方法指针的结构中。
59 tree
60 binfo_or_else (tree base, tree type) in typeck2.c
61 {
62 tree binfo = lookup_base (type, base, ba_ignore, NULL);
63
64 if (binfo == error_mark_node)
65 return NULL_TREE;
66 else if (!binfo)
67 error_not_base_type (base, type);
68 return binfo;
69 }
如我们上面提及的,在构建函数调用的上下文中, pfn 必须是一个方法指针类型的实例,而对于一个正确的函数调用,它必须已经被某个 PTRMEM_CST 初始化,如上面所示。
那么 build_ptrmemfunc_access_expr 用于获取上面提到的域,参数 member_name 指出是哪一个。因此在 pfn_from_ptrmemfunc 中的 5650 行,通过 pfn_identifier 获取了 __pfn 域。
2031 tree
2032 build_ptrmemfunc_access_expr (tree ptrmem, tree member_name) in typeck.c
2033 {
2034 tree ptrmem_type;
2035 tree member;
2036 tree member_type;
2037
2038 /* This code is a stripped down version of
2039 build_class_member_access_expr. It does not work to use that
2040 routine directly because it expects the object to be of class
2041 type. */
2042 ptrmem_type = TREE_TYPE (ptrmem);
2043 my_friendly_assert (TYPE_PTRMEMFUNC_P (ptrmem_type), 20020804);
2044 member = lookup_member (ptrmem_type, member_name, /*protect=*/ 0,
2045 /*want_type=*/ false);
2046 member_type = cp_build_qualified_type (TREE_TYPE (member),
2047 cp_type_quals (ptrmem_type));
2048 return fold (build (COMPONENT_REF, member_type, ptrmem, member));
2049 }
然后,上面在 2356 行, e3 正如我们已经看到的,是关于函数地址的表达式(非虚函数指针,或虚函数的 vtable 索引)。接着,它获取 __delta 域入 delta 。
对于 x86 芯片,在 2362 行的 e1 是测试 __pfn 的最低位是否是 1 的表达式,这是查看该指针是否指向虚函数。在 2363 行,在为虚函数的情形下, idx 被更新为真正的 vtable 偏移量。
get_member_function_from_ptrfunc (continue)
2357 delta = build_ptrmemfunc_access_expr (function, delta_identifier );
2358 idx = build1 (NOP_EXPR, vtable_index_type , e3);
2359 switch (TARGET_PTRMEMFUNC_VBIT_LOCATION)
2360 {
2361 case ptrmemfunc_vbit_in_pfn:
2362 e1 = cp_build_binary_op (BIT_AND_EXPR, idx, integer_one_node);
2363 idx = cp_build_binary_op (MINUS_EXPR, idx, integer_one_node);
2364 break ;
2365
2366 case ptrmemfunc_vbit_in_delta:
2367 e1 = cp_build_binary_op (BIT_AND_EXPR, delta, integer_one_node);
2368 delta = cp_build_binary_op (RSHIFT_EXPR, delta, integer_one_node);
2369 break ;
2370
2371 default :
2372 abort ();
2373 }
2374
2375 /* Convert down to the right base before using the instance. First
2376 use the type... */
2377 basetype = TYPE_METHOD_BASETYPE (TREE_TYPE (fntype));
2378 basetype = lookup_base (TREE_TYPE (TREE_TYPE (instance_ptr)),
2379 basetype, ba_check, NULL);
2380 instance_ptr = build_base_path (PLUS_EXPR, instance_ptr, basetype, 1);
2381 if (instance_ptr == error_mark_node)
2382 return error_mark_node;
注意 instance_ptr 是一个指针,那么 instance_ptr 的 TREE_TYPE 的 TREE_TYPE 返回这个对象的 RECORD_TYPE ,而 TYPE_METHOD_BASETYPE 返回声明该函数的基类。看到与 instance_ptr 关联的类及与该函数关联的类必须具有派生关系。
函数 build_base_path 转换自 / 至一个基类子对象。参数 expr 是一个具有类型‘ A ’或‘ A* ’的表达式,返回一个具有‘ B ’或‘ B* ’的表达式。把 A 转换到一个基类 B , code 是 PLUS_EXPR 而 binfo 是 A 中 B 基类实例的 binfo 。把基类 A 转换到派生类 B , code 是 MINUS_EXPR 而 binfo 是 B 中 A 实例的 binfo 。在后一个情形中, A 必须不是 B 一个确实的虚拟基类。参数 nonnull 是 true ,如果 expr 已知是非空的(当 expr 具有指针类型,这才需要)。 CV 限定从 expr 保留。考虑下面的例子:
class A { public : void fa (); }; 例 - 4
class B: public A { public : void fb(); };
class C:: public B {};
void (B::*pfn) = &B::f;
C c;
(c.*pfn) ();
对于这个例子, fntype 是 pfn 的类型,因此 2377 行的 basetype 是 B (与该方法指针绑定的类型);而 instance_ptr 是类型 C 。需要调整 instance_ptr 到基类 B 来执行语句“ (c.*pfn)(); ”。
下面的 build_base_path 构建进行这样转换的表达式。在这个函数中, instance_ptr 被传入做参数 expr ,它是一个指针,因此下面的 want_pointer 是非 0 值。显然参数 nonnull 也是非 0 。然后 v_binfo 指向最接近 binfo 的虚拟派生类,而 d_binfo ,通过 261 行的 FOR 循环,记录了最后派生类。注意到 binfo 从由 expr 的类型为根节点的继承树中获得,对于 PLUS_EXPR ,必然满足 272 行的断言。
242 tree
243 build_base_path (enum tree_code code, in class.c
244 tree expr,
245 tree binfo,
246 int nonnull)
247 {
248 tree v_binfo = NULL_TREE;
249 tree d_binfo = NULL_TREE;
250 tree probe;
251 tree offset;
252 tree target_type;
253 tree null_test = NULL;
254 tree ptr_target_type;
255 int fixed_type_p;
256 int want_pointer = TREE_CODE (TREE_TYPE (expr)) == POINTER_TYPE;
257
258 if (expr == error_mark_node || binfo == error_mark_node || !binfo)
259 return error_mark_node;
260
261 for (probe = binfo; probe; probe = BINFO_INHERITANCE_CHAIN (probe))
262 {
263 d_binfo = probe;
264 if (!v_binfo && TREE_VIA_VIRTUAL (probe))
265 v_binfo = probe;
266 }
267
268 probe = TYPE_MAIN_VARIANT (TREE_TYPE (expr));
269 if (want_pointer)
270 probe = TYPE_MAIN_VARIANT (TREE_TYPE (probe));
271
272 my_friendly_assert (code == MINUS_EXPR
273 ? same_type_p (BINFO_TYPE (binfo), probe)
274 : code == PLUS_EXPR
275 ? same_type_p (BINFO_TYPE (d_binfo), probe)
276 : false, 20010723);
277
278 if (code == MINUS_EXPR && v_binfo)
279 {
280 error ("cannot convert from base `%T' to derived type `%T' via virtual base `%T'",
281 BINFO_TYPE (binfo), BINFO_TYPE (d_binfo), BINFO_TYPE (v_binfo));
282 return error_mark_node;
283 }
284
285 if (!want_pointer)
286 /* This must happen before the call to save_expr. */
287 expr = build_unary_op (ADDR_EXPR, expr, 0);
288
289 fixed_type_p = resolves_to_fixed_type_p (expr, &nonnull);
290 if (fixed_type_p <= 0 && TREE_SIDE_EFFECTS (expr))
291 expr = save_expr (expr);
在调用下面的函数之前,如果 want_pointer 是 0 ,表示 expr 不是指针。把 expr 封装入一个 ADDR_EXPR 节点,它将用下面的函数把 nonnull 强制为非 0 值。注意到 5410 行的 t 是 instance 的静态类型( static type ),但它可能与真正类型不完全一样(动态类型)。考虑下面的例子,它将给出与例 3 一样的结果:
class A { public : 例 - 5
virtual A* f () { printf ("A::f/n"); return 0; }
virtual A* f1 () { printf ("A::f1/n"); return 0; }
virtual ~A() {}
};
class B1 : virtual public A { public : virtual B1* f1 () { printf ("B1::f1/n"); return 0; } };
class B2 : virtual public A { public : virtual B2* f () { printf ("B2::f/n"); return 0; } };
class C: public B1, public B2 { };
int main() {
A *pa = new C;
A* (A::*pfn) () = &A::f;
(pa->*pfn)();
delete pa;
return 0;
}
那么对于 pa ,其静态类型是“ A* ”,如它被声明的那样,而其动态类型是“ C* ”,它在运行时由 new 语句确定。注意到 pfn 最后调用 B2::f ,因为虚函数机制。
函数 resolves_to_fixed_type_p 返回非 0 值,如果静态类型与动态类型相同(而如果 instance 还是构造函数 / 析构函数,则返回负数)。
5407 int
5408 resolves_to_fixed_type_p (tree instance, int* nonnull) in class.c
5409 {
5410 tree t = TREE_TYPE (instance);
5411 int cdtorp = 0;
5412
5413 tree fixed = fixed_type_or_null (instance, nonnull, &cdtorp);
5414 if (fixed == NULL_TREE)
5415 return 0;
5416 if (POINTER_TYPE_P (t))
5417 t = TREE_TYPE (t);
5418 if (!same_type_ignoring_top_level_qualifiers_p (t, fixed))
5419 return 0;
5420 return cdtorp ? -1 : 1;
5421 }
instance 的动态类型如果已知,由 fixed_type_or_null 返回, 其中参数 nonnull 被设置,当且仅当 instance 明确知道是非空的,不管对其类型 所知多少;而参数 cdtorp 是非 0 值,如果 instance 还是‘ this ’指针的表达式。