1.2.4. 與位址相關節點的構造
在C++中,指針、引用和位址在一定程度上可以混用。語言允許通過指針或者引用直接改變位址所在的内容。而函數調用,事實上是通過跳轉到相應的位址來實作。另外在語言中,數組名也代表數組的首位址。是以,編譯器可能需要首先建立與位址相關的節點,再由這個節點出發建構其它節點。比如,建構函數或數組節點。
1.2.4.1. 指針類型節點的構造
1.2.4.1.1. 用于表示類型的樹節點
用于表示語言中的類型的樹節點是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
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中的辨別位
1.2.4.1.2. 節點的建立
指針類型的樹節點,由下面的函數來建構。
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
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
3640 if (mode == ptr_mode)
3641 TYPE_POINTER_TO (to_type) = t;
3642
3643
3646 layout_type (t);
3647
3648 return t;
3649 }
對于指針要指向的類型,首先要確定有關該類型的資訊已經被收集。這包括:類型占的位元組數(域TYPE_SIZE_UNIT),類型占的比特數(域TYPE_SIZE)以及最合适的模式(mode)。而對于聚集類型(比如,C++中的數組類型,struct/union/class),除此之外,它們中的每個資料成員都有自己的對齊要求,離開類起始位址的偏移,加入對齊量後由位元組、比特衡量的大小。這些由函數layout_type來實作。
1.2.4.1.3. 類型的布局
下面我們僅看與指針類型有關的代碼。其它情形會在有關章節中碰到。注意對于基本類型,是以關于大小,符号,對齊量的資訊都由節點的模式(mode)指出(關于模式的概念,可參見)。
1516 void
1517 layout_type (tree type) in stor-layout..c
1518 {
1519 if (type == 0)
1520 abort ();
1521
1522
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
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
1413 if (TYPE_NEXT_VARIANT (type)
1414 || type != TYPE_MAIN_VARIANT (type))
1415 {
1416 tree variant;
1417
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
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開始,我們需要對這些類型節點進行更新。
1.2.4.2. 建立引用類型的節點
引用類型與指針類型在編譯器内部并無本質的差別,這一點在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
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
3678 if (mode == ptr_mode)
3679 TYPE_REFERENCE_TO (to_type) = t;
3680
3681 layout_type (t);
3682
3683 return t;
3684 }
1.2.4.3. 為位址表達式建立節點
如下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檢查位址是否引用了靜态對象的記憶體(例如,一個字元串常量,标簽,或者靜态、全局變量)。這樣的位址在運作時不會改變;相比之下在函數内的局部變量,在每次調用可能會有不同的位址,這樣的位址不是常量。
1.2.4.4. 為OFFSET_TYPE建立節點
在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
3963 t = make_node (OFFSET_TYPE);
3964
3965 TYPE_OFFSET_BASETYPE (t) = TYPE_MAIN_VARIANT (basetype);
3966 TREE_TYPE (t) = type;
3967
3968
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串接在一起。在确定偏移時,我們使用未修飾的類型。