在 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
为了更好地理解解析器的行为及树形式的构建及内容,最好能走一遍一个真实程序的编译过程。而为了了解编译器兼容 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 )中有精辟的解释,令人叹为观止。
解析器看到的第一条语句是“ 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 了。