C/C++程序是怎么运行的?

一个C/C++程序运行经历的过程:预处理、编译、汇编、链接、执行
C/C++程序是怎么运行的?

  1. 预处理:将预处理指令(可以简单理解为#开头的正确指令)转换为实际代码中的内容(展开头文件、宏替换、删注释、条件编译)。生成后缀为“.i”的文件。   
  2. 编译(和优化):将预处理后的文件转换成汇编语言。生成后缀为“.s”的文件   
  3. 汇编:由汇编生成的文件(汇编代码)翻译为二进制目标文件(机器码)。生成后缀为“.o”的文件   
  4. 链接:多个目标文件(二进制)结合库函数等综合成的能直接独立执行的执行文件。生成后缀为“.out”的文件

gcc无法进行库文件的链接,即无法编译完成步骤4;而g++则能完整编译出可执行文件。(实质上,g++从步骤1-步骤3均是调用gcc完成,步骤4连接则由自己完成)

【注】其中源程序、修改了的源程序和汇编程序都是文本文件,而可重定位目标程序和可执行目标程序都是二进制文件。

其中编译阶段所有代码都是单独编译的,只是在最终的链接阶段所有的目标文件。

预处理(Pre-Processing).c 👉 .i 一个C程序→另外一个C程序

首先我们准备一个简单的 Hello World 程序,命名为 main.c

#include<stdio.h>
#define info "Hello, worldn"

int main()
{
    // A simple program.
    printf(info);
    return 0;
}

预处理阶段做的事情就是 预处理器(cpp)根据以字符#开头的代码修改 原始的C程序。

  • 比如#include <stdio.h>,将头文件stdio.h中的内容 加到 程序文本中。
  • 比如#define info "Hello, worldn",会将 宏定义 的info 替换 成字符串“Hello, worldn”(当然我这里只是为了举例,一般我们不这么写)
  • 此外,会将 注释 比如// A simple program. 删除

预处理是直接对源文件进行处理(不关注语法规则),然后得到另一个C程序,通常以 .i 作为文件扩展名。

使用gcc -E main.c -o main.i命令得到预处理后的C程序main.i

gcc -E main.c -o main.i

我们可以在Linux系统(我这里用的是Ubuntu)下直接使用gcc -E main.c -o main.i命令得到预处理后的C程序main.i。
我截取了一部分(总共有800+行),可以很明显的看到:

  • 引入了头文件
  • info被替换了
  • 注释没了
  • 还添加了一些特殊的标记,告诉编译器每行的来源,以便它可以使用它们来产生合理的错误消息。

编译(Compilation).i 👉 .s 另一个C程序→汇编语言

编译阶段做的事情就是编译器(cc1)将C程序main.i翻译成汇编语言程序main.s

  • 检查C程序的语法错误
  • 将文件翻译成中间代码,即汇编语言
  • 可选地优化翻译后的中间代码,获得更好的性能

我们可以使用gcc -S main.i -o main.s得到翻译后的汇编程序main.s,截取部分如下。

gcc -S main.i -o main.s

.file"main.c".text.LC0:.string"Hello, world".text.globl mainmain:.LFB0:.cfi_startproc pushq%rbp movq%rsp,%rbp.......LFE0:.size main,.-main.ident"GCC: (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0".section.note.GNU-stack,"",@progbits

其中的语句比如pushq %rbp描述了一条低级机器语言指令。汇编语言是有用的,它为不同高级语言的不同汇编器提供了通用的输出语言,C汇编器和Fortran汇编器产生的输出文件都是一样的汇编语言。

汇编和优化(Assembling).s👉.o 汇编语言→机器语言指令

汇编阶段做的事情就是汇编器(as)

  • main.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,
  • 并将结果保存在目标文件main.o(Windows下为xx.obj而Linux下为xx.o)中。

main.o是一个二进制文件,如果我们使用文本编辑器打开main.o,会看到一堆乱码。

我们可以使用gcc -c main.s -o main.o得到可重定位目标程序main.o,如下所示。

gcc -c main.s -o main.o

LF>?@@UH䈍=距ello,worldGCC:(Ubuntu7.3.0-27ubuntu1~18.04)7.3.0zRx#main.cmain_GLOBAL_OFFSET_TABLE_puts?ÿÿ C ??ÿÿ.symtab.strtab.shstrtab.rela.text.data.bss.rodata.comment.note.GNU-stack.rela.eh_frame @0&90d+BWR@@

不出所料的一堆乱码?。

链接(Linking).o + ... + .o 👉可执行程序

我们的main.c程序中使用了 printf函数,printf函数是每个C编译器都提供的标准C库中的一个函数,它存在于一个名为printf.o的单独预编译好了的目标文件中

链接阶段做的事情就是链接器(ld)

  • 将需要用到的目标文件比如main.o和printf.o进行合并
  • 并生成一个可执行(目标)文件,可以被加载到内存中,由系统执行。

实际上我们直接执行gcc main.c -o main命令得到的就是可执行文件,Windows下为.exe,Linux下默认为具有可执行权限的a.out,当然我们使用了-o来自定义输出文件名。

gcc main.c -o main

C/C++程序是怎么运行的?

执行(executing)

./main

参考:
https://maxwell-lx.vip/posts/c++gccg++编译详解/

原文链接: https://www.cnblogs.com/ddqdd/p/17067886.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    C/C++程序是怎么运行的?

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/314069

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月16日 下午1:09
下一篇 2023年2月16日 下午1:09

相关推荐