LCC编译器的源程序分析(2)(3)


 /***********************************
 *作者:蔡军生
 *出处: http://blog.csdn.net/caimouse/
 ************************************/
  LCC编译器的源程序分析(2)LCC编译器的预处理
上面已经介绍了C编译器的目标,其实在实现这个目标之前,是经历了很多阶段处理的,其中第一个阶段的处理,就是预处理。预处理的任务是做什么呢?在LCC里预处理主要是把所有包含的头文件和源程序生成一个中间文件,并且把所有宏展开,替换为真实的值。前面介绍的例子源程序,经过预处理后,就会生成下面的代码,如下:
 
#line 1 "hello.c"
#line 1 "include/stdio.h"
 
typedef unsigned int size_t;
typedef unsigned short wchar_t;
typedef wchar_t wint_t;
typedef wchar_t wctype_t;
typedef char *    va_list;
#line 39 "include/stdio.h"
 
struct _iobuf {
 char *_ptr;
 int   _cnt;
 char *_base;
 int   _flag;
 int   _file;
 int   _charbuf;
 int   _bufsiz;
 char *_tmpfname;
 };
typedef struct _iobuf FILE;
 
 extern FILE *_iob;
 typedef long fpos_t;
 extern FILE (*__imp__iob)[];
 
  int _filbuf(FILE *);
int flsbuf(int, FILE *);
 
FILE * _fsopen(const char *, const char *, int);
 
void clearerr(FILE *);
int fclose(FILE *);
int _fcloseall(void);
 
FILE * fdopen(int, const char *);
 
int feof(FILE *);
int ferror(FILE *);
int fflush(FILE *);
int fgetc(FILE *);
wchar_t fgetwc(FILE *);
wchar_t getwc(FILE *);
 
int _fgetchar(void);
int fgetpos(FILE *, fpos_t *);
char * fgets(char *, int, FILE *);
 
int fileno(FILE *);
 
 
int _flushall(void);
FILE * fopen(const char *, const char *);
int fprintf(FILE *, const char *, ...);
int xfprintf(FILE *,const char *,...);
int fputc(int, FILE *);
int _fputchar(int);
int fputs(const char *, FILE *);
size_t fread(void *, size_t, size_t, FILE *);
FILE * freopen(const char *, const char *, FILE *);
int fscanf(FILE *, const char *, ...);
int xfscanf(FILE *,const char *,...);
int fsetpos(FILE *, const fpos_t *);
int fseek(FILE *, long, int);
long ftell(FILE *);
size_t fwrite(const void *, size_t, size_t, FILE *);
int getc(FILE *);
int getchar(void);
char * gets(char *);
int getw(FILE *);
int _pclose(FILE *);
 
FILE * popen(const char *, const char *);
 
int printf(const char *, ...);
int xprintf(const char *,...);
int dprintf(const char *, ...);
int putc(int, FILE *);
int putchar(int);
int puts(const char *);
int _putw(int, FILE *);
int remove(const char *);
int rename(const char *,const char *);
void rewind(FILE *);
int _rmtmp(void);
int scanf(const char *, ...);
int xscanf(const char *,...);
void setbuf(FILE *, char *);
int setvbuf(FILE *, char *, int, size_t);
int _snprintf(char *, size_t, const char *, ...);
int sprintf(char *, const char *, ...);
int xsprintf(char *, const char *, ...);
int snprintf(char *,size_t,const char *,...);
int xsnprintf(char *,size_t,const char *,...);
int sscanf(const char *, const char *, ...);
int xsscanf(const char *,const char *,...);
char * _tempnam(char *, char *);
FILE * tmpfile(void);
char * tmpnam(char *);
char *tempnam(char *,char *);
int ungetc(int, FILE *);
int _unlink(const char *);
 
int vfprintf(FILE *, const char *, va_list);
int vprintf(const char *, va_list);
int _vsnprintf(char *, size_t, const char *, va_list);
int _vsnwprintf(wchar_t *, size_t, const wchar_t *, va_list);
int vsnprintf(char *,size_t,const char *,va_list);
int xvsnprintf(char *,size_t,const char *,va_list);
int xvsnprintf(char *,size_t,const char *,va_list);
int vsprintf(char *, const char *, va_list);
int xvsprintf(char *, const char *, va_list);
void perror(const char *);
void _wperror(const wchar_t *);
 
 
#line 2 "hello.c"
 
int main(void)
{
 int nTest1 = 1;
 int nTest2 = 2;
 int nTest3;
 int i;
 
 nTest3 = nTest1 + nTest2;
 printf("nTest3 = %d/r/n",nTest3);
 
 for (i = 0; i < 5; i++)
 {
       printf("%d/r/n",nTest3+i);
 }
 
 printf("00:30:28"" ""Apr 07 2007""/r/nhello world/n");
 return 0;
}
 
现在就来分析一下经过预处理的中间文件。
#line 1 "hello.c"
这行告诉编译器要编译的源文件名称和行号。这里是hello.c的第一行。
#line 1 "include/stdio.h"
在hello.c的第一行里包含了一个头文件include/stdio.h,而这个头文件源程序紧跟着后面,直到#line 2 "hello.c"行前,都是包含的头文件源程序。在那行的后面才是hello.c的源程序,可以看到在main函数里的宏定义也被替换掉了。如下:
printf("00:30:28"" ""Apr 07 2007""/r/nhello world/n");
预处理把宏定义__TIME__变成了"00:30:28"字符串,把宏定义__DATE__变成了"Apr 07 2007"。这两个宏定义生成的字符串跟编译时间相关,其实就是把当前编译的时间生成字符放到相应的位置。
 
