在 GCC 编译一个 C 源代码时,先会通过宏处理,形成 一个叫转译单元(translation_unit),接着进行语法分析,C 的语法分析入口是
static void c_parser_translation_unit(c_parser *parser);
接着就通过类似递归下降解析器(Recursive descent parser)的方式进行语法解析。当完成一个函数的解析后,会将该函数加入到符号表(Symbol Table)中的调用图(Call Graph)的节点集(nodes)中。通过下面 gdb 的 backtrace 可以看到:
#0 cgraph_node::get_create (decl=0x7ffff79b2700) at <gcc-project>/gcc/cgraph.cc:537
#1 0x00000000012d06d4 in c_genericize (fndecl=0x7ffff79b2700) at <gcc-project>/gcc/c-family/c-gimplify.cc:796
#2 0x00000000011587ec in finish_function (end_loc=33408) at <gcc-project>/gcc/c/c-decl.cc:11501
#3 0x00000000011d9734 in c_parser_declaration_or_fndef at <gcc-project>/gcc/c/c-parser.cc:3010
#4 0x00000000011d680e in c_parser_external_declaration (parser=0x7ffff7fbc5b0) at <gcc-project>/gcc/c/c-parser.cc:2088
#5 0x00000000011d6254 in c_parser_translation_unit (parser=0x7ffff7fbc5b0) at <gcc-project>/gcc/c/c-parser.cc:1952
#6 0x000000000123cf9e in c_parse_file () at <gcc-project>/gcc/c/c-parser.cc:29613
#7 0x00000000012f68c8 in c_common_parse_file () at <gcc-project>/gcc/c-family/c-opts.cc:1379
#8 0x0000000001bd64d3 in compile_file () at <gcc-project>/gcc/toplev.cc:452
#9 0x0000000001bd9b1e in do_compile () at <gcc-project>/gcc/toplev.cc:2200
#10 0x0000000001bd9fb9 in toplev::main (this=0x7fffffffd3d2, argc=19, argv=0x7fffffffd518) at <gcc-project>/gcc/toplev.cc:2354
#11 0x0000000004482a35 in main (argc=19, argv=0x7fffffffd518) at <gcc-project>/gcc/main.cc:39
cgraph_node::get_create 就是对于一个解析后的函数所构建的调用图中的节点,调用节点(cgraph_node)。
由此可见,符号表(Symbol Table)会包含整个转译单元中的符号,对于函数的话,会形成对应的调用节点(cgraph_node:symtab_node)。
此时,加入以调用节点加入到符号表中的函数是已经解析好,以GENERIC 中间语言所表示的。
加入后,cc1 调用 cgraph_node::finalize_function 进一步处理。如下图所描述的。
这里,关键是说明 C 源代码在解析(Parse)后,所存在的形式,如何提供到后面基于过程(Pass)机制的处理。
那么此时,在compile_file函数中,如下图:
经过 lang_hooks.parse_file (); 后,gcc 已经将C的源代码解析完毕(Parsed)并以调用图的方式存放符号表(Symbol Table)中。
在接着的 symtab->finalize_compilation_unit (); 就开始通过以处理过程(Passes)的方式,将GENERIC中间语言转换成最终的汇编语言。