GCC源码分析(五) — 语法/语义分析之声明符解析(上)

版权声明:本文为CSDN博主「ashimida@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lidan113lidan/article/details/119974165

更多内容可关注微信公众号  

 c_parser_declaration_or_fndef在解析完声明说明符之后接着要解析的就是声明符,对于函数和声明来说,虽然产生式不一样但这一步基本是一样的(除了断言在之前已经处理过了),函数定义和声明的组成都是 "声明说明符 声明符 ... "开头的,所以在声明说明符解析完毕后不论是函数还是声明的解析,下一步都是要解析声明符, 而声明符的解析函数是:

c_parser_declaration_or_fndef =>  c_parser_declarator
   声明符实际上指的是如具体的变量或函数名,举例如下:
static int x, y=0;    // 这里的x和y都是声明符
int func(void x);     //这里的func和x都是声明符
int func(void x)       //这里的func和x都是声明符
{
  ......
}
// 声明说明符的意思是声明的说明符,其本质是多个说明符的组合,如这里的static, int都是一个单独的说明符
// 而"声明的"中的声明,指的就是这里的func,x,y,这些才是真正的声明符.
    c_parser_declarator函数是声明符的解析函数, 此函数的返回就代表一个声明符解析的结束,在声明和函数定义的解析过程中(c_parser_declaration_or_fndef),由于二者的产生式不完全相同(在一个声明中产生式是初始声明符列表,而函数定义中是单一的声明符),所以此函数默认是需循环解析多个声明符的,而如果解析完一个声明符发现是函数定义,则无需再解析下一个,其源码简化如下:
static void c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, bool static_assert_ok, bool empty_ok,
                bool nested, bool start_attr_ok, tree *objc_foreach_object_declaration, vec<c_token> omp_declare_simd_clauses,
                struct oacc_routine_data *oacc_routine_data, bool *fallthru_attr_p)
{
  struct c_declspecs *specs;
  ......
  specs = build_null_declspecs ();    /* 为声明说明符结构体(struct c_declspecs)分配空间 */
  ......

  /* 解析声明说明符,所有的说明符(如static int x, y; 中的static int分别是两个说明符)的信息最终都会被记录到同一个 c_declspecs 结构体中返回(到specs)  */
  c_parser_declspecs (parser, specs, true, true, start_attr_ok, true, true, cla_nonabstract_decl);

    /* 代表声明说明符解析完毕,此函数只对部分关键字修正类型 */
  finish_declspecs (specs);