上面的中间源程序是LCC预处理生成样子,没有删除任何一行,即使是空行也没有删除一行。其实在C里,还有使用#define定义的宏定义,也会替换掉的。因此,在宏定义处理里,没有复杂的计算,只是字符串的替换。在这里没有介绍完整的预处理代码,因为把主要精力放到C编译器去。
 
C编译器就是接收上面这样的中间文件,进行编译处理的。
 
 LCC编译器的源程序分析(3)选择不同的目标代码接口
在LCC里,最重要的一个特征是可以输出不同的目标代码。比如同一个C程序,可以生成MIPS,X86等汇编代码,只需要选择不同的目标参数。这里只分析生成X86的代码,所以命令行的参数如下:
rcc.exe -target=x86/nasm hello.i hello.asm
参数-target=x86/nasm是让C编译器选择生成X86的NASM汇编代码。
参数hello.i是前面已经介绍的中间文件,它是经过预处理的。
参数hello.asm是生成的目标文件。
 
C编译的入口函数是main,它在文件main.c里,定义如下:
//
//C编译器入口。
//蔡军生 2007/05/13 深圳
//
int main(int argc, char *argv[])
 
在main函数开始里,就是处理参数的代码,如下:
#001 int main(int argc, char *argv[])
#002 {
#003  int i, j;
#004
#005  for (i = argc - 1; i > 0; i--)
#006  {   
#007         if (strncmp(argv[i], "-target=", 8) == 0)
#008         {
#009               break;
#010         }   
#011  }
#012
#013  if (i > 0)
#014  {
#015         char *s = strchr(argv[i], '//');
#016         if (s != NULL)
#017         {
#018               *s = '/';
#019         }   
#020
#021         for (j = 0; bindings[j].name && bindings[j].ir; j++)
#022         {
#023               if (strcmp(&argv[i][8], bindings[j].name) == 0)
#024               {
#025                    IR = bindings[j].ir;
#026                    break;
#027               }
#028         }
#029
#030         if (s != NULL)
#031         {
#032               *s = '//';
#033         }   
#034  }
#035 
#036  if (!IR)
#037  {
#038         fprint(stderr, "%s: unknown target", argv[0]);
#039         if (i > 0)
#040         {
#041               fprint(stderr, " `%s'", &argv[i][8]);
#042         }   
#043
#044         fprint(stderr, "; must specify one of/n");
#045
#046         for (i = 0; bindings[i].name; i++)
#047         {
#048               fprint(stderr, "/t-target=%s/n", bindings[i].name);
#049         }   
#050
#051         exit(EXIT_FAILURE);
#052  }
 
程序的第5行到第11行是找到目标代码的参数,也就是识别-target=参数。如果找到就跳出循环。
程序的第13行到第34行是找到相应的生成目标代码的接口。C编译器已经定义好可以生成代码的数组,因此这里只需要简单地比较一下名称,就可以找到相应的目标代码的接口了。接口定义的结构如下:
typedef struct binding {
 char *name;
 Interface *ir;
} Binding;
name保存目标代码的名称,ir保存了接口。而接口定义如下:
#001 typedef struct interface {
#002  Metrics charmetric;
#003  Metrics shortmetric;
#004  Metrics intmetric;
#005  Metrics longmetric;
#006  Metrics longlongmetric;
#007  Metrics floatmetric;
#008  Metrics doublemetric;
#009  Metrics longdoublemetric;
#010  Metrics ptrmetric;
#011  Metrics structmetric;
#012  unsigned little_endian:1;
#013  unsigned mulops_calls:1;
#014  unsigned wants_callb:1;
#015  unsigned wants_argb:1;
#016  unsigned left_to_right:1;
#017  unsigned wants_dag:1;
#018  unsigned unsigned_char:1;
#019 void (*address)(Symbol p, Symbol q, long n);
#020 void (*blockbeg)(Env *);
#021 void (*blockend)(Env *);
#022 void (*defaddress)(Symbol);
#023 void (*defconst) (int suffix, int size, Value v);
#024 void (*defstring)(int n, char *s);
#025 void (*defsymbol)(Symbol);
#026 void (*emit)    (Node);
#027 void (*export)(Symbol);
#028 void (*function)(Symbol, Symbol[], Symbol[], int);
#029 Node (*gen)     (Node);
#030 void (*global)(Symbol);
#031 void (*import)(Symbol);
#032 void (*local)(Symbol);
#033 void (*progbeg)(int argc, char *argv[]);
#034 void (*progend)(void);
#035 void (*segment)(int);
#036 void (*space)(int);
#037 void (*stabblock)(int, int, Symbol*);
#038 void (*stabend) (Coordinate *, Symbol, Coordinate **, Symbol *, Symbol *);
#039 void (*stabfend) (Symbol, int);
#040 void (*stabinit) (char *, int, char *[]);
#041 void (*stabline) (Coordinate *);
#042  void (*stabsym) (Symbol);
#043 void (*stabtype) (Symbol);
#044  Xinterface x;
#045 } Interface;
接口定义了输出汇编目标代码需要的函数和数据成员。以后再一个函数一个函数地介绍。
继续分析main函数里,第36行到第52行是找不到生成目标代码接口的出错处理。
命令行的参数是-target=x86/nasm,所以找到的接口,就是x86/name的接口,它保存在IR全局变量里。
            到这里,就分析完成选择不同的目标代码生成接口了。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值