• 从C语言编译看高级程序语言执行


    从C语言编译看高级程序语言执行

    1. C语言编译过程

    编译过程流程图:

    1.1. 预处理文本(Preprocessing)

    解析源码文件文件中的宏指令,将源码转换为更详细的源码,对于文件main.c:

    #include<main.h>
    
    int main(){
    	return 0 ;
    }
    
    

    定义main.h:

    int add(int a, int b);
    

    进行预处理:

    gcc -E -I . main.c
    

    参数-E含义:

    -E Only run the preprocessor

    参数-I含义:

    -I

    Add directory to include search path

    输出结果内容:

    # 1 "main.c"
    # 1 "<built-in>" 1
    # 1 "<built-in>" 3
    # 361 "<built-in>" 3
    # 1 "<command line>" 1
    # 1 "<built-in>" 2
    # 1 "main.c" 2
    # 1 "./main.h" 1
    int add(int a, int b);
    # 2 "main.c" 2
    
    int main(){
     return 0 ;
    }
    

    预处理后的内容把#include<main.h>替换成了main.h中的代码。

    1.2. 编译成汇编(Assemble)

    将预处理后的文件编译成汇编文件:

    gcc -I . -S main.c
    

    生成文件main.s:

    	.section	__TEXT,__text,regular,pure_instructions
    	.macosx_version_min 10, 13
    	.globl	_main                   ## -- Begin function main
    	.p2align	4, 0x90
    _main:                                  ## @main
    	.cfi_startproc
    ## %bb.0:
    	pushq	%rbp
    	.cfi_def_cfa_offset 16
    	.cfi_offset %rbp, -16
    	movq	%rsp, %rbp
    	.cfi_def_cfa_register %rbp
    	xorl	%eax, %eax
    	movl	$0, -4(%rbp)
    	popq	%rbp
    	retq
    	.cfi_endproc
                                            ## -- End function
    
    .subsections_via_symbols
    
    

    参数 -S:

    -S Only run preprocess and compilation steps

    1.3. 生成目标文件

    目标文件也是机器码,但是没有运行库,不能执行:

    gcc -c main.s -o main.o
    

    文件内容main.o:

    ����� 8p�p__text__TEXT��__compact_unwind__LD �@__eh_frame__TEXT0@
                                                                     h$
    HX
     PUH��1��E�]�zRx
    _main          �$��������A�C
    

    因为main.s是汇编文件,也可以使用汇编的编译方法:

    as main.s -o main.o
    

    1.4. 链接运行库,生成二进制

    给目标文件链接上运行库和OS信息,变成可以执行文件:

    gcc main.o main
    

    可执行文件因为有了运行库,所以就比main.o文件大多了。

    注意main文件不仅仅有机器指令,还有操作系统的信息,否则windows/linux的应用程序可以兼容了。

    1.5. gcc 编译过程产生的文件

    • *.c, *.h源码文件

    • *.i 预处理后的文件

    • *.s 汇编文件

    • *.o 机器码文件,可能是链接前的目标文件,也可能是链接后的可执行文件

    文件之间的关系:

    上述案例中产生的文件大小对比:

    ❯ ls -l
    total 56
    -rwxr-xr-x  1 wuhf  staff  4248  6 13 10:48 main  # main.o和运行库链接生成的可执行文件,比main.o大
    -rw-r--r--@ 1 wuhf  staff    45  6 13 01:11 main.c
    -rw-r--r--  1 wuhf  staff    23  6 13 01:11 main.h 
    -rw-r--r--  1 wuhf  staff   211  6 13 10:55 main.i  # 比 main.c + main.h 还大,是拼接到一起的
    -rw-r--r--  1 wuhf  staff   608  6 13 10:47 main.o # 由 main.s 编译生成的目标文件
    -rw-r--r--  1 wuhf  staff   485  6 13 10:37 main.s # 由main.i 生成的汇编文件
    

    1.6. gcc 编译器干了什么

    程序必须变成机器码才能被CPU执行,不管这个程序是OS还是应用。
    gcc的最终目标是将txt的源码文件变成可以被硬件CPU识别的机器指令。
    但是gcc并没有直接把txt变成机器指令,而是翻译成了汇编指令,汇编指令又转换为可以被机器识别的指令。
    所以gcc 最核心的功能就是把txt的源码翻译汇编指令

    1.7. gcc 参数

    -S Only run preprocess and compilation steps

    -E Only run the preprocessor

    -I

    Add directory to include search path

    2. 验证上述分析的可靠性

    建立一个hello.c内容:

    #include<stdio.h>
    
    int main(){
    	printf("Hello World!");
    }
    

    分步编译:

    ❯ gcc -S hello.c
    ❯ as hello.s -o hello.o
    ❯ gcc hello.o -o hello
    ❯ ./hello
    Hello World!
    

    直接将c文件编译成可执行程序:

    ❯ gcc hello.c -o hello
    ❯ ./hello
    Hello World!
    

    3. C/Java/Python 程序执行方式对比

    3.1. C

    gcc 把c文件翻译成汇编指令,再把汇编指令编译成机器指令执行,C文件变成了机器可执行的文件。

    3.2. Java

    java编译器的主要工作是把java源码文件转换为符合jvm规范的class文件;

    jvm的主要工作是执行class文件,把class文件中的指令翻译成不同操作系统的函数调用。

    java与C之间明显的区别是java程序的可执行程序就是java.exe,换句话说java的源码没有(不代表不能)被编译成机器指令。

    问:Java 为什么不直接编译成机器指令?

    答: 编译成机器指令会携带操作系统的信息,导致编译出来的程序不能跨平台使用。

    问:class能否变成机器指令?

    答: 如果class文件能变成机器指令,那么java既可以使用class来实现跨平台,一次编译到处运行,也可以实现更好的性能。
    但是这样做的弊端是执行class前需要再进行一次编译,也是耗时的。
    jvm本身就是C/C++程序,某种程度讲JAVA就是C的一个高级API,所以性能不比C落后太多,所以每次运行前编译得不偿失。
    Java有JIT技术可以在运行时把把特定代码编译成机器指令。

    3.3. Python

    python是脚本语言,所以它不需要编译器,执行python的是它的解释器python.exe,所以python的可执行程序是python.exe,
    它把python中的代码翻译成系统函数调用执行,从某种程度上讲python源码是python.exe(C/C++程序)一个复杂配置文件

    3.4. 一个编程语言包括什么?

    • 对于脚本语言:解释器
      • 有解释器就可以执行脚本了
    • 对于不能编译成可执行程序的语言:编译器+运行时
      • 编译器把源码编译成中间文件
      • 运行时(Runtime)执行中间文件
    • 对于可以编译成可执行程序的语言: 编译器
      • 操作系统就是运行时

    4. 一些反思

    上学时候应该是大一的时候就学习C语言程序设计,学完之后对C程序如何编译,如何执行却没有具体的认识。
    整体的印象是在VC 6.0中写好程序点一下运行代码就跑起来了,造成一个错觉:使用C就必须有VC6.0,C语言的执行就是写完代码再点击那个神奇的按钮,编译的作用是体现不出来的。后来学习Java就没有这种感觉,因为老师教学时用了一个文本编辑器+javac命令编译。
    然后我们就知道了写java代码有javac和java这俩命令就够了,所以等到后面工作在Linux上运维还是ide开发都还是熟悉的配方,熟悉的命令。
    但是C语言就不一样的了,只会用VC6.0,界面又丑,windows又升级还不好安装,基本上不想折腾了。
    然后得出一个结论:受限环境有益,当工具不齐全时候更能深刻地认识问题, 就像我们老师说的那样,刚入门学习一个语言不要上来就用ide,会错过很多细节。

  • 相关阅读:
    Codeforces 67A【模拟】
    Codeforces325 D【并查集维护连通性】
    CodeForces 363D 【二分+贪心】
    Lightoj1084【DP啊DP】
    lightoj1062【几何(二分)】
    lightoj1066【BFS】
    lightoj1064 【DP求方案】
    lightoj1063【求割点】
    lightoj 1074【spfa判负环】
    CodeForces 382C【模拟】
  • 原文地址:https://www.cnblogs.com/oaks/p/13113683.html
Copyright © 2020-2023  润新知