  /* 不论声明还是函数定义,声明说明符后面至少要有一个声明符,这里的while循环用来解析一个或多个声明符 */
  while (true)
    {
      /* 
        c_declarator指针代表一个声明符解析的结果,其通常是一个链表,链表中的每个元素都是一 个c_declarator,称为c声明符,如 int * p; 实际上是由两个c声明符链接成的,
        第一个c声明符代表指针(这里的*),第二个c声明符代表标识符p,二者链接后的c_declarator结构体,才代表int *p;中*p整个声明符
      */
      struct c_declarator *declarator;
      bool dummy = false;
      tree fnbody = NULL_TREE;
      /* 解析一个声明符, 如 int x,y; 中的x */
      declarator = c_parser_declarator (parser, specs->typespec_kind != ctsk_none, C_DTR_NORMAL, &dummy);
      
      /* 声明说明符 声明符 解析完毕,如果其后面接的是 =/;/,/或者一些c汇编等其他的, 则走这里, 除了函数定义(后面接函数体),基本都走这里 */
      if (c_parser_next_token_is (parser, CPP_EQ)
      || c_parser_next_token_is (parser, CPP_COMMA)      
      || c_parser_next_token_is (parser, CPP_SEMICOLON)  
      || c_parser_next_token_is_keyword (parser, RID_ASM)
      || c_parser_next_token_is_keyword (parser, RID_ATTRIBUTE)
      || c_parser_next_token_is_keyword (parser, RID_IN))
    {
      /* 如果已经识别到一个声明了,那么后续不可能再出现函数定义了 */
      fndef_ok = false;
      /* 解析到等号则说明此声明符为初始声明符,需要赋初值,如 int x = 0; */
      if (c_parser_next_token_is (parser, CPP_EQ))
        {
          /* 为当前的声明符构建一个声明节点,除了赋值外其他操作这里都完成了 */
          d = start_decl (declarator, specs, true, chainon (postfix_attrs, all_prefix_attrs));
          start_init (d, asm_name, global_bindings_p (), &richloc);
          init = c_parser_initializer (parser);
          flag_sanitize = flag_sanitize_save;
          finish_init ();
        }
      else
        {
          /* 非等号的情况下不用赋初值,但是也同样要构建声明节点 */
          tree d = start_decl (declarator, specs, false, chainon (postfix_attrs, all_prefix_attrs));
          if (d & ...)
            DECL_ARGUMENTS (d) = declarator->u.arg_info->parms;
          if (d)
            finish_decl (d, UNKNOWN_LOCATION, NULL_TREE,NULL_TREE, asm_name);
        }
        
      /* 到这里代表声明说明符 声明符均解析完毕,且声明节点也生成完毕了,若后续为逗号,则要循环解析下一个声明符 */
      if (c_parser_next_token_is (parser, CPP_COMMA))
        {
          c_parser_consume_token (parser);
          ......
          continue;
        }
      else if (c_parser_next_token_is (parser, CPP_SEMICOLON))
        {
          /* 这里是分号的情况,直接消耗掉且代表当前声明解析完毕了 */
          c_parser_consume_token (parser);
          return;
        }
      else error; return;
    }
      
    /* 到这里(没有匹配到;/=/,等符号),则说明这是一个函数定义,开始解析函数体 */
      if (!start_function (specs, declarator, all_prefix_attrs))
    ......
}
    可以看到在声明或函数定义的解析过程中默认是循环解析声明符的, c_parser_declarator一次只解析一个声明符, 并最终返回一个c_declarator指针(若存在嵌套则嵌套的内部会完全解析完毕,如 int func(int x, int y); 在返回函数func(int x, int y)的c_declarator节点时(注意这个整体叫做函数,func叫做标识符),int x, int y的声明节点都已经构建完毕,并连接到c_declarator->arg_info->parms中了), 此函数的大体流程如下:
/* 此函数实际上只递归处理了声明符开头的多个指针部分,其余部分(直接声明符),是通过子函数c_parser_direct_declarator处理的,每个c声明符的结果通过c_declarator结构体层层包裹并返回 */
struct c_declarator *
c_parser_declarator (c_parser *parser, bool type_seen_p, c_dtr_syn kind,
             bool *seen_id)
{
  /* 对于指针开头的声明符,单独处理指针,剩余部分递归处理 */
  if (c_parser_next_token_is (parser, CPP_MULT))
    {
      /* 指针和直接声明符之间可能会有类型限定符和属性,这里先处理下类型限定符和属性(若有),然后再递归处理,如 int * const p; 中的const */
      struct c_declspecs *quals_attrs = build_null_declspecs ();
      struct c_declarator *inner;
      c_parser_consume_token (parser);        /* 消耗掉此 * 语法元素 */
      /* 复用声明说明符解析函数解析类型限定符列表,只解析attribute和类型限定符 两类说明符 */
      c_parser_declspecs (parser, quals_attrs, false, false, true, false, false, cla_prefer_id);

      /* *之后还有可能有*,产生式中没有*个数限制, 故这里再按照正常的声明符递归解析 */
      inner = c_parser_declarator (parser, type_seen_p, kind, seen_id);
      if (inner == NULL)
        return NULL;    /* NULL代表解析出错 */
      else
      /* 为*生成一个新的c_declarator,包裹内部的解析结果返回,内部(inner)则是递归后最终通过 c_parser_direct_declarator解析出来的 */
    return make_pointer_declarator (quals_attrs, inner);
    }
  /* 非指针开头的情况,调用此函数解析直接声明符,最终返回一个c_declarator链表 */
  return c_parser_direct_declarator (parser, type_seen_p, kind, seen_id);
}

