C语言变成可执行文件的四大步骤

嵌入式技术

1368人已加入

描述

从C语言变成最终的可执行文件,需要经过四步:

预处理;

编译;

汇编;

链接。

下面就以Linux环境为例,来分析下四个步骤。
  预处理
  写一段简单的代码:

#include 


#define OK     0


int main()
{
    printf("hellowrld
");


    return OK; 
}
其中,井号键开头的代码有两行:包含头文件和宏定义。 预处理命令:
root@Turbo:t# gcc -E test.c -o test.i
root@Turbo:t# ls
test  test.c  test.i
root@Turbo:t#
预处理后的test.i代码:
# 1 "test.c"
# 1 ""
# 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/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4 
# 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4 
# 1 "/usr/include/features.h" 1 3 4 
# 461 "/usr/include/features.h" 3 4 
# 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4 
# 452 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4 
# 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4 
# 453 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4 
# 1 "/usr/include/x86_64-linux-gnu/bits/long-double.h" 1 3 4 
# 454 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4 
# 462 "/usr/include/features.h" 2 3 4
...............
# 5 "test.c"
int main()
{
 printf("hellowrld
");


 return 0;
}
代码量从原来的10行变成了七百多行,主要的变化有两个:

 

头文件没了;

return OK 变成了 return 0。

  所以基本上可以得出预处理的作用: 处理所有以井号键开头的代码,包括头文件、宏定义、条件编译等等。

  头文件展开。以stdio.h为例,编译器会去默认的目录下(一般是/usr/include)找到这个文件,然后把里面的内容复制一份,粘贴到C文件中。这就是为什么预处理后的文件变成了七百多行。

  宏定义替换。预处理的时候如果遇到了宏定义,直接把宏替换掉,比如代码中的OK就变成了数字0。

  条件编译。下面的代码就属于条件编译:
 

#ifndef _STDIO_H
#define _STDIO_H


#endif
条件编译会在预处理的时候做出判断,满足条件的代码留下来,不满足的去掉。
  编译
  编译的命令:
root@Turbo:t# gcc -S test.i -o test.s
root@Turbo:t# ls
test  test.c  test.i  test.s
root@Turbo:t#
查看编译后的文件test.s内容:
    .file   "test.c"
    .text
    .section    .rodata
.LC0:
    .string "hellowrld"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16 
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    .LC0(%rip), %rdi
    call    puts@PLT
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
    .section    .note.GNU-stack,"",@progbits
    .section    .note.gnu.property,"a"
    .align 8
    .long    1f - 0f
    .long    4f - 1f
    .long    5
0:
    .string  "GNU"
1:
    .align 8
    .long    0xc0000002
    .long    3f - 2f
2:
    .long    0x3
3:
    .align 8
4:
如果你搞过单片机的话,一定能看出来这是汇编代码。

 

编译的作用:

语法检查;

把C代码翻译成汇编代码。

  汇编 汇编的命令:

root@Turbo:t# gcc -c test.s -o test.o
root@Turbo:t# ls
test  test.c  test.i  test.o  test.s
查看汇编后test.o的内容:
 

 

Linux

很显然, 这是一个二进制文件。


  既然是二进制文件,那能不能执行呢?答案是不行,原因也很简单,因为此时代码还不知道printf函数在哪。


  汇编的作用:


  把汇编代码翻译成二进制代码。


  链接

  链接的命令:
 

root@Turbo:t# gcc test.o -o test
root@Turbo:t# ls
test  test.c  test.i  test.o  test.s
root@Turbo:t# ./test 
hellowrld
如果工程里面有多个C文件,就会产生多个.o文件,链接的时候,把所有.o文件加上就行。

  链接的作用:
 
把所有需要的源文件合并到一起;

链接代码中需要用到的库(比如printf,需要链接C库)。

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分