GCC-3.4.6源代码学习笔记(88)

5.12.   解析编译单元 声明序列

cp_parser_declaration_seq_opt 中,声明具体由 cp_parser_declaration 来处理,因此正常情况下,真正能落到它手上的符号,只有文件结尾符( CPP_EOF )。“ } ”及“ ; ”都是不正常的情形。在这么高级别的函数中多出“ } ”,意味着解析无法继续,只能退出,交由上面的 cp_parser_translation_unit 来产生错误信息(通常这也意味着,声明本身已经有问题,更准确的错误信息已经产生)。

 

6221 static void

6222 cp_parser_declaration_seq_opt ( cp_parser * parser)                                        in parser.c

6223 {

6224   while (true)

6225   {

6226     cp_token *token;

6227      

6228     token = cp_lexer_peek_token (parser->lexer);

6229      

6230     if (token->type == CPP_CLOSE_BRACE

6231        || token->type == CPP_EOF)

6232       break ;

6233      

6234     if (token->type == CPP_SEMICOLON)

6235     {

6236       /* A declaration consisting of a single semicolon is

6237         invalid. Allow it unless we're being pedantic.  */

6238       if (pedantic && !in_system_header )

6239            pedwarn ("extra `;'");

6240          cp_lexer_consume_token (parser->lexer);

6241       continue ;

6242     }

6243

6244     /* The C lexer modifies PENDING_LANG_CHANGE when it wants the

6245       parser to enter or exit implicit `extern "C"' blocks.  */

6246     while (pending_lang_change > 0)

6247     {

6248       push_lang_context (lang_name_c);

6249       --pending_lang_change ;

6250     }

6251     while (pending_lang_change < 0)

6252     {

6253       pop_lang_context ();

6254       ++pending_lang_change ;

6255     }

6256

6257     /* Parse the declaration itself.  */

6258     cp_parser_declaration (parser);

6259   }

6260 }

 

如果系统头文件不像 C 那样支持 C++ pending_lang_change 将得到使用。对于我们所假定的目标系统,这不是事实。因此,在 cp_parser_declaration_seq_opt 中,从 6246 6255 行代码被跳过。

根据【 3 】, declaration 的简要语法树列出如下。

declaration

block- declaration

function-definition

template-declaration

explicit-instantiation

linkage-specification

namespace-definition

GNU Ext __extension__ declaration

5.12.1.      学习编译过程的第一个例子

为了更好地理解解析器的行为及树形式的构建及内容,最好能走一遍一个真实程序的编译过程。而为了了解编译器兼容 C++ 标准的能力,我们需要仔细选择程序,使它能覆盖语言的大部分议题。

在这里我们选择了著名的泛型库 LOKI 作为例子。我们选用的源文件是那些定义了 SmallObj ,这个类定义了分配及回收小尺寸对象的高性能框架。这里我们跳过了预处理器部分的操作,前面的章节对此已经描述得很清楚。

在源文件 SmallObject.cc 中,在防止重复包含的宏后面,是如下的语句:

 

21    #include "Threads.h"                                                                           in Smallobject.c

22    #include "Singleton.h"

23    #include <cstddef>

24    #include <vector>

 

2 个被包含的文件是该工程的一部分,余下的是系统头文件。这是一些公司(包括我所服务的公司)所建议的规则—首先包含关系最紧密的头文件,出现得越靠后的文件越通用。

读入包含文件的过程也已在前面章节描述,我们这里亦跳过。首先在文件 Threads.h 中,我们看到如下的代码片段(忽略了宏):

 

34    namespace Loki