    c_parser_declarator递归处理语法符号*后,最终调用其子函数c_parser_direct_declarator解析直接说明符,其代码简化如下:

/* 
  direct-declarator:          
    identifier T4                          
    (attributes[opt] declarator) T4  
  此函数只是用来解析上面的产生式中非T4的部分的,而T4的部分则是通过其子函数c_parser_direct_declarator_inner解析的,
 非T4的部分是声明符的标识符部分,T4部分是声明符[],或()内部包裹的部分,故其解析函数中有一个inner.
  故此函数中只是识别了声明符中的标识符部分(若有),包括int x;中的x,或 int func(...)中的func,或int p[...]中的p;
*/
static struct c_declarator *
c_parser_direct_declarator (c_parser *parser, bool type_seen_p, c_dtr_syn kind,
                bool *seen_id)
{
   /* 正常除了 int (*p)(...);这种,以标识符开头的case基本都走这里 */
  if (kind != C_DTR_ABSTRACT
      && c_parser_next_token_is (parser, CPP_NAME)                             
      && ((type_seen_p         
       && (c_parser_peek_token (parser)->id_kind == C_ID_TYPENAME
           || c_parser_peek_token (parser)->id_kind == C_ID_CLASSNAME))
      || c_parser_peek_token (parser)->id_kind == C_ID_ID))
    {
      /* 因为匹配到标识符了,这里纤维标识符构建一个c声明符(c_declarator)节点 */
      struct c_declarator *inner = build_id_declarator (c_parser_peek_token (parser)->value);  //1)
      /* 这里并没有区分标识符列表的解析,在 grokparms 函数中才会真正的检查  */
      *seen_id = true;
      inner->id_loc = c_parser_peek_token (parser)->location;
      c_parser_consume_token (parser);
      /*
         这里传入的inner是标识符节点, 如果当前是一个普通标识符定义,那么inner直接返回,如果是函数或数组定义,以函数int func(int x, int y); 为例, 最终返回的c_declarator代表的是函数
        func(int x, int y); 而其中的func是一个声明符,int x, int y是一个参数类型列表,二者都属于此函数的inner, 这里将标识符func当做inner传入,而此函数内部解析参数类型列表,
        最终返回的c_declarator则代表此函数.
      */
      return c_parser_direct_declarator_inner (parser, *seen_id, inner);
    }

  /* 参数列表解析过程中会递归到声明符解析,此时会调用到这里,如 int func(int [10])中 int [10]的解析会进入这里, 在参数列表中声明符可以省略标识符 */
  if (kind != C_DTR_NORMAL && c_parser_next_token_is (parser, CPP_OPEN_SQUARE))
    {
      /* 参数声明中的声明符可以省略标识符,此时设置标识符节点为空树,其他不变 */
      struct c_declarator *inner = build_id_declarator (NULL_TREE);
      inner->id_loc = c_parser_peek_token (parser)->location;
      return c_parser_direct_declarator_inner (parser, *seen_id, inner);
    }

  /* 若直接声明符的第一个符号不是标识符或参数解析时的方括号,那么就必须是圆括号,否则语法错误,这里解析的是圆括号的情况 */
  if (c_parser_next_token_is (parser, CPP_OPEN_PAREN))
    {
      ......
      /* 对于 int (*p)(...); 这种,先匹配 *p这个声明符  */
      inner = c_parser_declarator (parser, type_seen_p, kind, seen_id);
      /* *p的位置只能是一个声明符,然后必须就是闭括号了,否则报错 */
      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
      {
        ......
        /* 解析后面(...)的内容,并返回 */
        return c_parser_direct_declarator_inner (parser, *seen_id, inner);
      }
      else {
        /* (*p 后面没有直接闭括号,语法错误 */
        c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, "expected %<)%>");
        return NULL;
      }
    }
  else
    {
      /* 如果不是 open paren,且是在解析正常的直接声明符,则报错,没有这样的产生式,如 int , */
      if (kind == C_DTR_NORMAL) 
          c_parser_error (parser, "expected identifier or %<(%>"); return NULL;
      else
          return build_id_declarator (NULL_TREE);
    }
}

    其子函数c_parser_direct_declarator_inner则负责解析声明符中的T4表达式:

