器→工具, 术→技巧, 研发, 编程语言

C语言之Hello World程序编译

钱魏Way · · 1,139 次浏览
!            文章内容如有格式错误,请反馈,谢谢...

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  40043c
  4003d1:       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程序整个执行过程。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注