35    {

36   

37    // class template SingleThreaded

38    // Implementation of the ThreadingModel policy used by various classes

39    // Implements a single-threaded model; no synchronization

40   

41   

42        template <class Host>

43        class SingleThreaded

44        {

45        public :

46            struct Lock

47            {

48                Lock() {}

49                Lock(const Host&) {}

50            };

51           

52            typedef Host VolatileType;

53   

54            typedef int IntType;

55   

56            static IntType AtomicAdd(volatile IntType& lval, IntType val)

57            { return lval += val; }

58           

59            static IntType AtomicSubtract(volatile IntType& lval, IntType val)

60            { return lval -= val; }

61   

62            static IntType AtomicMultiply(volatile IntType& lval, IntType val)

63            { return lval *= val; }

64           

65            static IntType AtomicDivide(volatile IntType& lval, IntType val)

66            { return lval /= val; }

67           

68            static IntType AtomicIncrement(volatile IntType& lval)

69            { return ++lval; }

70           

71            static IntType AtomicDivide(volatile IntType& lval)

72            { return --lval; }

73           

74            static void AtomicAssign(volatile IntType & lval, IntType val)

75            { lval = val; }

76           

77            static void AtomicAssign(IntType & lval, volatile IntType & val)

78            { lval = val; }

79        };

117   }

 

这个代码的奥秘和作用在《 C++ 设计新思维》(作者 Andrei Alexandrescu )中有精辟的解释,令人叹为观止。

5.12.2.      第一条语句 – NAMESPACE_DECL

解析器看到的第一条语句是“ namespace Loki ”,在 cp_parser_declaration 里,它将被下面的代码片段所处理:

 

cp_parser_declaration (continue)

 

6335   /* If the next token is `namespace', check for a named or unnamed

6336      namespace definition.  */

6337   else if (token1.keyword == RID_NAMESPACE

6338         && (/* A named namespace definition.  */

6339            (token2.type == CPP_NAME

6340         && (cp_lexer_peek_nth_token (parser->lexer, 3)->type

6341             == CPP_OPEN_BRACE))

6342             /* An unnamed namespace definition.  */

6343             || token2.type == CPP_OPEN_BRACE))

6344     cp_parser_namespace_definition (parser);

 

namespace-definition 的简要语法树如下。一个命名的名字空间可以跨越多个源文件。所有的定义都将被合并起来。在这些定义中,编译器看到的第一个定义就是 original-namespace-definition ,以后看到的都是 extension-namespace-definition 。不过两者都有相同的外观。相反,匿名名字空间只能被定义在一个文件里。

namespace-definition

named-namespace-definition

  |    original-namespace-definition

  |          namespace identifier { namespace-body }

  |                                declaration-seq [opt]

  |      extension-namespace-definition

  |          namespace original-namespace-name { namespace-body }

  |                        identifier

  unnamed-namespace-definition

      namespace { namespace-body }

 

9538       static void

9539       cp_parser_namespace_definition (cp_parser* parser)                               in parser.c

9540       {

9541         tree identifier;

9542      

9543         /* Look for the `namespace' keyword.  */

9544         cp_parser_require_keyword (parser, RID_NAMESPACE, "`namespace'");

9545      

9546         /* Get the name of the namespace. We do not attempt to distinguish

9547           between an original-namespace-definition and an

9548           extension-namespace-definition at this point. The semantic

9549           analysis routines are responsible for that.  */

9550         if (cp_lexer_next_token_is (parser->lexer, CPP_NAME))

9551           identifier = cp_parser_identifier (parser);

9552         else

9553           identifier = NULL_TREE;

9554      

9555         /* Look for the `{' to start the namespace.  */

9556         cp_parser_require (parser, CPP_OPEN_BRACE, "`{'");

9557         /* Start the namespace.  */

9558         push_namespace (identifier);

9559         /* Parse the body of the namespace.  */

9560         cp_parser_namespace_body (parser);

9561         /* Finish the namespace.  */

9562         pop_namespace ();

9563         /* Look for the final `}'.  */

9564         cp_parser_require (parser, CPP_CLOSE_BRACE, "`}'");

9565       }

 

创建 std名字空间 一节描述了加入名字空间作用域的细节。简而言之,在向全局名字空间加入名字空间 Loki 后,我们将得到如下图的树的简明布局。看到现在名字空间 Loki scope_chain 指向,作为当前作用域(绑定域)。

46 :加入 Loki 名字空间后的简明布局

cp_parser_namespace_body 的定义如下。

 

9572 static void

9573 cp_parser_namespace_body (cp_parser* parser)                                           in parser.c

9574 {

9575   cp_parser_declaration_seq_opt (parser);

9576 }

 

我们又跑回 cp_parser_declaration_seq_opt 了。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值