/*
   T4:                         
   array-declarator T4            
   ( parameter-type-list ) T4     
   ( identifier-list[opt] ) T4    
   empty
   T4表达式实际上代表一个声明符括号"内部"的东西,如 int func(...);或 int p[...];中的 ...
   此函数中的inner通常是标识符的c_declarator结构体,如这里的func,或p的c_declarator, func/p对于函数func(...)或数组p[...]来说,是其内部的标识符元素,且在解析到括号前
  已经解析完毕了,故会被当做此函数的inner传进来
*/
static struct c_declarator *
c_parser_direct_declarator_inner (c_parser *parser, bool id_present,
                  struct c_declarator *inner)
{
  /* 若 int fun 后面跟的是一个 open square "[" , 则说明是一个数组定义*/
  if (c_parser_next_token_is (parser, CPP_OPEN_SQUARE))
    {
      ......
      /* 解析数组 [...]内部的 ... */
      dimen = c_parser_expr_no_commas (parser, NULL);
      /* 右值转左值 */
      dimen = convert_lvalue_to_rvalue (brace_loc, dimen, true, true);
      /* 创建一个c声明符记录 [...]内部的内容 */
      declarator = build_array_declarator (brace_loc, dimen.value, quals_attrs, static_seen, star_seen);
      /* 再创建一个数组c声明符,将[...] 和数组标识符都记录其中,此声明符代表整个数组 */
      inner = set_array_declarator_inner (declarator, inner);
      /* 继续递归解析T4表达式,如果后面是empty就直接返回inner 了 */
      return c_parser_direct_declarator_inner (parser, id_present, inner);
    }
  /* 如果跟着的是 open paren "(", 则代表是函数声明/定义/调用 */
  else if (c_parser_next_token_is (parser, CPP_OPEN_PAREN))
    {           ......
      /* 
         遇到括号,则这里解析参数类型列表(parameter-type-list),或标识符列表(identifier-list[opt]) ,此函数最终返回
        一个c_arg_info结构体分别代表解析到的形参/实参列表.
      */
      args = c_parser_parms_declarator (parser, id_present, attrs);

      /* 遇到括号,则一定是函数声明或函数定义或函数调用,这里都会先生成一个函数声明的c声明符,参数分别为函数名(inner)和形参/实参列表(args)*/
      inner = build_function_declarator (args, inner);
      /* 这里是括号解析完毕后,递归重新解析T4表达式 */
      return c_parser_direct_declarator_inner (parser, id_present, inner);
    }
  /*  非括号的情况直接返回,这也是递归的最终终结位置 */
  return inner;
}
    c_parser_direct_declarator_inner的子函数c_parser_parms_declarator负责解析参数类型列表或标识符列表:
