编写一个C程序,使用gcc进行编译,然后得到一个可执行程序。相当简单,对吗?
你有没有问过自己,编译处理期间到底发生了什么事情,还有C程序如何转换成可执行程序呢?
为了最后变成可执行程序,源代码要经过的行程主要有四个阶段。
C源程序变成可执行程序的四个阶段如下:
预处理编译汇编链接
本文第一部分将讨论gcc编译器在把C程序的源代码编译成可执行程序时所经历的步骤。
在进一步讨论之前,让我们先用hello world这个简单的例子,迅速地看一下如何使用gcc编译和运行C代码。
$ vi print.c
#include
#define STRING "Hello World"
int main(void)
{
/* Using a macro to print 'HelloWorld'*/
printf(STRING);
return 0;
}
现在,让我们运行gcc编译器编译这个源代码以建立可执行程序。
$ gcc -Wall print.c -o print
在上述命令中:
gcc – 调用GNU C编译器-Wall – gcc打开所有警告提示的标记。-W表示警告,我们还把“all”传给-W。print.c – 输入给gcc的C程序-o print – 指示C编译器创建名为print的可执行程序。如果没有指定-o,C编译器将默认地创建名为a.out的可执行程序
最后,执行print,这个命令将执行C程序并显示hello world。
$ ./printHello World
注意:当你致力于编写包含几个C程序的大项目时,请按我们早先讨论的一样,用make实用程序来操控C程序的编译。
现在,我们对如何使用gcc把源代码转换成二进制代码已经有了基本的概念,我们将重新考虑把C源程序变成可执行程序必须经历的四个阶段。
1. 预处理
这正是源代码必经的第一阶段。这个阶段要完成以下任务:
宏替换去掉注释包含文件的扩充
为了更好地理解预处理,可用标记-E编译上面的程序print.c,这将把预处理后的结果显示在标准输出上。
$ gcc -Wall -E print.c
如下所示,使用标记-save-temps可能会更好。标记-save-temps指示编译器把被gcc使用的临时中间文件存储在当前目录中。
$ gcc -Wall -save-temps print.c -o print
所以,当我们用-save-temps标记来编译程序print.c时,就可以在当前目录中(除可执行文件print以外,还可以)得到以下中间文件:
$ lsprint.iprint.sprint.o
预处理的结果存储在扩展名.i的临时文件(本例即print.i)中。
$ vi print.i........................# 846 "/usr/include/stdio.h" 3 4extern FILE *popen (__const char *__command, __const char *__modes) ;extern int pclose (FILE *__stream);extern char *ctermid (char *__s) __attribute__ ((__nothrow__)); # 886 "/usr/include/stdio.h" 3 4extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__));extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__)) ;extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__)); # 916 "/usr/include/stdio.h" 3 4# 2 "print.c" 2 int main(void){printf("Hello World");return 0;}
在以上输出结果中,可见源代码现在充满许许多多信息,但在结尾部分仍然能够看到我们编写的几行代码。让我们先分析这些代码行。
首先观察到的是printf()的参数现在直接包含字符串“Hello World”而不是宏。事实上,宏的定义和宏的使用已经完全消失。这证实,预处理阶段的第一个任务就是展开所有的宏。其次看到的是,我们在原来的代码中所写的注释也不在了。这表明去掉了所有的注释。第三,找不到#include这一行了,取而代之的是完整的大量代码。所以,可以放心地断定,头文件stdio.h已经展开,并且逐字逐句地包含在我们的源代码中。因此,我们知道,编译器就能够看见printf()函数的声明。
我在搜索print.i文件时发现,函数printf声明为:
extern int printf (__const char *__restrict __format, ...);
关键字extern表明,函数printf()不是在这个文件中定义的,它是外部函数。稍后就会明白,gcc如何获得printf()的定义。
可用gdb调试C程序。既然对预处理阶段会发生什么有了相当好的理解,那么,让我们进入下一阶段吧。
2. 编译
编译器完成预处理阶段的工作之后,下一步就是把print.i作为输入,进行编译,然后产生编译过的中间输出结果。这一阶段的输出文件是print.s。print.s中包含的是汇编级指令。
用编辑器打开文件print.s并查看文件内容。
$ vi print.s.file "print.c".section .rodata.LC0:.string "Hello World".text.globl main.type main, @functionmain:.LFB0:.cfi_startprocpushq %rbp.cfi_def_cfa_offset 16movq %rsp, %rbp.cfi_offset 6, -16.cfi_def_cfa_register 6movl $.LC0, %eaxmovq %rax, %rdimovl $0, %eaxcall printfmovl $0, %eaxleaveret.cfi_endproc.LFE0:.size main, .-main.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3".section .note.GNU-stack,"",@progbits
虽然我不想在汇编级程序设计方面卷得太深,但迅速查看后可以断定,这一汇编输出结果采用一些汇编程序能够理解的指令格式并把它转换成机器语言。
3. 汇编
在这一阶段,文件print.s被当作输入并产生中间文件print.o。这个文件也被认为是目标文件。
这个文件由汇编程序产生,汇编程序理解并把带有汇编指令的.s文件转换成包含机器指令的目标文件(.o)。这一阶段只把现有的代码转换成机器语言,而像printf()这样的函数调用暂不解析。
由于这个阶段的输出是机器级文件(print.o),所以不能查看其内容。如果你还是试图要打开并查看print.o,那么,你将看到的是一些完全不可读的东西。
$ vi print.o^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@0^^@UH<89>å¸^@^@^@^@H<89>ǸHello World^@^@GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3^@^T^@^@^@^@^@^@^@^AzR^@^Ax^P^A^[^L^G^H<90>^A^@^@^\^@^@]^@^@^@^@A^N^PC<86>^B^M^F^@^@^@^@^@^@^@^@.symtab^@.strtab^@.shstrtab^@.rela.text^@.data^@.bss^@.rodata^@.comment^@.note.GNU-stack^@.rela.eh_frame^@^@^@^@^@^@^@^@^@^@^@^......…
通过查看文件print.o,唯一能够解释的是有关字符串ELF。
ELF表示可执行并且可链接的格式。
相对而言,这是由gcc产生的机器级目标文件和可执行文件的新格式。在此之前,使用的是称之为a,out的格式。据说,ELF的格式比a,out的复杂得多。
注意:如果没有指定输出文件名来编译代码,虽然所产生的输出文件的名字是a.out,但现在已经改为ELF格式。仅仅是缺省的可执行文件名字依旧一样。
4. 链接
这是最后的阶段,完成对所有函数调用及其定义的链接。正如早先讨论的一样,直到这时,gcc还不了解像printf()一样的库函数的定义。在编译器确切知道所有这些函数在什么地方实现之前,函数调用只是简单地采用占位符。正是在这一阶段,print()的定义得到解析,并且插入printf()函数的实际地址。
链接器在这个阶段加入行动并完成这项任务。
链接器也做一些额外的工作;它把一些程序开始运行和程序结束运行所需的附加代码合并到程序。例如,设置运行环境的标准代码,像传递命令行参数、环境变量给每个程序等。类似地,把程序的返回值返回给系统也需要一些标准代码。
编译器的上述任务可以通过小实验加以证实。从现在开始,我们就知道链接器把.o文件(print.o)转换成可执行文件(print)。
因此,要是比较print.o和print这两个文件的大小,就会发现差别。
$ size print.o text data bss dec hex filename 97 0 0 97 61 print.o $ size print text data bss dec hex filename 1181 520 16 1717 6b5 print
通过size命令,我们获得一个粗略的概念,从目标文件到可执行文件,输出文件的大小增加了。这全都是因为链接器把额外的标准代码和我们的程序合并在一起。
现在,你知道C源程序在变成可执行程序之前发生的事情,了解到预处理、编译、汇编和链接等阶段。链接阶段还有更多事情需要了解,我们将在这一系列的下一篇文章报道。
相关推荐
【NLP】使用 PyTorch 从头构建自己的大型语言模型 (LLM)
如果我们在 n 维空间中绘制一个向量,看起来相似的物体(如狗和猫)将彼此靠近,而看起来不相似的物体(如学校和家庭)的嵌入向量将位于更远的地方。为此,我们将使用一种流行的标记器,称为 BPE 标记器,这是一种子词标记器,正在 GPT3 等模型中使用。:前馈网络使用深度神经网络来学习两个线性层(第一层有 d_model 节点,第二层有 d_ff 节点,根据注意力论文分配值)中嵌入向量的所有特征,并且将 ReLU 激活函数应用于第一线性层的输出,为嵌入值提供非线性,并应用 dropout 以进一步避免过度拟合。
从c源程序到Linux可执行代码的过程
你写了一个C程序,然后用gcc编译之后得到一个可执行程序。看起来相当简单,是吗? 你有没有想过编译的过程中发生了什么,C程序怎么转变成二进制程序的呢? 源程序最终成为可执行程序经历了如下4个阶段: 1、预处理 2、编译 3、汇编 4、连接 在这篇文章的第一部分,我们讨论一下:c程序源代码被编译成可执行程序过程,gcc编译器经过的步骤。 在深入讨论前,通过一个hell
从零配置到批量处理:FaceFusion 3.1完整使用指南(含hififace_256模型测试)
本文详细介绍了FaceFusion 3.1的完整使用指南,包括环境配置、模型选择(特别是hififace_256模型的深度测试)以及批量处理技巧。通过AI驱动的脸部替换技术,帮助用户高效实现高质量的面部处理,适用于影视制作、广告设计等多种场景。
C语言从最基础实现printf
C语言从最基础实现printfprintf最常处理的一些数据类型printf原函数输入输出 众所周知,printf是c语言中非常重要也非常常用的函数了,那下面我们就尝试自己实现一下printf printf最常处理的一些数据类型 符号 意义 %d 十进制有符号整数 %u 十进制无符号整数 %f 浮点数 %s 字符串 %c 单个字符 %p 指针的地址 %e 指数形式的浮点数 %x, %X 无符号以十六进制表示的整数 %o 无符号以八进制表示的整数 %p 输出
使用CAPL实现DoIP刷写
本文介绍了使用CAPL实现基于DoIP协议的ECU刷写方法,包含完整源码。
基于RK3562J开发板的MCP2518FD CAN-FD控制器驱动适配与测试指南
本文详细介绍了在RK3562J开发板上适配MCP2518FD CAN-FD控制器驱动的完整流程。内容涵盖内核驱动配置、设备树修改、编译烧录以及实际通信测试,重点分享了调试方法,旨在帮助开发者快速为嵌入式系统扩展高速CAN-FD通信能力,适用于汽车电子、工业控制等场景。
(实训第一天)Linux下编译运行C源程序的过程(图解)
1.首先进去Linux系统终端 2.输入gedit 源文件名.c ;最好在最后再加上一个参数&,这样可以让源文件编辑窗口在后台运行,这样就可以继续使用终端窗口。 3.编辑源程序,最后保存。
MIPI CSI-2 协议解析
CSI-2标准协议前言一、Overview of CSI-2二、CSI-2 layer Definitions三、 Camera Control Interface(CCI)3.1 数据传输协议(Data Transfer Protocol)3.1.1 消息类型(Message Type)3.1.2 读写操作(Read/Write Operations)3.1.2.1 随机位置单次读3.1.2.2 当前位置单次读3.1.2.3 随机位置连续读3.1.2.4 当前位置连续读3.1.2.5 随机位置单次写3.1
C语言源程序怎么到可执行程序
源程序(源代码)是指按照一定的程序设计语言规范编写的未经过编译的文本文件,是具有可读性的计算机语言指令。可以比作自己手写的程序代码。 可执行程序(Executable Program,EXE file)是可以在操作系统存储空间中浮动定位的二进制可执行程序。一般软件安装后会生成可执行文件,可以直接加载到内存中,并又操作系统加载并执行。 以 C 语言为例,自己编写的源代码文件扩展名为 c,这个 c...
别再手动盘点了!教你给微信小程序仓库系统加个‘扫码枪’:完整扫码入库与查询功能实现
本文详细介绍了如何为微信小程序仓库系统添加扫码入库与查询功能,实现零硬件成本的智能仓储管理。通过优化摄像头参数、增强动态识别算法和深度整合业务逻辑,提升扫码精度和效率,同时提供混合查询方案应对复杂场景。这套方案显著提升入库效率,降低错误率,适用于各类仓储管理需求。
Testbench编写:激励生成、响应监控、仿真时间控制
本文介绍了Verilog/VHDL Testbench的核心技术,重点阐述了激励生成、响应监控和仿真时间控制三大关键环节。通过时钟生成、复位信号生成等具体代码示例,展示了如何有效构建测试平台。文章还总结了波形输出、自动检查等验证方法,并提出了模块化设计、自动化检查等最佳实践,帮助开发者提升FPGA仿真效率和验证质量。这些技术要点为数字电路设计的正确性验证提供了系统化的解决方案。
【Linux】程序翻译的四个阶段及编译器gcc/g++的验证
为了在系统上运行一个程序,每条C语句都必须被其他程序转化为一系列的低级机器语言指令,然后这些指令按照一种为可执行目标程序的格式打好包,并以二进制磁盘文件的形式存放起来。目标程序也称为可执行目标文件。在Linux操作系统上,从源文件到目标文件的转化是由编译器驱动程序(gcc/g++)完成的。gcc在Linux下负责编译C语言,g++主要负责编译C++。上面这个指令,gcc编译器驱动程序读取源程序文件hello.c,并把它翻译成一个可执行目标文件hello。
模型可解释性分析:基于SHAP的深度探索
SHAP模型可解释性分析摘要 本文系统性地介绍了SHAP(SHapley Additive exPlanations)方法在机器学习模型可解释性分析中的应用。SHAP基于博弈论的Shapley值理论,为特征重要性评估提供了统一框架。文章详细阐述了四种核心算法:Kernel SHAP(模型无关方法)、Tree SHAP(针对树模型的高效算法)、Deep SHAP(适用于深度学习)和Linear SHAP(线性模型专用)。通过Python实现展示了从数据准备到SHAP分析的全流程,包括摘要图、依赖图等可视化技术
描述c语言的编译执行,C语言文件的编译与执行的四个阶段并分别描述
答案:采纳了加我不懂问我一 C编译过程概述目前Linux下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序.GCC不仅功能非常强大,结构也异常灵活.最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3...
IMSL CNL 7.0.0 (x86-64)Crack
IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件
LinePhone3.2.7(android端最新版本) sip客户端
SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/
Linux平台下源程序到可执行程序的详解
我们知道一个源程序到可执行程序,一般要经过预处理,编译,汇编、链接最后生成可执行程序,并且在前面的博客中有了这些步骤分别对应编译器会做什么工作,在Linux平台下我们对这些步骤中,分别对应来测试编译器在不同阶段生成的文件的内容,是否与我们所了解到的一致。 1、预处理: 预处理功能主要包括宏定义,文件包含,条件编译,去注释等。 预处理指令是以#号开头的代码行。 实例: gcc –E test.c –o mytest.i 选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程。 选项“-o”是指目标
VC中sqlite的使用
在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示
在Linux系统中运行C语言程序
正式学习c语言的第一天 2022/1/11 在之前的学习中已经在Windows系统中用Microsoft VC++上实现了C语言程序的运行,现在将在Linux系统上运行C语言程序。 首先明确C语言程序开发的4个步骤:编辑、编译、链接、运行 在Microsoft VC++中编译和运行都可以直接用鼠标或按键快捷运行,Linux系统中则需要手动在终端中输入指令。 1.编辑 打开Linux终端,输入#vi test.c(#vim test.c或者#touch test.c也可以)创建一个后缀为.c的源..
复分析 阿尔福斯(Ahlfors) 第三版答案 英文
答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案
将源程序变为可执行程序的过程
将源程序变为可执行程序的过程
Linux-C C语言编译过程
Linux-C C语言编译过程 一、简述 GCC(GNU Compiler Collection,即 GNU 编译器套装),是一套由 GNU 开发的编程 语言编译器。简单介绍使用gcc编译器将hello.c文件编译成为hello可执行文件的过程。 在伪终端下输入如下命令 Liang@ubuntu:~$ gcc hello.c -o hello (这个命令包括...
从源程序到可执行程序
程序的开发与执行涉及计算机的各个不同层面。我们在编写好程序后通常是被称为用户程序(user program)或应用程序我们以最常使用,也是很经典的C源程序为例,逐步刨析程序的执行过程,源程序如何成功再计算机上运行,中间到底有多少细节。为了更好的逐步看清执行过程,我们将在Linux系统环境下进行C程序的预处理、编译、汇编、连接,最终将生成可执行目标文件。GCC编译驱动程序对程序进行处理。
【Linux】编译的四个步骤
编译做了什么?
gcc编译程序四个阶段 预处理、编译、汇编、链接
我们在windows下的时候大部分通过vs等编译器来编写我们的代码,这种编译器有一个统称的名字叫做IDE(Integrated Development Environment)中文名字是集成开发环境,为什么叫它集成开发环境,是因为你只需要安装一个vs你就可以做到对代码的编辑、编译、调试等等,每次我们在vs环境下编写完代码之后拿一下f5程序自动开始执行,我们就能得到程序的执行结果,实际上在你编写...
程序执行的四个阶段
链接阶段:请注意,hello程序调用了printf函数,它是每个C编译器都提供的标准C库中的一个函数。printf函数存在于一个名为printf.o的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的he11o.o程序中。汇编阶段:接下来,汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序(relocatable object program)的格式,并将结果保存在目标文件hello.o中。结果就得到了另一个C程序,通常是以。
linux 中输入一个c程序,从c源程序到Linux可执行代码的过程
你写了一个C程序,然后用gcc编译之后得到一个可执行程序。看起来相当简单,是吗?你有没有想过编译的过程中发生了什么,C程序怎么转变成二进制程序的呢?其实,源程序最终成为可执行程序经历了如下4个阶段:1、预处理2、编译3、汇编4、连接在这篇文章的第一部分,我们讨论一下:c程序源代码被编译成可执行程序过程,gcc编译器经过的步骤。在深入讨论前,通过一个hello world程序,我们简单的了解一下怎样...
【Linux】gcc-程序的翻译四个阶段(图示)
编译器是如何将源代码一步步翻译成可执行程序的(Linux环境)
深扒源程序到执行程序的过程
主要讲从代码到可执行文件经历的翻译环境,代码是如何从语言到机器指令,又是如何链接在一起变成可执行文件
linux下C程序的编译链接
linux下C程序的编译可以分为四个步骤,分别是预处理,编译,汇编和链接。 1.预编译: 首先预处理完成预替换、去注释、头文件按照路径展开、以及条件编译。 建立一个test.c文件。里面写入注释、宏定义、头文件、条件编译。 如下图: 使用的gcc命令是:gcc -E test.c -o test.i gcc -E的作用是将.c文件转化成.i文件 使用vi命令
1952



被折叠的 条评论
为什么被折叠?



