在C++中,指针、引用和地址在一定程度上可以混用。语言允许通过指针或者引用直接改变地址所在的内容。而函数调用,事实上是通过跳转到相应的地址来实现。另外在语言中,数组名也代表数组的首地址。因此,编译器可能需要首先创建与地址相关的节点,再由这个节点出发构建其它节点。比如,构建函数或数组节点。
用于表示语言中的类型的树节点是tree_type,它的定义如下。
1089 struct tree_type GTY(()) in tree.h
1090 {
1091 struct tree_common common;
1092 tree values;
1093 tree size;
1094 tree size_unit;
1095 tree attributes;
1096 unsigned int uid;
1097
1098 unsigned int precision : 9;
1099 ENUM_BITFIELD(machine_mode) mode : 7;
1100
1101 unsigned string_flag : 1;
1102 unsigned no_force_blk_flag : 1;
1103 unsigned needs_constructing_flag : 1;
1104 unsigned transparent_union_flag : 1;
1105 unsigned packed_flag : 1;
1106 unsigned restrict_flag : 1;
1107 unsigned spare : 2;
1108
1109 unsigned lang_flag_0 : 1;
1110 unsigned lang_flag_1 : 1;
1111 unsigned lang_flag_2 : 1;
1112 unsigned lang_flag_3 : 1;
1113 unsigned lang_flag_4 : 1;
1114 unsigned lang_flag_5 : 1;
1115 unsigned lang_flag_6 : 1;
1116 unsigned user_align : 1;
1117
1118 unsigned int align;
1119 tree pointer_to;
1120 tree reference_to;
1121 union tree_type_symtab {
1122 int GTY ((tag ("0"))) address;
1123 char * GTY ((tag ("1"))) pointer;
1124 struct die_struct * GTY ((tag ("2"))) die;
1125 } GTY ((desc ("debug_hooks == &sdb_debug_hooks? 1: debug_hooks == &dwarf2_debug_hooks? 2: 0"),
1126 descbits ("2"))) symtab;
1127 tree name;
1128 tree minval;
1129 tree maxval;
1130 tree next_variant;
1131 tree main_variant;
1132 tree binfo;
1133 tree context;
1134 HOST_WIDE_INT alias_set;
1135 /* Points to a structure whose details depend on the language in use. */
1136 struct lang_type *lang_specific;
1137 };
在上面的定义中,下列宏用于访问结构体中的域(红字部分为宏的定义)。
Ø TYPE_BINFO (TYPE_CHECK (NODE)->type.binfo)
² 对于聚集类型(struct/union/class)节点,该域保存关于类型的信息。如果节点不是RECORD_TYPE,QUAL_UNION_TYPE,或者UNION_TYPE,该域的用法取决于前端。
Ø TYPE_ALIAS_SET (TYPE_CHECK (NODE)->type.alias_set)
² 特定于语言的(language-specific),基于类型的(typed-based)别名集(alias set)。含有不同TYPE_ALIAS_SET的对象,不能互为别名(cannot alias each other)。如果 TYPE_ALIAS_SET是-1,表明该类型还没有别名集。如果TYPE_ALIAS_SET是0,该类型的对象可为任何类型对象的别名(比如char指针)。
Ø TYPE_ALIAS_SET_KNOWN_P (TYPE_CHECK (NODE)->type.alias_set != -1)
² 对于该类型,如果基于类型的(typed-bases)的别名集(alias set)已被推算,该域为非0值。
Ø TYPE_ATTRIBUTES (TYPE_CHECK (NODE)->type.attributes)
² 用于该类型的属性节点链表。GCC提供丰富的属性集,作为C/C++语言的扩展。
Ø TYPE_ALIGN (TYPE_CHECK (NODE)->type.align)
² 该类型的对象所需要的对齐量(alignment)。它的值是整数,单位为比特。
Ø TYPE_USER_ALIGN (TYPE_CHECK (NODE)->type.user_align)
² 其值为1,如果该类型的对齐要求由"aligned"属性指定。否则为0。
Ø TYPE_ALIGN_UNIT (TYPE_ALIGN (NODE) / BITS_PER_UNIT)
² 单位为字节的对齐量。
Ø TYPE_NO_FORCE_BLK (TYPE_CHECK (NODE)->type.no_force_blk_flag)
² 在RECORD_TYPE,UNION_TYPE或者QUAL_UNION_TYPE节点中,它表示该类型,因为缺少对齐量的要求,拥有BLKmode(参见后面有关tree_mode的章节)。
Ø TYPE_IS_SIZETYPE (INTEGER_TYPE_CHECK (NODE)->type.no_force_blk_flag)
² 在INTEGER_TYPE节点中,它表示该节点代表一个尺寸(size)。我们将其用于合法性检验(validity checking)而且它使得对其它类型不安全的优化得以执行。注意到C中的size_t类型不能设置此标识。类型size_t只是一个对普通的整数类型的typedef,这个整数类型恰好是sizeof的返回类型。这个位设置上的所有表达式,都代表真实的尺寸(actual sizes)。
Ø TYPE_RETURNS_STACK_DEPRESSED
(FUNCTION_TYPE_CHECK (NODE)->type.no_force_blk_flag)
² 在FUNCTION_TYPE中,表示函数返回时不改变栈指针(with the stack pointer depressed)。
Ø TYPE_STRING_FLAG (TYPE_CHECK (NODE)->type.string_flag)
² 如果在ARRAY_TYPE中设置,表示字符串类型(对于区分字符串和字符数组的语言而言)。如果在SET_TYPE中设置,表示bitstring类型。
Ø TYPE_VECTOR_SUBPARTS
GET_MODE_NUNITS (VECTOR_TYPE_CHECK (VECTOR_TYPE)->type.mode)
² 对于VECTOR_TYPE,表示vector的sub-part的数目。
Ø TYPE_NEEDS_CONSTRUCTING
(TYPE_CHECK (NODE)->type.needs_constructing_flag)
² 表示该类型的对象,在创建时必须调用相应的函数进行初始化。
Ø TYPE_TRANSPARENT_UNION
(UNION_TYPE_CHECK (NODE)->type.transparent_union_flag)
² 表明该类型(UNION_TYPE)的对象,按该union类型的第一个成员的转递(passed)方法来传递。
Ø TYPE_NONALIASED_COMPONENT
(ARRAY_TYPE_CHECK (NODE)->type.transparent_union_flag)
² 对于ARRAY_TYPE,表明该类型的元素的地址不可访问。
Ø TYPE_PACKED (TYPE_CHECK (NODE)->type.packed_flag)
² 表明该类型的对象,有尽可能紧凑的布局。
Ø TYPE_LANG_FLAG_0 ~ TYPE_LANG_FLAG_6
² 由各前端使用。
Ø TYPE_NEXT_VARIANT (TYPE_CHECK (NODE)->type.next_variant)
² 用于链接所有通过不同类型修饰符(type modifier),例如:const 和volatile,来声明的类型。
Ø TYPE_MAIN_VARIANT (TYPE_CHECK (NODE)->type.main_variant)
² 在上述链表中,对于任意节点,该域指向链表的头(最基本的类型,没有任何类型修饰)。
列表2 tree_type中的标识位
指针类型的树节点,由下面的函数来构建。
3653 tree
3654 build_pointer_type (tree to_type) in tree.c
3655 {
3656 return build_pointer_type_for_mode (to_type, ptr_mode);
3657 }
参数to_type给出了对应的指针的类型,而函数build_pointer_type_for_mode的第二个参数是ptr_mode。同时这个函数也被用于创建指向vector 模式(mode)的数据。(参见后端,有关章节,genmodes工具)。
3625 tree
3626 build_pointer_type_for_mode (tree to_type, enum machine_mode mode) in tree.c
3627 {
3628 tree t = TYPE_POINTER_TO (to_type);
3629
3630 /* First, if we already have a type for pointers to TO_TYPE, use it. */
3631 if (t != 0 && mode == ptr_mode)
3632 return t;
3633
3634 t = make_node (POINTER_TYPE);
3635
3636 TREE_TYPE (t) = to_type;
3637 TYPE_MODE (t) = mode;
3638
3639 /* Record this type as the pointer to TO_TYPE. */
3640 if (mode == ptr_mode)
3641 TYPE_POINTER_TO (to_type) = t;
3642
3643 /* Lay out the type. This function has many callers that are concerned
3644 with expression-construction, and this simplifies them all.
3645 Also, it guarantees the TYPE_SIZE is in the same obstack as the type. */
3646 layout_type (t);
3647
3648 return t;
3649 }
对于指针要指向的类型,首先要确保有关该类型的信息已经被收集。这包括:类型占的字节数(域TYPE_SIZE_UNIT),类型占的比特数(域TYPE_SIZE)以及最合适的模式(mode)。而对于聚集类型(比如,C++中的数组类型,struct/union/class),除此之外,它们中的每个数据成员都有自己的对齐要求,离开类起始地址的偏移,加入对齐量后由字节、比特衡量的大小。这些由函数layout_type来实现。
下面我们仅看与指针类型有关的代码。其它情形会在有关章节中碰到。注意对于基本类型,所以关于大小,符号,对齐量的信息都由节点的模式(mode)指出(关于模式的概念,可参见)。
1516 void
1517 layout_type (tree type) in stor-layout..c
1518 {
1519 if (type == 0)
1520 abort ();
1521
1522 /* Do nothing if type has been laid out before. */
1523 if (TYPE_SIZE (type))
1524 return;
1525
1526 switch (TREE_CODE (type))
1527 {
…
1602 case POINTER_TYPE:
1603 case REFERENCE_TYPE:
1604 {
1605
1606 enum machine_mode mode = ((TREE_CODE (type) == REFERENCE_TYPE
1607 && reference_types_internal)
1608 ? Pmode : TYPE_MODE (type));
1609
1610 int nbits = GET_MODE_BITSIZE (mode);
1611
1612 TYPE_SIZE (type) = bitsize_int (nbits);
1613 TYPE_SIZE_UNIT (type) = size_int (GET_MODE_SIZE (mode));
1614 TREE_UNSIGNED (type) = 1;
1615 TYPE_PRECISION (type) = nbits;
1616 }
1617 break;
…
1795 }
…
1797 if (TREE_CODE (type) != RECORD_TYPE
1798 && TREE_CODE (type) != UNION_TYPE
1799 && TREE_CODE (type) != QUAL_UNION_TYPE)
1800 finalize_type_size (type);
…
1818 }
如果所有的REFERENCE_TYPE都是内建类型,第1607行的reference_types_internal是非零值,表明节点应该按Pmode来分配(Pmode是个宏,定义为目标机器上某个模式的别名),而不是ptr_mode模式(大小为POINTER_SIZE的模式)。这个全局变量由被前端调用的函数internal_reference_types设置。在C++,它一直保持为0。
而1804行的finalize_type_size由指定的模式,算出对齐量。
1363 static void
1364 finalize_type_size (tree type)
1365 {
1366 /* Normally, use the alignment corresponding to the mode chosen.
1367 However, where strict alignment is not required, avoid
1368 over-aligning structures, since most compilers do not do this
1369 alignment. */
1370
1371 if (TYPE_MODE (type) != BLKmode && TYPE_MODE (type) != VOIDmode
1372 && (STRICT_ALIGNMENT
1373 || (TREE_CODE (type) != RECORD_TYPE && TREE_CODE (type) != UNION_TYPE
1374 && TREE_CODE (type) != QUAL_UNION_TYPE
1375 && TREE_CODE (type) != ARRAY_TYPE)))
1376 {
1377 TYPE_ALIGN (type) = GET_MODE_ALIGNMENT (TYPE_MODE (type));
1378 TYPE_USER_ALIGN (type) = 0;
1379 }
…
1412 /* Also layout any other variants of the type. */
1413 if (TYPE_NEXT_VARIANT (type)
1414 || type != TYPE_MAIN_VARIANT (type))
1415 {
1416 tree variant;
1417 /* Record layout info of this variant. */
1418 tree size = TYPE_SIZE (type);
1419 tree size_unit = TYPE_SIZE_UNIT (type);
1420 unsigned int align = TYPE_ALIGN (type);
1421 unsigned int user_align = TYPE_USER_ALIGN (type);
1422 enum machine_mode mode = TYPE_MODE (type);
1423
1424 /* Copy it into all variants. */
1425 for (variant = TYPE_MAIN_VARIANT (type);
1426 variant != 0;
1427 variant = TYPE_NEXT_VARIANT (variant))
1428 {
1429 TYPE_SIZE (variant) = size;
1430 TYPE_SIZE_UNIT (variant) = size_unit;
1431 TYPE_ALIGN (variant) = align;
1432 TYPE_USER_ALIGN (variant) = user_align;
1433 TYPE_MODE (variant) = mode;
1434 }
1435 }
1436 }
对于由const,volatile,register修饰的类型节点,它们串接在一起,通过TYPE_MAIN_VARIANT指向没有修饰的类型节点,并通过TYPE_NEXT_VARIANT访问彼此。从行1413开始,我们需要对这些类型节点进行更新。
引用类型与指针类型在编译器内部并无本质的区别,这一点在3693行引用类型的模式是ptr_mode可看出。因此,其创建过程和指针类型非常相似。
3690 tree
3691 build_reference_type (tree to_type) in tree.c
3692 {
3693 return build_reference_type_for_mode (to_type, ptr_mode);
3694 }
3663 tree
3664 build_reference_type_for_mode (tree to_type, enum machine_mode mode) in tree.c
3665 {
3666 tree t = TYPE_REFERENCE_TO (to_type);
3667
3668 /* First, if we already have a type for pointers to TO_TYPE, use it. */
3669 if (t != 0 && mode == ptr_mode)
3670 return t;
3671
3672 t = make_node (REFERENCE_TYPE);
3673
3674 TREE_TYPE (t) = to_type;
3675 TYPE_MODE (t) = mode;
3676
3677 /* Record this type as the pointer to TO_TYPE. */
3678 if (mode == ptr_mode)
3679 TYPE_REFERENCE_TO (to_type) = t;
3680
3681 layout_type (t);
3682
3683 return t;
3684 }
如下3683行所示,由build_address返回的类型是tree_exp(更确切的,是地址表达式)。 关于ADDR_EXPR(地址表达式)的描述给出如下:
地址表达式(ADDR_EXPR)[2]
² 这些节点用于表示一个对象的地址。(这些表达式拥有指针或引用类型)。它们的操作数可能是另一个表达式,或者它可能是一个声明。作为扩展,GCC允许使用标签(label)的地址。在这种情况下,ADDR_EXPR的操作数应该是LABEL_DECL。而这样的表达式的类型是void*。如果被取址的对象不是左值(lvalue),一个临时对象会被创建,这个临时对象的地址被使用。
3675 tree
3676 build_address (tree t) in typeck.c
3677 {
3678 tree addr;
3679
3680 if (error_operand_p (t) || !cxx_mark_addressable (t))
3681 return error_mark_node;
3682
3683 addr = build1 (ADDR_EXPR, build_pointer_type (TREE_TYPE (t)), t);
3684 if (staticp (t))
3685 TREE_CONSTANT (addr) = 1;
3686
3687 return addr;
3688 }
在3680行,cxx_mark_addressable标识t以表明我们需要t的地址可被使用。 因此t不能存放在寄存器中。如果标识成功,函数返回true 。而在3684行的函数staticp检查地址是否引用了静态对象的内存(例如,一个字符串常量,标签,或者静态、全局变量)。这样的地址在运行时不会改变;相比之下在函数内的局部变量,在每次调用可能会有不同的地址,这样的地址不是常量。
在C/C++的编程中,我们可以使用宏offsetof来确定,特定的数据成员到对应的struct/class 的起始地址的偏移。这个宏得到的就是这里的OFFSET_TYPE节点。我们通过A.b或A->b来访问数据成员时,这个节点就会被使用。
3956 tree
3957 build_offset_type (tree basetype, tree type) in tree.c
3958 {
3959 tree t;
3960 unsigned int hashcode;
3961
3962 /* Make a node of the sort we want. */
3963 t = make_node (OFFSET_TYPE);
3964
3965 TYPE_OFFSET_BASETYPE (t) = TYPE_MAIN_VARIANT (basetype);
3966 TREE_TYPE (t) = type;
3967
3968 /* If we already have such a type, use the old one and free this one. */
3969 hashcode = TYPE_HASH (basetype) + TYPE_HASH (type);
3970 t = type_hash_canon (hashcode, t);
3971
3972 if (!COMPLETE_TYPE_P (t))
3973 layout_type (t);
3974
3975 return t;
3976 }
我们已经看过,TYPE_MAIN_VARIANT返回未修饰的类型节点,而其他被诸如,const和volatile修饰的类型节点通过TYPE_NEXT_VARIANT串接在一起。在确定偏移时,我们使用未修饰的类型。