天天看點

GCC-3.4.6源代碼學習筆記(152)

5.12.5.2.2.2.2.          預設實參

如果耗盡了 arg ,但 parm 還沒有, parm 必然包含了預設實參,該形參清單應該以特殊節點 void_list_node 來結尾。那麼在對應的節點中, TREE_VALUE 儲存了該類型,而 TREE_PURPOSE 是預設實參的表達式。

4258 tree

4259 convert_default_arg (tree type, tree arg, tree fn, int parmnum)                                    in call.c

4260 {

4261   

4263    if (TREE_CODE (arg) == DEFAULT_ARG)

4264    {

4265      error ("the default argument for parameter %d of `%D' has "

4266            "not yet been parsed",

4267            parmnum, fn);

4268      return error_mark_node;

4269    }

4270

4271    if (fn && DECL_TEMPLATE_INFO (fn))

4272      arg = tsubst_default_argument (fn, type, arg);

4273

4274    arg = break_out_target_exprs (arg);

4275

4276    if (TREE_CODE (arg) == CONSTRUCTOR)

4277    {

4278      arg = digest_init (type, arg, 0);

4279      arg = convert_for_initialization (0, type, arg, LOOKUP_NORMAL,

4280                                "default argument", fn, parmnum);

4281    }

4282    else

4283    {

4284     

4285      if (TREE_HAS_CONSTRUCTOR (arg))

4286        arg = copy_node (arg);

4287

4288      arg = convert_for_initialization (0, type, arg, LOOKUP_NORMAL,

4289                                "default argument", fn, parmnum);

4290      arg = convert_for_arg_passing (type, arg);

4291    }

4292

4293    return arg;

4294 }

節點 DEFAULT_ARG 是為未解析的預設實參所建構的。記得在解析類定義的過程中,預設實參被 DEFAULT_ARG 所緩存,它将在該解析結束後才解析。是以在這裡不應該出現 DEFAULT_ARG (如果是這樣,這可能是在類定義中缺少了“ }; ”),而且在這一點上,前端不知道如何處理這種節點。

那麼在 4274 傳遞給下面函數的 arg ,是由該函數所有調用所共享的預設實參。不過,正如【 3 】所定義的,“每次調用該函數都要評估預設實參 ”,是以在真正評估這個預設實參之前,需要 準備合适的 arg 的拷貝(我們不能直接改變 arg ),并如下所示地更新這個局部臨時對象。

1259 tree

1260 break_out_target_exprs (tree t)                                                                  in cp/tree.c

1261 {

1262    static int target_remap_count;

1263    static splay_tree target_remap;

1264

1265    if (!target_remap_count++)

1266      target_remap = splay_tree_new (splay_tree_compare_pointers,

1267                                  NULL,

1268                                 NULL);

1269    walk_tree (&t, bot_manip , target_remap, NULL);

1270    walk_tree (&t, bot_replace , target_remap, NULL);

1271

1272    if (!--target_remap_count)

1273    {

1274      splay_tree_delete (target_remap);

1275      target_remap = NULL;

1276    }

1277

1278    return t;

1279 }

上面, walk_tree 周遊以 t 為根的子樹,并在以 t 的編碼所選出的節點上執行給定的函數。在第一次周遊中,使用下面的函數。注意該函數總是傳回 NULL (下面的 copy_tree_r 傳回 NULL )來強制 walk_tree 執行深度優先的完整周遊(即, tp 可能是一個 tree_list ,其中的節點可以包含操作數,它們依次亦可能是 tree_list ,等等,通路将從底部開始向上);不過是否進入子樹(即操作數)由局部變量 walk_subtrees 來控制(它在下面的函數中作為實參 walk_subtrees 傳入)。在使用指定的函數處理樹節點前, walk_subtrees 被設定為 1 ;是指定的函數來決定該節點是否是感興趣的,并且需要進入。

1182 static tree

1183 bot_manip (tree* tp, int* walk_subtrees, void* data)                                     in cp/tree.c

