C語言之簡易了解程序環境
大綱:
- 程序的翻譯環境
- 預編譯
- 編譯
- 匯編
- 鏈接
- 程序的運行環境
在ANSI C的任何一種實現中,存在兩個不同的環境。
第1種是翻譯環境,在這個環境中源代碼被轉換為可執行的機器指令。
第2種是執行環境,它用於實際執行代碼。
一.程序的翻譯環境
我們上手的第一個C語言程序大致都是 “Hello World!”吧!
可是,不知道大家想過沒有?一個代碼文件,是怎么轉換成了我們可運行的exe文件呢,而且我們都知道計算機是只能看得懂二進制文件,所以再進一層說:
一個 .c 文件是怎么轉換為 .exe 文件的呢?這就是通過翻譯環境來實現的。而翻譯環境又包括編譯和鏈接,編譯又分為預編譯,編譯和匯編。
接下來,我們就來看看它們到底干了什么:
1.預編譯
預編譯過程主要處理以 # 開頭的語句,如#include,#define 等
而預編譯都干了一些什么呢,主要如下:
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
2.編譯
在預編譯之后就是編譯了,我們可以用 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。
3.匯編
這個階段的主要工作是將匯編代碼轉換為二進制指令(包括形成符號表 --- 將匯總的符號與地址聯系起來),如果我們不指定閱讀器打開,就會顯示一堆亂碼。命令就是剛剛用來查看符號的第一步: gcc -c test.c 然后,我們發現我們的路徑底下多了一個test.o文件。(要是你使用的是coldblocks軟件來寫你的c程序,運行完再相應文件夾都會出現 .o 文件)
注:
形成符號表:
經過這些掃描、語法分析、語義分析、源代碼優化、代碼生成和目標代碼優化,編譯器 忙活了這么多個步驟以后,源代碼終於被編譯成了目標代碼。但是這個目標代碼中有一個問 題是:一些函數符號的地址還沒有確定。如果我們要把目標代碼使用匯編器編譯成真正能夠在機器上執行的指令,那么它們的地址應該從哪兒得到呢?如果它們的定義在跟上面的源代碼同一個編譯單元里面,那么編譯器可以為它們分配空間, 確定它們的地址:那如果是定義在其他的程序模塊呢?
這個看似簡單的問題引出了我們一個很大的話題:冃標代碼中有變量定義在其他模塊, 該怎么辦?事實上,定義其他模塊的全局變量和函數在最終運行時的絕對地址都要在最終鏈接的時候才能確定。所以現代的編譯器可以將一個源代碼文件編譯成一個未鏈接的目標文 件,然后由鏈接器最終將這些目標文件鏈接起來形成可執行文件。讓我們帶着這個問題,走進鏈接的世界。
4.鏈接
鏈接:
程序沒計的模塊化是人們一直在追求的目標,因為當一個系統十分復雜的時候,我們不得不將一個復雜的系統逐步分割成小的系統以達到各個突破的目的。一個復雜的軟件也如 此,人們把毎個源代碼模塊獨立地編譯,然后按照須要將它們“組裝”起來,這個組裝模塊的過程就是鏈接
鏈接的主要內容就是把各個模塊之間相互引用的部分都處理好, 使得各個模塊之間能夠正確地銜接。
gcc下的命令是: gcc test.o ,然后我們會發現,路徑底下多了一個叫a.out的文件,這就相當於我們的exe文件,我們可以 ./a.out 來運行它,看是否是我們想要的結果。
而它又在這個過程干了什么呢?
1.合並段表
我們在上方提到,匯編之后就會生成相應的 .o 文件,而這個 .o 文件又被分為幾個段,在鏈接過程中就會把相同的段通過某種規則合並起來。
2.符號表的合並和定位
將所有文件的符號匯總在一起,在配上相應的地址。
3.重定位
若不同源文件出現了相同的符號,比如說我們在一個源文件里定義一個函數,在另一個源文件里用extern來聲明這個函數,所以在符號表匯總的時候會出現兩個此函數的符號,但應其中一個是非法地址(如extern的那個)所以最終只留下一個此符號。
也就是說,在鏈接過程中,會把目標文件和鏈接庫(庫函數信息)鏈接起來,最后生成可執行程序。
注意:
比如所,我們使用了一個我們並未定義的函數,而編譯器就是在這一步才能看到錯誤。
5.總結
這便是程序的翻譯環境,再來簡練一下:
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函數;也有可能是意外終止。
----參考:《程序員的自我修養——鏈接、裝載與庫》
|------------------------------------------------------------------
到此,對於程序的運行環境的簡易了解便到此結束!
因筆者水平有限,若有錯誤,還望指正!