大綱:linux
在ANSI C的任何一種實現中,存在兩個不一樣的環境。程序員
第1種是翻譯環境,在這個環境中源代碼被轉換爲可執行的機器指令。算法
第2種是執行環境,它用於實際執行代碼。編程
咱們上手的第一個C語言程序大體都是 「Hello World!」吧!vim
但是,不知道你們想過沒有?一個代碼文件,是怎麼轉換成了咱們可運行的exe文件呢,並且咱們都知道計算機是隻能看得懂二進制文件,因此再進一層說:數組
一個 .c 文件是怎麼轉換爲 .exe 文件的呢?這就是經過翻譯環境來實現的。而翻譯環境又包括編譯和連接,編譯又分爲預編譯,編譯和彙編。cookie
接下來,咱們就來看看它們到底幹了什麼:編程語言
預編譯過程主要處理以 # 開頭的語句,如#include,#define 等ide
而預編譯都幹了一些什麼呢,主要以下:模塊化
1.頭文件的包含
2.註釋的刪除
3.#define定義的符號的替換
4.處理一些條件預處理指令,如 #if 、 #ifdef 等等(這個咱們會在後面提到)
5.保留全部的 #pragma 命令
6.一些文本操做
假設咱們這裏有一份代碼:
#include<stdio.h> #define NUM 10 int main() { int i = 0; for(i=0;i<NUM;i++) { printf("%d \n",i); } return 0; }
咱們來看看它預編譯以後產生什麼:
# 1 "test.c" # 1 "<built-in>" # 1 "<命令行>" # 31 "<命令行>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 32 "<命令行>" 2 # 1 "test.c" # 1 "/usr/include/stdio.h" 1 3 4 # 27 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 375 "/usr/include/features.h" 3 4 # 1 "/usr/include/sys/cdefs.h" 1 3 4 # 392 "/usr/include/sys/cdefs.h" 3 4 # 1 "/usr/include/bits/wordsize.h" 1 3 4 # 393 "/usr/include/sys/cdefs.h" 2 3 4 # 376 "/usr/include/features.h" 2 3 4 # 399 "/usr/include/features.h" 3 4 # 1 "/usr/include/gnu/stubs.h" 1 3 4 # 10 "/usr/include/gnu/stubs.h" 3 4 # 1 "/usr/include/gnu/stubs-64.h" 1 3 4 # 11 "/usr/include/gnu/stubs.h" 2 3 4 # 400 "/usr/include/features.h" 2 3 4 # 28 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/local/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/stddef.h" 1 3 4 # 216 "/usr/local/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/stddef.h" 3 4 # 216 "/usr/local/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/stddef.h" 3 4 typedef long unsigned int size_t; # 34 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/include/bits/types.h" 1 3 4 # 27 "/usr/include/bits/types.h" 3 4 # 1 "/usr/include/bits/wordsize.h" 1 3 4 # 28 "/usr/include/bits/types.h" 2 3 4 typedef unsigned char __u_char; typedef unsigned short int __u_short; typedef unsigned int __u_int; typedef unsigned long int __u_long; typedef signed char __int8_t; typedef unsigned char __uint8_t; typedef signed short int __int16_t; typedef unsigned short int __uint16_t; typedef signed int __int32_t; typedef unsigned int __uint32_t; typedef signed long int __int64_t; typedef unsigned long int __uint64_t; typedef long int __quad_t; typedef unsigned long int __u_quad_t; # 130 "/usr/include/bits/types.h" 3 4 # 1 "/usr/include/bits/typesizes.h" 1 3 4 # 131 "/usr/include/bits/types.h" 2 3 4 typedef unsigned long int __dev_t; typedef unsigned int __uid_t; typedef unsigned int __gid_t; typedef unsigned long int __ino_t; typedef unsigned long int __ino64_t; typedef unsigned int __mode_t; typedef unsigned long int __nlink_t; typedef long int __off_t; typedef long int __off64_t; typedef int __pid_t; typedef struct { int __val[2]; } __fsid_t; typedef long int __clock_t; typedef unsigned long int __rlim_t; typedef unsigned long int __rlim64_t; typedef unsigned int __id_t; typedef long int __time_t; typedef unsigned int __useconds_t; typedef long int __suseconds_t; typedef int __daddr_t; typedef int __key_t; typedef int __clockid_t; typedef void * __timer_t; typedef long int __blksize_t; typedef long int __blkcnt_t; typedef long int __blkcnt64_t; typedef unsigned long int __fsblkcnt_t; typedef unsigned long int __fsblkcnt64_t; typedef unsigned long int __fsfilcnt_t; typedef unsigned long int __fsfilcnt64_t; typedef long int __fsword_t; typedef long int __ssize_t; typedef long int __syscall_slong_t; typedef unsigned long int __syscall_ulong_t; typedef __off64_t __loff_t; typedef __quad_t *__qaddr_t; typedef char *__caddr_t; typedef long int __intptr_t; typedef unsigned int __socklen_t; # 36 "/usr/include/stdio.h" 2 3 4 # 44 "/usr/include/stdio.h" 3 4 struct _IO_FILE; typedef struct _IO_FILE FILE; # 64 "/usr/include/stdio.h" 3 4 typedef struct _IO_FILE __FILE; # 74 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/libio.h" 1 3 4 # 32 "/usr/include/libio.h" 3 4 # 1 "/usr/include/_G_config.h" 1 3 4 # 15 "/usr/include/_G_config.h" 3 4 # 1 "/usr/local/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/stddef.h" 1 3 4 # 16 "/usr/include/_G_config.h" 2 3 4 # 1 "/usr/include/wchar.h" 1 3 4 # 82 "/usr/include/wchar.h" 3 4 typedef struct { int __count; union { unsigned int __wch; char __wchb[4]; } __value; } __mbstate_t; # 21 "/usr/include/_G_config.h" 2 3 4 typedef struct { __off_t __pos; __mbstate_t __state; } _G_fpos_t; typedef struct { __off64_t __pos; __mbstate_t __state; } _G_fpos64_t; # 33 "/usr/include/libio.h" 2 3 4 # 50 "/usr/include/libio.h" 3 4 # 1 "/usr/local/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/stdarg.h" 1 3 4 # 40 "/usr/local/lib/gcc/x86_64-pc-linux-gnu/8.3.0/include/stdarg.h" 3 4 typedef __builtin_va_list __gnuc_va_list; # 51 "/usr/include/libio.h" 2 3 4 # 145 "/usr/include/libio.h" 3 4 struct _IO_jump_t; struct _IO_FILE; # 155 "/usr/include/libio.h" 3 4 typedef void _IO_lock_t; struct _IO_marker { struct _IO_marker *_next; struct _IO_FILE *_sbuf; int _pos; # 178 "/usr/include/libio.h" 3 4 }; enum __codecvt_result { __codecvt_ok, __codecvt_partial, __codecvt_error, __codecvt_noconv }; # 246 "/usr/include/libio.h" 3 4 struct _IO_FILE { int _flags; char* _IO_read_ptr; char* _IO_read_end; char* _IO_read_base; char* _IO_write_base; char* _IO_write_ptr; char* _IO_write_end; char* _IO_buf_base; char* _IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; int _flags2; __off_t _old_offset; unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; # 294 "/usr/include/libio.h" 3 4 __off64_t _offset; # 303 "/usr/include/libio.h" 3 4 void *__pad1; void *__pad2; void *__pad3; void *__pad4; size_t __pad5; int _mode; char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; }; typedef struct _IO_FILE _IO_FILE; struct _IO_FILE_plus; extern struct _IO_FILE_plus _IO_2_1_stdin_; extern struct _IO_FILE_plus _IO_2_1_stdout_; extern struct _IO_FILE_plus _IO_2_1_stderr_; # 339 "/usr/include/libio.h" 3 4 typedef __ssize_t __io_read_fn (void *__cookie, char *__buf, size_t __nbytes); typedef __ssize_t __io_write_fn (void *__cookie, const char *__buf, size_t __n); typedef int __io_seek_fn (void *__cookie, __off64_t *__pos, int __w); typedef int __io_close_fn (void *__cookie); # 391 "/usr/include/libio.h" 3 4 extern int __underflow (_IO_FILE *); extern int __uflow (_IO_FILE *); extern int __overflow (_IO_FILE *, int); # 435 "/usr/include/libio.h" 3 4 extern int _IO_getc (_IO_FILE *__fp); extern int _IO_putc (int __c, _IO_FILE *__fp); extern int _IO_feof (_IO_FILE *__fp) __attribute__ ((__nothrow__ , __leaf__)); extern int _IO_ferror (_IO_FILE *__fp) __attribute__ ((__nothrow__ , __leaf__)); extern int _IO_peekc_locked (_IO_FILE *__fp); extern void _IO_flockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__)); extern void _IO_funlockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__)); extern int _IO_ftrylockfile (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__)); # 465 "/usr/include/libio.h" 3 4 extern int _IO_vfscanf (_IO_FILE * __restrict, const char * __restrict, __gnuc_va_list, int *__restrict); extern int _IO_vfprintf (_IO_FILE *__restrict, const char *__restrict, __gnuc_va_list); extern __ssize_t _IO_padn (_IO_FILE *, int, __ssize_t); extern size_t _IO_sgetn (_IO_FILE *, void *, size_t); extern __off64_t _IO_seekoff (_IO_FILE *, __off64_t, int, int); extern __off64_t _IO_seekpos (_IO_FILE *, __off64_t, int); extern void _IO_free_backup_area (_IO_FILE *) __attribute__ ((__nothrow__ , __leaf__)); # 75 "/usr/include/stdio.h" 2 3 4 typedef __gnuc_va_list va_list; # 90 "/usr/include/stdio.h" 3 4 typedef __off_t off_t; # 102 "/usr/include/stdio.h" 3 4 typedef __ssize_t ssize_t; typedef _G_fpos_t fpos_t; # 164 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/bits/stdio_lim.h" 1 3 4 # 165 "/usr/include/stdio.h" 2 3 4 extern struct _IO_FILE *stdin; extern struct _IO_FILE *stdout; extern struct _IO_FILE *stderr; extern int remove (const char *__filename) __attribute__ ((__nothrow__ , __leaf__)); extern int rename (const char *__old, const char *__new) __attribute__ ((__nothrow__ , __leaf__)); extern int renameat (int __oldfd, const char *__old, int __newfd, const char *__new) __attribute__ ((__nothrow__ , __leaf__)); extern FILE *tmpfile (void) ; # 209 "/usr/include/stdio.h" 3 4 extern char *tmpnam (char *__s) __attribute__ ((__nothrow__ , __leaf__)) ; extern char *tmpnam_r (char *__s) __attribute__ ((__nothrow__ , __leaf__)) ; # 227 "/usr/include/stdio.h" 3 4 extern char *tempnam (const char *__dir, const char *__pfx) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ; extern int fclose (FILE *__stream); extern int fflush (FILE *__stream); # 252 "/usr/include/stdio.h" 3 4 extern int fflush_unlocked (FILE *__stream); # 266 "/usr/include/stdio.h" 3 4 extern FILE *fopen (const char *__restrict __filename, const char *__restrict __modes) ; extern FILE *freopen (const char *__restrict __filename, const char *__restrict __modes, FILE *__restrict __stream) ; # 295 "/usr/include/stdio.h" 3 4 # 306 "/usr/include/stdio.h" 3 4 extern FILE *fdopen (int __fd, const char *__modes) __attribute__ ((__nothrow__ , __leaf__)) ; # 319 "/usr/include/stdio.h" 3 4 extern FILE *fmemopen (void *__s, size_t __len, const char *__modes) __attribute__ ((__nothrow__ , __leaf__)) ; extern FILE *open_memstream (char **__bufloc, size_t *__sizeloc) __attribute__ ((__nothrow__ , __leaf__)) ; extern void setbuf (FILE *__restrict __stream, char *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__)); extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf, int __modes, size_t __n) __attribute__ ((__nothrow__ , __leaf__)); extern void setbuffer (FILE *__restrict __stream, char *__restrict __buf, size_t __size) __attribute__ ((__nothrow__ , __leaf__)); extern void setlinebuf (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int fprintf (FILE *__restrict __stream, const char *__restrict __format, ...); extern int printf (const char *__restrict __format, ...); extern int sprintf (char *__restrict __s, const char *__restrict __format, ...) __attribute__ ((__nothrow__)); extern int vfprintf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg); extern int vprintf (const char *__restrict __format, __gnuc_va_list __arg); extern int vsprintf (char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__nothrow__)); extern int snprintf (char *__restrict __s, size_t __maxlen, const char *__restrict __format, ...) __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 4))); extern int vsnprintf (char *__restrict __s, size_t __maxlen, const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 0))); # 412 "/usr/include/stdio.h" 3 4 extern int vdprintf (int __fd, const char *__restrict __fmt, __gnuc_va_list __arg) __attribute__ ((__format__ (__printf__, 2, 0))); extern int dprintf (int __fd, const char *__restrict __fmt, ...) __attribute__ ((__format__ (__printf__, 2, 3))); extern int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...) ; extern int scanf (const char *__restrict __format, ...) ; extern int sscanf (const char *__restrict __s, const char *__restrict __format, ...) __attribute__ ((__nothrow__ , __leaf__)); # 443 "/usr/include/stdio.h" 3 4 extern int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...) __asm__ ("" "__isoc99_fscanf") ; extern int scanf (const char *__restrict __format, ...) __asm__ ("" "__isoc99_scanf") ; extern int sscanf (const char *__restrict __s, const char *__restrict __format, ...) __asm__ ("" "__isoc99_sscanf") __attribute__ ((__nothrow__ , __leaf__)) ; # 463 "/usr/include/stdio.h" 3 4 extern int vfscanf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__format__ (__scanf__, 2, 0))) ; extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__format__ (__scanf__, 1, 0))) ; extern int vsscanf (const char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0))); # 494 "/usr/include/stdio.h" 3 4 extern int vfscanf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vfscanf") __attribute__ ((__format__ (__scanf__, 2, 0))) ; extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vscanf") __attribute__ ((__format__ (__scanf__, 1, 0))) ; extern int vsscanf (const char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vsscanf") __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0))); # 522 "/usr/include/stdio.h" 3 4 extern int fgetc (FILE *__stream); extern int getc (FILE *__stream); extern int getchar (void); # 550 "/usr/include/stdio.h" 3 4 extern int getc_unlocked (FILE *__stream); extern int getchar_unlocked (void); # 561 "/usr/include/stdio.h" 3 4 extern int fgetc_unlocked (FILE *__stream); extern int fputc (int __c, FILE *__stream); extern int putc (int __c, FILE *__stream); extern int putchar (int __c); # 594 "/usr/include/stdio.h" 3 4 extern int fputc_unlocked (int __c, FILE *__stream); extern int putc_unlocked (int __c, FILE *__stream); extern int putchar_unlocked (int __c); extern int getw (FILE *__stream); extern int putw (int __w, FILE *__stream); extern char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream) ; # 640 "/usr/include/stdio.h" 3 4 # 665 "/usr/include/stdio.h" 3 4 extern __ssize_t __getdelim (char **__restrict __lineptr, size_t *__restrict __n, int __delimiter, FILE *__restrict __stream) ; extern __ssize_t getdelim (char **__restrict __lineptr, size_t *__restrict __n, int __delimiter, FILE *__restrict __stream) ; extern __ssize_t getline (char **__restrict __lineptr, size_t *__restrict __n, FILE *__restrict __stream) ; extern int fputs (const char *__restrict __s, FILE *__restrict __stream); extern int puts (const char *__s); extern int ungetc (int __c, FILE *__stream); extern size_t fread (void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream) ; extern size_t fwrite (const void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __s); # 737 "/usr/include/stdio.h" 3 4 extern size_t fread_unlocked (void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream) ; extern size_t fwrite_unlocked (const void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream); extern int fseek (FILE *__stream, long int __off, int __whence); extern long int ftell (FILE *__stream) ; extern void rewind (FILE *__stream); # 773 "/usr/include/stdio.h" 3 4 extern int fseeko (FILE *__stream, __off_t __off, int __whence); extern __off_t ftello (FILE *__stream) ; # 792 "/usr/include/stdio.h" 3 4 extern int fgetpos (FILE *__restrict __stream, fpos_t *__restrict __pos); extern int fsetpos (FILE *__stream, const fpos_t *__pos); # 815 "/usr/include/stdio.h" 3 4 # 824 "/usr/include/stdio.h" 3 4 extern void clearerr (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int feof (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern int ferror (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern void clearerr_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int feof_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern int ferror_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern void perror (const char *__s); # 1 "/usr/include/bits/sys_errlist.h" 1 3 4 # 26 "/usr/include/bits/sys_errlist.h" 3 4 extern int sys_nerr; extern const char *const sys_errlist[]; # 854 "/usr/include/stdio.h" 2 3 4 extern int fileno (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern int fileno_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; # 873 "/usr/include/stdio.h" 3 4 extern FILE *popen (const char *__command, const char *__modes) ; extern int pclose (FILE *__stream); extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__)); # 913 "/usr/include/stdio.h" 3 4 extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); # 943 "/usr/include/stdio.h" 3 4 # 2 "test.c" 2 # 4 "test.c" int result = 0; int ADD(int x, int y) { return x + y; } int main() { int a = 10; result = ADD(a, 10); printf("%d+%d=%d\n", a, 10, result); return 0; }
咱們看到test.i文件竟然包含了快900行代碼,並且咱們會看到如今的生成的文件效果跟咱們上面所提到的相符,如刪除註釋,替換#define替換的符號等
注:
能夠用 set nu 來查看行數
怎麼生成 .i文件(即預編譯後的文件)呢?
在gcc編譯器下咱們可使用以下指令:
1. gcc -E test.c > test.i
2. gcc -E test.c -o test.i
在預編譯以後就是編譯了,咱們能夠用 gcc -S test.c > test.s 來生成編譯後的文件,咱們能夠用vim test.s來查看其內容,內容以下:
.file "test.c" .text .globl result .bss .align 4 .type result, @object .size result, 4 result: .zero 4 .text .globl ADD .type ADD, @function ADD: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl -4(%rbp), %edx movl -8(%rbp), %eax addl %edx, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size ADD, .-ADD .section .rodata .LC0: .string "%d+%d=%d\n" .text .globl main .type main, @function main: .LFB1: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $10, -4(%rbp) movl -4(%rbp), %eax movl $10, %esi movl %eax, %edi call ADD movl %eax, result(%rip) movl result(%rip), %edx movl -4(%rbp), %eax movl %edx, %ecx movl $10, %edx movl %eax, %esi movl $.LC0, %edi movl $0, %eax call printf movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size main, .-main .ident "GCC: (GNU) 8.3.0" .section .note.GNU-stack,"",@progbits
而程序在編譯階段是將咱們的c代碼轉爲了彙編代碼,包含以下內容:
1.詞法分析
首先源代碼程序被輸入到掃描器(Scanner),掃描器的任務很簡單,它只是簡單地進 行詞法分析,運用一種相似於有限狀態機(Finite State Machine)的算法能夠很輕鬆地將源 代碼的字符序列分割成一系列的記號(Token)。
詞法分析產生的記號通常能夠分爲以下幾類:關鍵字、標識符、字面量(包含數字、字 符串等)和特殊符號(如加號、等號〉。在識別記號的同時,掃描器也完成了其餘工做。好比將標識符存放到符號表,將數字、字符串常景存放到文字表等,以備後面的步驟使用。 有一個叫作lex的程序能夠實現同法掃描,它會按照用戶以前描述好的詞法規則將輸入的字符串分割成一個個記號。由於這樣一個程序的存在,編譯器的開發者就無須爲每一個編譯器開發一個獨立的詞法掃描器,而是根據須要改變詞法規則就能夠了。
另外對於一些有預處理的語言,好比C語言,它的宏替換和文件包含等工做通常不納入編譯器的範圍而交給一個獨立的預處理器。
2.語法分析
接下來語法分析器(Grammar Parser)將對由掃描器產生的記號進行語法分析,從而 產生語法樹(SyntaxTree)。整個分析過程採用了上下文無關語法(Context-free Grammar) 的分析手段,若是你對上下文無關語法及下推自動機很熟悉,那麼應該很好理解。不然,能夠參考一些計算理論的資料,通常都會有很詳細的介紹。此處再也不贅述。簡單地講,由語法分析器生成的語法樹就是以表達式(Expression)爲節點的樹。咱們知道,C語言的一個語句是一個表達式,而複雜的語句是不少表達式的組合。
上面例子中的語句就是一個由賦值表達式、加法表達式、乘法表達式、數組表達式、括號表達式組成的複雜語句。它在通過語法分析器之後造成如圖所示的語法樹。
此例文件:
從圖屮咱們能夠看到,整個語句被看做是一個賦值表達式;賦值表達式的左邊是一個數組表達式,它的右邊是一個乘法表達式;數組表達式又由兩個符號表達式組成,等等。 符號和數字是最小的表達式,它們不是由其餘的表達式來組成的,因此它們一般做爲整個語法樹的葉節點。在語法分析的同時,不少運算符號的優先級和含義也被肯定下來了。好比乘法表達式的優先級比加法高,而圓括號表達式的優先級比乘法髙,等等。另外有些符號具備多重含義,好比星號*在C語言中能夠表示乘法表達式,也能夠表示對指針取內容的表達式, 因此語法分析階段必須對這些內容進行區分。若是出現了表達式不合法,好比各類括號不匹配、表達式中缺乏操做符等,編譯器就會報告語法分析階段的錯誤。 正如前面詞法分析有lex 同樣,語法分析也有一個現成的工具叫作yacc (Yet Another CompilerCompiler)。它也像lex 樣,能夠根據用戶給定的語法規則對輸入的記號序列進行 解析,從而構建出一棵語法樹。對於不一樣的編程語言,編譯器的開發者只須改變語法規則, 而無須爲每一個編譯器編寫一個語法分析器,因此它又被稱爲「編譯器編譯器(Compiler Compiler)**。
3.語義分析
接下米進行能夠看到,每一個表達式(包括符號和數字)都被標識了類型。咱們的例子中幾乎全部的 表達式都是整型的,因此無須作轉換,整個分析過程很順利。語義分析器還對符號表裏的符 號類型也作了更新。的是語義分析,由語義分析器(Semantic Analyzer)來完成。語法分析僅 儀是完成了對錶達式的語法層面的分析,可是它並不瞭解這個語句是否真正有意義。好比C 語言裏囪兩個指針作乘法運算是沒有意義的,可是這個語句在語法上是合法的;好比一樣一 個指針和一個浮點數作乘法運算是否合法等。編譯器所能分析的語義是靜態語義(Static Semantic),所謂靜態語義是指在編譯期能夠肯定的語義,與之對應的動態語義(DynamicSemantic)就是隻有在運行期才能肯定的語義。 靜態語義一般包括聲明和類型的匹配,類型的轉換。好比當一個浮點型的表達式賦值給 一個整型的表達式時,其屮隱含了一個浮點型到整型轉換的過程,語義分析過程當中須要完成 這個步驟。好比將一個浮點型賦值給一個指針的時候,語義分析程序會發現這個類型不匹配, 編譯器將會報錯。動態語義通常指在運行期出現的語義相關的問題,好比將0做爲除數是一個運行期語義錯誤。 通過語義分析階段之後,整個語法樹的表達式都被標識了類型,若是有些類型須要作隱式轉換,語義分析程序會在語法樹屮插入相應的轉換節點。上面描述的語法樹在通過語義分析階段之後成爲如圖2-4所示的形式。
能夠看到,每一個表達式(包括符號和數字)都被標識了類型。咱們的例子中幾乎全部的表達式都是整型的,因此無須作轉換,整個分析過程很順利。語義分析器還對符號表裏的符號類型也作了更新。
4.符號彙總
對於符號彙總,咱們能夠先生成 .o文件來查看一下咱們一開始的例子所擁有的符號,命令: gcc -c test.c ,而後再用 readelf -s test.o 來查看所擁有的符號。
咱們再對比源代碼,發現符號都是全局變量,以及一個咱們使用的庫函數,而這些符號都是所參與咱們編譯文件的符號的一個彙總,如:我如今有兩個.c文件,一個是含有add符號,另一個含有main符號,那麼,彙總後就是add,main。
這個階段的主要工做是將彙編代碼轉換爲二進制指令(包括造成符號表 --- 將彙總的符號與地址聯繫起來),若是咱們不指定閱讀器打開,就會顯示一堆亂碼。命令就是剛剛用來查看符號的第一步: gcc -c test.c 而後,咱們發現咱們的路徑底下多了一個test.o文件。(要是你使用的是coldblocks軟件來寫你的c程序,運行完再相應文件夾都會出現 .o 文件)
注:
造成符號表:
通過這些掃描、語法分析、語義分析、源代碼優化、代碼生成和目標代碼優化,編譯器 忙活了這麼多個步驟之後,源代碼終於被編譯成了目標代碼。可是這個目標代碼中有一個問 題是:一些函數符號的地址尚未肯定。若是咱們要把目標代碼使用匯編器編譯成真正可以在機器上執行的指令,那麼它們的地址應該從哪兒獲得呢?若是它們的定義在跟上面的源代碼同一個編譯單元裏面,那麼編譯器能夠爲它們分配空間, 肯定它們的地址:那若是是定義在其餘的程序模塊呢?
這個看似簡單的問題引出了咱們一個很大的話題:冃標代碼中有變量定義在其餘模塊, 該怎麼辦?事實上,定義其餘模塊的全局變量和函數在最終運行時的絕對地址都要在最終連接的時候才能肯定。因此現代的編譯器能夠將一個源代碼文件編譯成一個未連接的目標文 件,而後由連接器最終將這些目標文件連接起來造成可執行文件。讓咱們帶着這個問題,走進連接的世界。
連接:
程序沒計的模塊化是人們一直在追求的目標,由於當一個系統十分複雜的時候,咱們不得不將一個複雜的系統逐步分割成小的系統以達到各個突破的目的。一個複雜的軟件也如 此,人們把毎個源代碼模塊獨立地編譯,而後按照需要將它們「組裝」起來,這個組裝模塊的過程就是連接
連接的主要內容就是把各個模塊之間相互引用的部分都處理好, 使得各個模塊之間可以正確地銜接。
gcc下的命令是: gcc test.o ,而後咱們會發現,路徑底下多了一個叫a.out的文件,這就至關於咱們的exe文件,咱們能夠 ./a.out 來運行它,看是不是咱們想要的結果。
而它又在這個過程幹了什麼呢?
1.合併段表
咱們在上方提到,彙編以後就會生成相應的 .o 文件,而這個 .o 文件又被分爲幾個段,在連接過程當中就會把相同的段經過某種規則合併起來。
2.符號表的合併和定位
將全部文件的符號彙總在一塊兒,在配上相應的地址。
3.重定位
若不一樣源文件出現了相同的符號,好比說咱們在一個源文件裏定義一個函數,在另外一個源文件裏用extern來聲明這個函數,因此在符號表彙總的時候會出現兩個此函數的符號,但應其中一個是非法地址(如extern的那個)因此最終只留下一個此符號。
也就是說,在連接過程當中,會把目標文件和連接庫(庫函數信息)連接起來,最後生成可執行程序。
注意:
好比所,咱們使用了一個咱們並未定義的函數,而編譯器就是在這一步才能看到錯誤。
這即是程序的翻譯環境,再來簡練一下:
1. 預處理 選項 gcc -E test.c -o test.i 預處理完成以後就停下來,預處理以後產生的結果都放在test.i文件中。
2. 編譯 選項 gcc -S test.c 編譯完成以後就停下來,結果保存在test.s中。
3. 彙編 gcc -c test.c 彙編完成以後就停下來,結果保存在test.o中。
程序執行的過程:
1. 程序必須載入內存中。在有操做系統的環境中:通常這個由操做系統完成。在獨立的環境中,程序的載入必須 由手工安排,也多是經過可執行代碼置入只讀內存來完成。
2. 程序的執行便開始。接着便調用main函數。
3. 開始執行程序代碼。這個時候程序將使用一個運行時堆棧(stack),存儲函數的局部變量和返回地址。程序同 時也可使用靜態(static)內存,存儲於靜態內存中的變量在程序的整個執行過程一直保留他們的值。
4. 終止程序。正常終止main函數;也有多是意外終止。
----參考:《程序員的自我修養——連接、裝載與庫》
|------------------------------------------------------------------
到此,對於程序的運行環境的簡易瞭解便到此結束!
因筆者水平有限,如有錯誤,還望指正!
詞法分析產生的記號通常能夠分爲以下幾類:關鍵字、標識符、字面量(包含數字、字 符串等)和特殊符號(如加號、等號〉。在識別記號的同時,掃描器也完成了其餘工做。比 如將標識符存放到符號表,將數字、字符串常景存放到文字表等,以備後面的步驟使用。有一個叫作lex的程序能夠實現同法掃描,它會按照用戶以前描述好的詞法規則將輸入 的字符串分割成一個個記號。由於這樣-個程序的存在,編譯器的開發者就無須爲每一個編譯 器開發•個獨立的詞法掃描器,而是根據須要改變詞法規則就能夠了。另外對T一些有預處理的語言,好比C語言,它的宏替換和文件包含等T做通常不歸 入編譯器的範圍而交給一個獨立的預處理器。