1184 {

1185    splay_tree target_remap = ((splay_tree) data);

1186    tree t = *tp;

1187

1188    if (TREE_CONSTANT (t))

1189    {

1190     

1193      *walk_subtrees = 0;

1194      return NULL_TREE;

1195    }

1196    if (TREE_CODE (t) == TARGET_EXPR)

1197    {

1198      tree u;

1199

1200      if (TREE_CODE (TREE_OPERAND (t, 1)) == AGGR_INIT_EXPR)

1201      {

1202        mark_used (TREE_OPERAND (TREE_OPERAND (TREE_OPERAND (t, 1), 0), 0));

1203        u = build_cplus_new

1204              (TREE_TYPE (t), break_out_target_exprs (TREE_OPERAND (t, 1)));

1205      }

1206      else

1207      {

1208         u = build_target_expr_with_type

1209              (break_out_target_exprs (TREE_OPERAND (t, 1)), TREE_TYPE (t));

1210      }

1211

1212      

1213      splay_tree_insert (target_remap,

1214                      (splay_tree_key) TREE_OPERAND (t, 0),

1215                      (splay_tree_value) TREE_OPERAND (u, 0));

1216

1217     

1218      *tp = u;

1219     

1222      *walk_subtrees = 0;

1223      return NULL_TREE;

1224    }

1225    else if (TREE_CODE (t) == CALL_EXPR)

1226      mark_used (TREE_OPERAND (TREE_OPERAND (t, 0), 0));

1227

1228   

1229    return copy_tree_r (tp, walk_subtrees, NULL);

1230 }

對于常量或 TRAGET_EXPR 以外的節點, copy_tree_r 拷貝這個節點,如果它是 *_CST (看到它其實被上面的 TREE_CONSTANT 濾掉了),或表達式,或 TREE_LIST ,或 TREE_VEC ,或 OVERLOAD (由下面的 C++ 的鈎子 tree_chain_matters_p 來辨識,并注意到在這裡 walk_subtrees 沒有改變, walk_tree 将進入該節點的子節點,并繼續拷貝其結構)。

1966 tree

1967 copy_tree_r (tree *tp, int *walk_subtrees, void *data ATTRIBUTE_UNUSED)  in tree-inline.c

1968 {

1969    enum tree_code code = TREE_CODE (*tp);

1970

1971    

1972    if (IS_EXPR_CODE_CLASS (TREE_CODE_CLASS (code))

1973        || TREE_CODE_CLASS (code) == 'c'

1974        || code == TREE_LIST

1975        || code == TREE_VEC

1976        || (*lang_hooks .tree_inlining.tree_chain_matters_p) (*tp))

1977    {

1978      

1980      tree chain = TREE_CHAIN (*tp);

1981

1982     

1983      *tp = copy_node (*tp);

1984

1985     

1987      if (code == PARM_DECL || code == TREE_LIST

1988 #ifndef INLINER_FOR_JAVA

1989          || (*lang_hooks .tree_inlining.tree_chain_matters_p) (*tp)

1990          || STATEMENT_CODE_P (code))

1991        TREE_CHAIN (*tp) = chain;

1992

1993      

1995      if (TREE_CODE (*tp) == SCOPE_STMT)

1996        SCOPE_STMT_BLOCK (*tp) = NULL_TREE;

1997 #else

1998          || (*lang_hooks .tree_inlining.tree_chain_matters_p) (*tp))

1999        TREE_CHAIN (*tp) = chain;

2000 #endif

2001    }

2002    else if (TREE_CODE_CLASS (code) == 't')

2003      *walk_subtrees = 0;

2004

2005    return NULL_TREE;

2006 }

一個 TARGET_EXPR 代表一個臨時對象。其第一個操作數是表示這個臨時對象的一個 VAR_DECL 。其第二個操作數是該臨時對象的初始值。該初始值被評估,然後拷貝入這個臨時對象。