/*
    c_parser_parms_declarator负责解析的是( parameter-type-list )或( identifier-list[opt] )中括号内的两部分
  参数id_list_ok代表当前是否支持标识符列表的解析
  参数attrs是其父函数中解析出来的属性(对于参数列表可能非空,对于标识符列表一定为空)
*/
static struct c_arg_info *
c_parser_parms_declarator (c_parser *parser, bool id_list_ok, tree attrs)
{
  /* 新建一层scope */
  push_scope ();
  /* 标记当前scope是个参数解析用的 */
  declare_parm_level ();

  /* 这个if满足则确定当前语法符号满足标识符列表的条件,按照标识符列表解析 */
  if (id_list_ok && !attrs  && c_parser_next_token_is (parser, CPP_NAME) && ...)
    {
      tree list = NULL_TREE, *nextp = &list;
      /*
         循环解析标识符列表,其中的每个循环中的符号都必须是普通声明符,实际上就是解析identifier-list的产生式
        identifier-list:
            identifier
            identifier-list , identifier
      */
      while (c_parser_next_token_is (parser, CPP_NAME)
         && c_parser_peek_token (parser)->id_kind == C_ID_ID)
    {
      /* 对于CPP_NAME来说,value就是个标识符节点(lang_identifier),这里新建一个tree_list节点,此节点保存的内容为此标识符节点的指针 */
      *nextp = build_tree_list (NULL_TREE, c_parser_peek_token (parser)->value);
      /* 最终按照标识符列表的出现顺序,list链接了所有标识符, 如 func(x,y) 则 list = tree_list(x); list->chain = tree_list(y); */
      nextp = & TREE_CHAIN (*nextp);
      /* 解析标识符列表,若标识符之间不是逗号,则解析结束,如果是则消耗掉此逗号继续 */
      c_parser_consume_token (parser);
      if (c_parser_next_token_is_not (parser, CPP_COMMA)) break;
      c_parser_consume_token (parser);

      /* 逗号后面跟闭括号是错误的,如(a,) 正常应该是标识符后面跟括号,如 (a,b) */
      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN)) {
          c_parser_error (parser, "expected identifier"); break;
        }
    }

    /* 解析完之后,如果标识符列表后面是闭括号,则代表解析正确 */
      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
     {
      /* 构建并分配一个结构体 c_arg_info,此结构体用来返回结果 */
      struct c_arg_info *ret = build_arg_info ();
      /* 解析标识符列表的返回结果就是将这个tree_list记录到types中返回,其含义是实参(的标识符)列表 */
      ret->types = list;
      c_parser_consume_token (parser);
      /* 退出当前scope并返回 */
      pop_scope ();
      return ret;
     }
      else
      /* 如果标识符列表解析完成之后不是闭括号,则报错 */
      c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,"expected %<)%>"); pop_scope (); return ret;
    }
  /* 到这个else则代表当前不满足标识符列表的解析条件,则按照参数类型类表解析 */
  else
    {
      /*
         此函数用来解析当前的参数类型列表,并将解析出来的多个参数声明信息和类型信息链接到c_arg_info中返回,细节后续分析,
        这里仅先需要知道参数类型列表的解析结果也是通过一个c_arg_info结构体返回的即可.
      */
      struct c_arg_info *ret = c_parser_parms_list_declarator (parser, attrs, NULL);
      pop_scope ();
      return ret;
    }
}
    到这里就是声明符解析的大体流程,实际上梳理起来还是比较清晰的:
产生式:
   declarator:
     pointer[opt] direct-declarator

   direct-declarator:
     identifier T4                          
     (attributes[opt] declarator) T4      
  
   T4:                        
     array-declarator T4            
     ( parameter-type-list ) T4     
     ( identifier-list[opt] ) T4    
     empty

c_parser_declaration_or_fndef    /* 声明或函数定义解析 */
{
    /* 解析声明说明符 */
    c_parser_declspecs (parser, specs, true, true, start_attr_ok, true, true, cla_nonabstract_decl);
    while(true) {
        /* 解析一个声明符 */
        declarator = c_parser_declarator (parser, specs->typespec_kind != ctsk_none, C_DTR_NORMAL, &dummy);
        if(不继续解析)
            break;
    }
}

c_parser_declarator    /* 负责解析声明符(declarator) */
  => c_parser_direct_declarator  /* 负责解析直接声明符(direct-declarator) */
     => c_parser_direct_declarator_inner  /* 负责解析T4表达式 */
        => c_parser_parms_declarator          /* 负责解析参数类型列表(parameter-type-list)或标识符列表(identifier-list[opt]) */
   这里没有展开的细节主要是参数类型列表是如何解析的(也就是 c_parser_parms_list_declarator函数的解析流程),此外此函数的分析还涉及到整个scope的流程,故下一个分析中先介绍scope的工作原理.
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值