C语言基本上大学都教过,但是很多人应该和我一样学习的时候还使用的是Windows平台,对于其中要用到的编译等知识都不了解。今天就针对这种情况来重新学习一遍C语言。
#include <stdio.h> main() { printf("hello world\n"); }
以上代码应该是最简单的C语言程序了,将上面的内容报错为hello.c,并通过gcc去编译它,具体使用到的指令为:
$ gcc -g -Wall hello.c -o hello
该命令将文件‘hello.c’中的代码编译为机器码并存储在可执行文件‘hello’中。机器码的文件名是通过-o选项指定的。该选项通常作为命令行中的最后一个参数。如果被省略,输出文件默认为‘a.out’。如果当前目录中与可执行文件重名的文件已经存在,它将被覆盖。
选项-Wall开启编译器几乎所有常用的警告。编译器有很多其他的警告选项,但-Wall是最常用的。默认情况下GCC不会产生任何警告信息。当编写C或C++程序时编译器警告非常有助于检测程序存在的问题。注意如果有用到math.h库等非gcc默认调用的标准库,请使用-lm参数。
选项”-g”表示在生成的目标文件中带调试信息,调试信息可以在程序异常中止产生core后,帮助分析错误产生的源头,包括产生错误的文件名和行号等非常多有用的信息。
执行万上述指令后我们发现有报错,具体内容为:
hello.c:2:1:警告:返回类型默认为‘int’[-Wreturn-type] hello.c:在函数‘main’中: hello.c:5:1:警告:在有返回值的函数中,控制流程到达函数尾[-Wreturn-type]
解决上诉问题的方法非常的简单,只要将代码修改为:
#include <stdio.h> int main() { printf("hello world\n"); return 0; }
不管采用上诉的哪种代码,在命令行直接执行编译出来的hello文件,即可看到输出的hello world。下面就来细讲整个编译的过程。
上图是一个hello的c程序由gcc编译器从源码文件hello.c中读取内容并将其翻译成为一个可执行的对象文件hello的过程。这个过程包含了几个阶段:
预处理过程
预处理(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中的第一行的#include <stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,插入到程序文本中。结果就得到里另一个C程序,通常是以*.i作为文件扩展名。
可通过执行如下指令获得:gcc -E hello.c -o hello.i
用记事本打开,可查看到如下的内容:
#1 "hello.c" #1 "" #1 "<命令行>" #1 "hello.c" #1 "/usr/include/stdio.h" 1 3 4 #28 "/usr/include/stdio.h" 3 4 #1 "/usr/include/features.h" 1 3 4 #324 "/usr/include/features.h" 3 4 #1 "/usr/include/x86_64-linux-gnu/bits/predefs.h" 1 3 4 #325 "/usr/include/features.h" 2 3 4 #357 "/usr/include/features.h" 3 4 #1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4 #378 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4 #1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4 #379 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4 #358 "/usr/include/features.h" 2 3 4 #389 "/usr/include/features.h" 3 4 #1 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 1 3 4 #1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4 #5 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4 #1 "/usr/include/x86_64-linux-gnu/gnu/stubs-64.h" 1 3 4 #10 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4 #390 "/usr/include/features.h" 2 3 4 #29 "/usr/include/stdio.h" 2 3 4typedef unsigned int _G_uint32_t __attribute__((__mode__(__SI__))); #33 "/usr/include/libio.h" 2 3 4 #53 "/usr/include/libio.h" 3 4 #1 "/usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h" 1 3 4 #40 "/usr/lib/gcc/x86_64-linux-gnu/4.6/include/stdarg.h" 3 4 typedef __builtin_va_list __gnuc_va_list; #54 "/usr/include/libio.h" 2 3 4 #172 "/usr/include/libio.h" 3 4 struct _IO_jump_t; struct _IO_FILE; #182 "/usr/include/libio.h" 3 4 typedef void _IO_lock_t;#228 "/usr/include/stdio.h" 34 extern char *tempnam(__const char *__dir, __const char *__pfx) __attribute__((__nothrow__, __leaf__)) __attribute__((__malloc__)); extern int fclose(FILE *__stream); extern int fflush(FILE *__stream); #253 "/usr/include/stdio.h" 34 extern int fflush_unlocked(FILE *__stream); #267 "/usr/include/stdio.h" 34 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); #296 "/usr/include/stdio.h" 34 #307 "/usr/include/stdio.h" 34 extern FILE *fdopen(int __fd, __const char *__modes) __attribute__((__nothrow__, __leaf__)); #320 "/usr/include/stdio.h" 34 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 vsscanf(__const char *__restrict__ s, __const char *__restrict__ format, __gnuc_va_list __arg) __asm__("""__isoc99_vsscanf") __attribute__((__nothrow__, __leaf__)) __attribute__((__format__(__scanf__, 2, 0))); #528 "/usr/include/stdio.h" 3 4编译器将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文件格式确切地描述了一条低级机器语言指令。 可通过如下指令获得: gcc -S hello.c -o hello.s
用记事本打开,可查看到如下的内容:.file "hello.c" .section .rodata .LC0: .string "hello world" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $.LC0, %edi call puts movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" .section .note.GNU-stack, "", @progbits汇编阶段
汇编器将 hello.s 翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并把结果保存在 hello.o 中,hello.o 是一个二进制文件,它的字节编码是机器语言指令,而不是字符。
可通过如下指令获得:
gcc -c hello.c
,最终生成的 hello.o 文件需要使用 objdump 打开,具体指令为:objdump -d hello.o
获取的内容为:
hello.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000: 0: 55 push %rbp 1: 48 89 e5 mov %rsp, %rbp 4: bf 00 00 00 00 mov $0x0, %edi 9: e8 00 00 00 00 callq e e: b8 00 00 00 00 mov $0x0, %eax 13: 5d pop %rbp 14: c3 retq 链接阶段
hello.c 程序中调用了 printf 函数,而 printf 函数存在于一个名为 printf.o 的单独的预编译好的目标文件中,而这个文件必须以某种方式合并到我们的 hello.o 程序中。连接器就负责处理这种合并,结果就得到 hello 文件,它是一个可执行文件,可以被加载到内存中由系统执行。
使用的指令为:
gcc hello.o -o hello
,最终生成了一个 hello 文件,同样此 hello 文件可用通过 objdump 打开:objdump -d hello
打开后的内容为:
hello: file format elf64-x86-64 Disassembly of section .init: 00000000004003c8<_init>: 4003c8: 48 83 ec 08 sub $0x8,%rsp 4003cc: e8 6b 00 00 00 callq 40043c4003d1: e8 fa 00 00 00 callq 4004d0 4003d6: e8 d5 01 00 00 callq 4005b0<__do_global_ctors_aux> 4003db: 48 83 c4 08 add $0x8,%rsp 4003df: c3 retq Disassembly of section .plt: 00000000004003e0 : 4003e0: ff 35 0a 0c 20 00 pushq 0x200c0a(%rip) # 600ff0<_GLOBAL_OFFSET_TABLE_+0x8> 4003e6: ff 25 0c 0c 20 00 jmpq *0x200c0c(%rip) # 600ff8<_GLOBAL_OFFSET_TABLE_+0x10> 4003ec: 0f 1f 40 00 nopl 0x0(%rax) 00000000004003f0 : 4003f0: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601000<_GLOBAL_OFFSET_TABLE_+0x18> 4003f6: 68 00 00 00 00 pushq $0x0 4003fb: e9 e0 ff ff ff jmpq 4003e0<_init+0x18> 0000000000400400<__libc_start_main@plt>: 400400: ff 25 02 0c 20 00 jmpq *0x200c02(%rip) # 601008<_GLOBAL_OFFSET_TABLE_+0x20> 400406: 68 01 00 00 00 pushq $0x1 40040b: e9 d0 ff ff ff jmpq 4003e0<_init+0x18> Disassembly of section .text: 0000000000400410<_start>: 400410: 31 ed xor %ebp,%ebp 400412: 49 89 d1 mov %rdx,%r9 400415: 5e pop %rsi 400416: 48 89 e2 mov %rsp,%rdx 400419: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 40041d: 50 push %rax 40041e: 54 push %rsp 40041f: 49 c7 c0 a0 05 40 00 mov $0x4005a0,%r8 400426: 48 c7 c1 10 05 40 00 mov $0x400510,%rcx 40042d: 48 c7 c7 f4 04 40 00 mov $0x4004f4,%rdi 400434: e8 c7 ff ff ff callq 400400<__libc_start_main@plt> 400439: f4 hlt 40043a: 90 nop 40043b: 90 nop 000000000040043c : 40043c: 48 83 ec 08 sub $0x8,%rsp 400440: 48 8b 05 99 0b 20 00 mov 0x200b99(%rip),%rax # 600fe0<_DYNAMIC+0x190> 400447: 48 85 c0 test %rax,%rax 40044a: 74 02 je 40044e 40044c: ff d0 callq *%rax 40044e: 48 83 c4 08 add $0x8,%rsp 400452: c3 retq 400453: 90 nop 400454: 90 nop 400455: 90 nop 400456: 90 nop 400457: 90 nop 400458: 90 nop 400459: 90 nop 40045a: 90 nop 40045b: 90 nop 40045c: 90 nop 40045d: 90 nop 40045e: 90 nop 40045f: 90 nop 0000000000400460<__do_global_dtors_aux>: 400460: 55 push %rbp 400461: 48 89 e5 mov %rsp,%rbp 400464: 53 push %rbx 400465: 48 83 ec 08 sub $0x8,%rsp 400469: 80 3d b0 0b 20 00 00 cmpb $0x0,0x200bb0(%rip) # 601020<__bss_start> 400470: 75 4b jne 4004bd<__do_global_dtors_aux+0x5d> 400472: bb 40 0e 60 00 mov $0x600e40,%ebx 400477: 48 8b 05 aa 0b 20 00 mov 0x200baa(%rip),%rax # 601028 40047e: 48 81 eb 38 0e 60 00 sub $0x600e38,%rbx 400485: 48 c1 fb 03 sar $0x3,%rbx 400489: 48 83 eb 01 sub $0x1,%rbx 40048d: 48 39 d8 cmp %rbx,%rax 400490: 73 24 jae 4004b6<__do_global_dtors_aux+0x56> 400492: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 400498: 48 83 c0 01 add $0x1,%rax 40049c: 48 89 05 85 0b 20 00 mov %rax,0x200b85(%rip) # 601028 4004a3: ff 14 c5 38 0e 60 00 callq *0x600e38(,%rax,8) 4004aa: 48 8b 05 77 0b 20 00 mov 0x200b77(%rip),%rax # 601028 4004b1: 48 39 d8 cmp %rbx,%rax 4004b4: 72 e2 jb 400498<__do_global_dtors_aux+0x38> 4004b6: c6 05 63 0b 20 00 01 movb $0x1,0x200b63(%rip) # 601020<__bss_start> 4004bd: 48 83 c4 08 add $0x8,%rsp 4004c1: 5b pop %rbx 4004c2: 5d pop %rbp 4004c3: c3 retq 4004c4: 66 66 66 2e 0f 1f 84 data32 data32 nopw %cs:0x0(%rax,%rax,1) 4004cb: 00 00 00 00 00 00000000004004d0 : 4004d0: 48 83 3d 70 09 20 00 cmpq $0x0,0x200970(%rip) # 600e48<__JCR_END__> 4004d7: 00 4004d8: 55 push %rbp 4004d9: 48 89 e5 mov %rsp,%rbp 4004dc: 74 12 je 4004f0 4004de: b8 00 00 00 00 mov $0x0,%eax 4004e3: 48 85 c0 test %rax,%rax 4004e6: 74 08 je 4004f0 4004e8: 5d pop %rbp 4004e9: bf 48 0e 60 00 mov $0x600e48,%edi 4004ee: ff e0 jmpq *%rax 4004f0: 5d pop %rbp 4004f1: c3 retq 4004f2: 90 nop 4004f3: 90 nop 00000000004004f4 : 4004f4: 55 push %rbp 4004f5: 48 89 e5 mov %rsp,%rbp 4004f8: bf fc 05 40 00 mov $0x4005fc,%edi 4004fd: e8 ee fe ff ff callq 4003f0 400502: b8 00 00 00 00 mov $0x0,%eax 400507: 5d pop %rbp 400508: c3 retq 400509: 90 nop 40050a: 90 nop 40050b: 90 nop 40050c: 90 nop 40050d: 90 nop 40050e: 90 nop 40050f: 90 nop 0000000000400510<__libc_csu_init>: 400510: 48 89 6c 24 d8 mov %rbp,-0x28(%rsp) 400515: 4c 89 64 24 e0 mov %r12,-0x20(%rsp)40051a: 488d 2d 03 09 20 00 lea 0x200903(%rip), %rbp # 600e24<__init_array_end> 400521: 4c 8d 25 fc 08 20 00 lea 0x2008fc(%rip), %r12 # 600e24<__init_array_end> 400528: 4c 89 6c 24 e8 mov %r13, -0x18(%rsp) 40052d: 4c 89 74 24 f0 mov %r14, -0x10(%rsp) 400532: 4c 89 7c 24 f8 mov %r15, -0x8(%rsp) 400537: 48 89 5c 24 d0 mov %rbx, -0x30(%rsp) 40053c: 48 83 ec 38 sub $0x38, %rsp 400540: 4c 29 e5 sub %r12, %rbp 400543: 41 89 fd mov %edi, %r13d 400546: 49 89 f6 mov %rsi, %r14 400549: 48 c1 fd 03 sar $0x3, %rbp 40054d: 49 89 d7 mov %rdx, %r15 400550: e8 73 fe ff ff callq 4003c8<_init> 400555: 48 85 ed test %rbp, %rbp 400558: 74 1c je 400576<__libc_csu_init+0x66> 40055a: 31 db xor %ebx, %ebx 40055c: 0f 1f 40 00 nopl 0x0(%rax) 400560: 4c 89 fa mov %r15, %rdx 400563: 4c 89 f6 mov %r14, %rsi 400566: 44 89 ef mov %r13d, %edi 400569: 41 ff 14 dc callq *(%r12,%rbx,8) 40056d: 48 83 c3 01 add $0x1, %rbx 400571: 48 39 eb cmp %rbp, %rbx 400574: 75 ea jne 400560<__libc_csu_init+0x50> 400576: 48 8b 5c 24 08 mov 0x8(%rsp), %rbx 40057b: 48 8b 6c 24 10 mov 0x10(%rsp), %rbp 400580: 4c 8b 64 24 18 mov 0x18(%rsp), %r12 400585: 4c 8b 6c 24 20 mov 0x20(%rsp), %r13 40058a: 4c 8b 74 24 28 mov 0x28(%rsp), %r14 40058f: 4c 8b 7c 24 30 mov 0x30(%rsp), %r15 400594: 48 83 c4 38 add $0x38, %rsp 400598: c3 retq 400599: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 00000000004005a0<__libc_csu_fini>: 4005a0: f3 c3 repz retq 4005a2: 90 nop 4005a3: 90 nop 4005a4: 90 nop 4005a5: 90 nop 4005a6: 90 nop 4005a7: 90 nop 4005a8: 90 nop 4005a9: 90 nop 4005aa: 90 nop 4005ab: 90 nop 4005ac: 90 nop 4005ad: 90 nop 4005ae: 90 nop 4005af: 90 nop 00000000004005b0<__do_global_ctors_aux>: 4005b0: 55 push %rbp 4005b1: 48 89 e5 mov %rsp, %rbp 4005b4: 53 push %rbx 4005b5: 48 83 ec 08 sub $0x8, %rsp 4005b9: 48 8b 05 68 08 20 00 mov 0x200868(%rip), %rax # 600e28<__CTOR_LIST__> 4005c0: 48 83 f8 ff cmp $0xffffffffffffffff, %rax 4005c4: 74 19 je 4005df<__do_global_ctors_aux+0x2f> 4005c6: bb 28 0e 60 00 mov $0x600e28, %ebx 4005cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 4005d0: 48 83 eb 08 sub $0x8, %rbx 4005d4: ff d0 callq *%rax 4005d6: 48 8b 03 mov (%rbx), %rax 4005d9: 48 83 f8 ff cmp $0xffffffffffffffff, %rax 4005dd: 75 f1 jne 4005d0<__do_global_ctors_aux+0x20> 4005df: 48 83 c4 08 add $0x8, %rsp 4005e3: 5b pop %rbx 4005e4: 5d pop %rbp 4005e5: c3 retq 4005e6: 90 nop 4005e7: 90 nop Disassembly of section .fini: 00000000004005e8<_fini>: 4005e8: 48 83 ec 08 sub $0x8, %rsp 4005ec: e8 6f fe ff ff callq 400460<__do_global_dtors_aux> 4005f1: 48 83 c4 08 add $0x8, %rsp 4005f5: c3 retq 经过上面四个过程,我们就可以把一个源代码文件编译成机器能运行的可执行文件。这个可执行文件刚开始是保存在磁盘上,当计算机要运行这个程序的时候,hello就被加载到内存中,接着程序指令被不断复制到寄存器中由CPU来执行,最后把"hello world"从寄存器中打到显示设备上。这就是hello程序整个执行过程。