一個 AGGR_INIT_EXPR 代表,作為一個函數調用傳回值,或一個構造函數結果的初始化。一個 AGGR_INIT_EXPR 将僅出現作為一個 TARGET_EXPR 的第二個操作數。該 AGGR_INIT_EXPR 的第一個操作數是所調用函數的位址,就像在一個 CALL_EXPR 那樣。第二個操作數是以一個 TREE_LIST 形式傳遞給該函數的實參,同樣類似于在一個 CALL_EXPR 裡那樣。這個表達式的值由這個函數傳回。

那麼如果 AGGR_INIT_EXPR 被用于 TRAGET_EXPR 裡,遞歸 break_out_target_exprs 來拷貝這個節點。對于這個拷貝過來的表達式, build_cplus_new 為其初始化産生了代碼。

2007 tree

2008 build_cplus_new (tree type, tree init)                                                          in cp/tree.c

2009 {

2010    tree fn;

2011    tree slot;

2012    tree rval;

2013    int is_ctor;

2014

2015   

2017    abstract_virtuals_error (NULL_TREE, type);

2018

2019    if (TREE_CODE (init) != CALL_EXPR && TREE_CODE (init) != AGGR_INIT_EXPR)

2020      return convert (type, init);

2021

2022    fn = TREE_OPERAND (init, 0);

2023    is_ctor = (TREE_CODE (fn) == ADDR_EXPR

2024             && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL

2025             && DECL_CONSTRUCTOR_P (TREE_OPERAND (fn, 0)));

2026

2027    slot = build_local_temp (type);

2028

2029   

2037

2038   

2040    if (is_ctor || TREE_ADDRESSABLE (type))

2041    {

2042      rval = build (AGGR_INIT_EXPR, type, fn, TREE_OPERAND (init, 1), slot);

2043      TREE_SIDE_EFFECTS (rval) = 1;

2044      AGGR_INIT_VIA_CTOR_P (rval) = is_ctor;

2045    }

2046    else

2047      rval = init;

2048

2049    rval = build_target_expr (slot, rval);

2050

2051    return rval;

2052 }

現在需要更新在 TARGET_EXPR 中的臨時對象,因為我們不是在産生原始的 TARGET_EXPR 的上下文中。看到這個臨時對象成為了局部的,其 DECL_CONTEXT 被強制設定為 current_function_decl 。

253    static tree

254    build_local_temp (tree type)                                                                      in cp/tree.c

255    {

256      tree slot = build_decl (VAR_DECL, NULL_TREE, type);

257      DECL_ARTIFICIAL (slot) = 1;

258      DECL_CONTEXT (slot) = current_function_decl ;

259      layout_decl (slot, 0);

260      return slot;

261    }

注意如果對于這個 AGGR_INIT_EXPR , AGGR_INIT_VIA_CTOR_P 成立,表示這個初始化是通過一個構造函數調用實作。在 2042 行所建構的 AGGR_INIT_EXPR ,其第三個操作數是這個臨時對象,它總是一個 VAR_DECL 。而 init 是原來 TARGET_EXPR 中的 AGGR_INIT_EXPR (這個 AGGR_INIT_EXPR 的第三個操作數的位址被提取,并替換實參清單中的值。這個情況下,該表達式的值是提供給這個 AGGR_INIT_EXPR 的 VAR_DECL ;構造函數不傳回值)。

最後,這個新建構的 TARGET_EXPR 被 build_cplus_new 傳回。

而對于 TARGET_EXPR 的第二個操作數不是 AGGR_INIT_EXPR 的情況,該操作數由 build_target_expr_with_type 來處理。這裡如果 init 是 TARGET_EXPR ,它必定是由在 bot_manip 中 1209 行的 break_out_target_exprs 所建構的,這個節點正是我們期望的。

320    tree

321    build_target_expr_with_type (tree init, tree type)                                        in cp/tree.c

322    {

323      tree slot;

324   

325      if (TREE_CODE (init) == TARGET_EXPR)

326        return init;

327      else if (CLASS_TYPE_P (type) && !TYPE_HAS_TRIVIAL_INIT_REF (type)

328            && TREE_CODE (init) != COND_EXPR

329            && TREE_CODE (init) != CONSTRUCTOR

330            && TREE_CODE (init) != VA_ARG_EXPR)

331       

336        return force_rvalue (init);

337   

338      slot = build_local_temp (type);

339      return build_target_expr (slot, init);

340    }

上面,如果 TYPE_HAS_TRIVIAL_INIT_REF 不是 0 ,表示該拷貝初始化可以使用按位拷貝。對于這樣的情形,可以簡單地建構 TARGET_EXPR ;否則,需要執行一個左值到右值的轉換,包括如下所示的拷貝構造函數調用。

590    tree

591    force_rvalue (tree expr)                                                                                  in cvt.c

592    {

593      if (IS_AGGR_TYPE (TREE_TYPE (expr)) && TREE_CODE (expr) != TARGET_EXPR)

594        expr = ocp_convert (TREE_TYPE (expr), expr,

595                        CONV_IMPLICIT|CONV_FORCE_TEMP, LOOKUP_NORMAL);

596      else

597        expr = decay_conversion (expr);

598   

599      return expr;

600    }

不久之後我們将看到 ocp_convert 的細節。在這裡總而言之,該函數将産生調用合适的拷貝構造函數代碼,然後調用 build_cplus_new 來産生建構這個臨時對象及其初始化。在離開 bot_manip 之前,看一下 build_target_expr 。

234    static tree

235    build_target_expr (tree decl, tree value)                                                     in cp/tree.c

236    {

237      tree t;

238   

239      t = build (TARGET_EXPR, TREE_TYPE (decl), decl, value,

240              cxx_maybe_build_cleanup (decl), NULL_TREE);

241     

245      TREE_SIDE_EFFECTS (t) = 1;

246   

247      return t;

248    }

對于具有非平凡析構函數的臨時對象,編譯器需要産生代碼,在其越出其作用域時,通過調用這個析構函數摧毀這個臨時對象。是以在 240 行, cxx_maybe_build_cleanup 産生這些代碼,如果需要的話。

現在在 bot_manip 的 1213 行, u 是對應于 t 的 TARGET_EXPR 。它把新舊版本的臨時對象映射起來。然後,我們立即用新版本取代了舊版本。不過,在由 break_out_target_exprs 處理的節點的某些子節點中,可能仍然保留了這個舊版本的引用,它們需要如下的更新。

1236 static tree

1237 bot_replace (tree* t,                                                                                  in cp/tree.c

1238             int* walk_subtrees ATTRIBUTE_UNUSED ,

1239             void* data)

1240 {

1241    splay_tree target_remap = ((splay_tree) data);

1242

1243    if (TREE_CODE (*t) == VAR_DECL)

1244    {

1245      splay_tree_node n = splay_tree_lookup (target_remap,

1246                                       (splay_tree_key) *t);

1247      if (n)

1248        *t = (tree) n->value;

1249    }

1250

1251    return NULL_TREE;

1252 }

回到 convert_default_arg ,在 4274 行從 break_out_target_exprs 得到這個更新的 arg ,然後接下來的函數用于産生初始化代碼。

5.12.5.2.2.2.3.          省略實參

最後一個的可能就是省略實參,注意到省略實參與預設實參不可共存。在前端中,為了識别包含了省略實參的函數聲明,形參清單由 NULL ,而不是 void_list_node 來結尾。

4161 tree

4162 convert_arg_to_ellipsis (tree arg)                                                                       in call.c

4163 {

4164   

4168    arg = decay_conversion (arg);

4169    

4176    if (TREE_CODE (TREE_TYPE (arg)) == REAL_TYPE

4177       && (TYPE_PRECISION (TREE_TYPE (arg))

4178            < TYPE_PRECISION (double_type_node)))

4179      arg = convert_to_real (double_type_node, arg);

4180    else if (INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (arg)))

4181      arg = perform_integral_promotions (arg);

4182

4183    arg = require_complete_type (arg);

4184   

4185    if (arg != error_mark_node

4186        && !pod_type_p (TREE_TYPE (arg)))

4187    {

4188     

4194      if (!skip_evaluation )

4195        warning ("cannot pass objects of non-POD type `%#T' through `...'; "

4196                "call will abort at runtime", TREE_TYPE (arg));

4197      arg = call_builtin_trap ();

4198      arg = build (COMPOUND_EXPR, integer_type_node, arg,

4199                integer_zero_node);

4200    }

4201

4202    return arg;

4203 }

【 3 】的條文 5.2.2 “函數調用”,條款 7 如下定義了編譯器在省略實參上的行為。

7.  當一個給定的實參沒有對應的形參,該實參被以這樣的方式傳入——接受函數可以通過調用 va_arg ( 18.7 )來得到該實參值。左值到右值( 4.1 ),數組到指針( 4.2 ),及函數到指針( 4.3 )标準轉換在該實參表達式上執行。在這些轉換之後,如果該實參不是數字,枚舉值,指針,成員的指針,或類類型,該程式非法。如果該實參具有一個 non-POD 類類型(條文 9 ),其行為是未定義的。如果該實參具有适用于整型提升( 4.5 )的整型或枚舉類型,或适用于浮點提升( 4.6 )的浮點類型,在該調用之前,該實參的值被轉換到提升後的類型。這些提升被稱為預設實參提升 。

函數 decay_conversion 執行在 exp 中的轉換,它應用在當一個左值出現在一個右值上下文中時候。包括左值到右值,數組到指針,及函數到指針的轉換。

1335 tree

1336 decay_conversion (tree exp)                                                                       n typeck.c

1337 {

1338    tree type;

1339    enum tree_code code;

1340

1341    type = TREE_TYPE (exp);

1342    code = TREE_CODE (type);

1343

1344    if (code == REFERENCE_TYPE)

1345    {

1346      exp = convert_from_reference (exp);

1347      type = TREE_TYPE (exp);

1348      code = TREE_CODE (type);

1349    }

1350

1351    if (type == error_mark_node)

1352      return error_mark_node;

1353

1354    if (type_unknown_p (exp))

1355    {

1356      cxx_incomplete_type_error (exp, TREE_TYPE (exp));

1357      return error_mark_node;

1358    }

1359   

1360   

1361    if (TREE_CODE (exp) == CONST_DECL)

1362      exp = DECL_INITIAL (exp);

1363   

1367    else if (code != ARRAY_TYPE)

1368    {

1369      exp = decl_constant_value (exp);

1370      type = TREE_TYPE (exp);

1371    }

1372

1373    

1375

1376    if (code == VOID_TYPE)

1377    {

1378      error ("void value not ignored as it ought to be");

1379      return error_mark_node;

1380    }

1381    if (invalid_nonstatic_memfn_p (exp))

1382      return error_mark_node;

1383    if (code == FUNCTION_TYPE || is_overloaded_fn (exp))

1384      return build_unary_op (ADDR_EXPR, exp, 0);

1385    if (code == ARRAY_TYPE)

1386    {

1387      tree adr;

1388      tree ptrtype;

1389

1390      if (TREE_CODE (exp) == INDIRECT_REF)

1391        return build_nop (build_pointer_type (TREE_TYPE (type)),

1392                      TREE_OPERAND (exp, 0));

1393

1394      if (TREE_CODE (exp) == COMPOUND_EXPR)

1395      {

1396        tree op1 = decay_conversion (TREE_OPERAND (exp, 1));

1397        return build (COMPOUND_EXPR, TREE_TYPE (op1),

1398                  TREE_OPERAND (exp, 0), op1);

1399      }

1400

1401      if (!lvalue_p (exp)

1402         && ! (TREE_CODE (exp) == CONSTRUCTOR && TREE_STATIC (exp)))

1403      {

1404        error ("invalid use of non-lvalue array");

1405        return error_mark_node;

1406      }

1407

1408      ptrtype = build_pointer_type (TREE_TYPE (type));

1409

1410      if (TREE_CODE (exp) == VAR_DECL)

1411      {

1412        if (!cxx_mark_addressable (exp))

1413          return error_mark_node;

1414        adr = build_nop (ptrtype, build_address (exp));

1415        TREE_SIDE_EFFECTS (adr) = 0;  

1416        return adr;

1417      }

1418     

1420      adr = build_unary_op (ADDR_EXPR, exp, 1);

1421      return cp_convert (ptrtype, adr);

1422    }

1423

1424   

1426    if (! CLASS_TYPE_P (type))

1427      exp = cp_convert (TYPE_MAIN_VARIANT (type), exp);

1428

1429    return exp;

1430 }

首先對于 REFERENCE_TYPE ,它是一個左值,要把它轉換為右值,就應該使用被引用的值,而不再儲存引用。在前端中, INDIRECT_REF 為這個目标而建構(這是因為引用總是傳入其位址就像指針那樣;而在編譯器中,引用的模式( mode )與指針的相同,都是 ptr_mode ,參見 build_reference_type )。

566    tree

567    convert_from_reference (tree val)                                                                    in cvt.c

568    {

569      if (TREE_CODE (TREE_TYPE (val)) == REFERENCE_TYPE)

570        return build_indirect_ref (val, NULL);

571      return val;

572    }

記得指針是右值。當處理 ARRAY_TYPE 時,如果我們正在使用形如: int *a[i] ( a 是次元大于 1 的數組,例如, int A[2][2] )的表達式,滿足 1390 行的條件, 是以建構了形如: int** 的類型,并且看到轉換指針“ int *a[i] ”到“ int** ”不需要産生任何代碼,因而為這個轉換建構了 NOP_EXPR 。

而如果 exp 隻是一個數組的聲明,例如: int a[8] ,這個聲明的右值是“ int* ”。可以直接建構這個右值。不過,對于其他的情形,例如:“ tempA.a ”(這是一棵以 SCOPE_REF 為根節點的樹),就沒有可以直接使用的簡單規則,是以調用 build_unary_op 及 cp_convert 來執行合适的轉換。

正如在 convert_arg_to_ellipsis 的 4188 行的注釋所提到的,對于對應于 non-POD 類類型的未定義行為, GCC 以前使用按位拷貝,不過在目前版本中,這個行為在後面對 cp_expr_size 的調用将導緻異常終止。這個終止由下面“ __builtin_trap ”的調用觸發,在 4198 行它成為了 COMPOUND_EXPR 形式的 arg 的一部分。

4148 c tree

4149 call_builtin_trap (void)                                                                               in call.c

4150 {

4151    tree fn = IDENTIFIER_GLOBAL_VALUE (get_identifier ("__builtin_trap"));

4152

4153    my_friendly_assert (fn != NULL, 20030927);

4154    fn = build_call (fn, NULL_TREE);

4155    return fn;

4156 }

内建陷阱的行為是:如果目标機器定義了陷阱指令,就使用它;否則編譯器将調用 abort () (參考 expand_builtin_trap )。

下面是對 non-POD 類類型省略實參的一個有趣的測試:

#include <stdarg.h>

class A {

    public : virtual void func() {}

};

int func (int a, ...) {

    va_list ap;

    va_start(ap, a) ;

    va_arg(ap, A); 

    va_end(ap);

    return 1;

}

int main() {

    A a;

    func (1, a);      // sizeof (func (1, a))

}

編譯器将給出以下警告:

test2.cpp: In function ‘int func(int, ...)’:

test2.cpp:11: warning: cannot receive objects of non-POD type ‘class A’ through ‘...’; call will abort at runtime

test2.cpp: In function ‘int main()’:

test2.cpp:19: warning: cannot pass objects of non-POD type ‘class A’ through ’...’; call will abort at runtime

當執行這個程式時,得到錯誤: Illegal instruction.

不過,如果我們使用注釋中的語句,編譯器将給出警告:

test2.cpp: In function ‘int func(int, ...)’:

test2.cpp:11: warning: cannot receive objects of non-POD type ‘class A’ through ‘...’; call will abort at runtime

而在執行時,沒有産生錯誤,因為 func(1, a) 沒有被評估,除了其傳回值。

繼續閱讀