你写了一个C程序,然后用gcc编译之后得到一个可执行程序。看起来相当简单,是吗?

你有没有想过编译的过程中发生了什么,C程序怎么转变成二进制程序的呢?

其实,源程序最终成为可执行程序经历了如下4个阶段:

1、预处理

2、编译

3、汇编

4、连接

在这篇文章的第一部分,我们讨论一下:c程序源代码被编译成可执行程序过程,gcc编译器经过的步骤。

在深入讨论前,通过一个hello world程序,我们简单的了解一下怎样用gcc编译和运行C程序

$ vi print.c

#include

#define STRING "Hello World"

int main(void)

{

/* 使用宏来打印出 'Hello World'*/

printf(STRING);

return 0;

}

现在,我们用gcc编译器

运行源程序,产生一个可执行程序

$ gcc -Wall print.c -o print在上面的命令中:

*gcc -调用GNU C编译器

*-Wall -gcc的参数,显示出所有警告,-W代表warning,all代表所有

*print.c -输入的源程序

*-o print -指示C编译器产生可执行程序:print,如果不指定 -o,编译器会默认产生一个可执行程序a.out

最后,运行print,它会运行c程序并打印出hello world

$ ./print

Hello World

注意:当你在做一个包含很多C程序的大型项目时,可以用make来实现对编译的管理

现在,我们对如何利用gcc将源程序转换成二进制程序有了基本的了解,我们会重新回顾一下源程序转换成二进制可执行程序的4个阶段。

1、预处理

这个阶段是源代码必须经历的第一个阶段,主要做的工作是:

1.替换宏2.去除注释3.扩展头文件

为了更好的理解预处理,你可以在编译‘print.c’的时候加上 -E参数,这样可以产生一个预处理之后的标准输出

$ gcc -Wall -E print.c

更好的方法是,你可以用标志‘-save-temps’,‘save-temps’标志指示gcc编译器保存在当前目录使用的临时中间文件

$ gcc -Wall -save-temps print.c -o print

因此,当你使用‘save-temps’标志编译‘print.c’后,我们可以在当前目录得到下面的中间文件(还有print可执行文件)

$ ls

print.i

print.s

print.o

经过预处理的输出时以扩展名.i结尾的临时文件(例如本例中的print.i文件)

现在我们打开print.i文件看一下它的内容

$ vi print.i

......

......

......

......

# 846 "/usr/include/stdio.h" 3 4

extern 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 4

extern 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;

}

从上面的输出我们可以看到源文件现在包含了很多信息,但是我们仍然可以看到最后几行我们写的代码,让我们先分析一下这几行代码。

1、首先,我们可以看到printf()的参数直接就是“Hello World"而不是宏。事实上,宏定义和使用完全不见了。这就证明了预处理的第一步就是替换所有的宏。

2、其次,我们看到我们在源文件中的注释已经没有了。这就说明所有的注释被去除掉了。

3、最后,我们看到’#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, @function

main:

.LFB0:

.cfi_startproc

pushq %rbp

.cfi_def_cfa_offset 16

movq %rsp, %rbp

.cfi_offset 6, -16

.cfi_def_cfa_register 6

movl $.LC0, %eax

movq %rax, %rdi

movl $0, %eax

call printf

movl $0, %eax

leave

ret

.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^@^@^@^@^@^@^@^@^@^@^@^

...

...

我们唯一可以解释的是字符串 ELFELF的意思是可执行并可链接的格式这是由gcc产生的可执行的机器级别的目标文件的新格式。在这之前,使用了a.out格式。ELF是比a.out格式更复杂的格式。(我们可以在之后的文章里深入研究一下ELF格式)注意:如果你编译程序时没有指定输出文件名,那么输出的文件默认就是a.out,但它的格式已经变成ELF格式。默认的可执行文件名还是一样。

4、链接

这是最后的阶段,在这个阶段所有程序调用与它们的定义的链接都完成了。就像我们之前讨论的一样,在这个阶段之前gcc不明白像printf()这样的函数定义。当编译器知道所有函数实现的地方,它就简单对每个函数调用用一个占位符。在这个阶段处理printf()的定义,插入printf()函数的真实地址。链接器在这个阶段开始运行并执行它的任务。链接器也会做一些额外的工作,包括程序开始和结束时需要的一些额外的代码。例如:用来设置运行环境的代码,比如为每个程序传递命令行参数,环境变量。同样的,一些标准代码要求给系统返回返回值。我们可以用一个小实验来检验上面编译器的任务。至此,我们已经懂得链接器如何将.o文件(print.o)转变成可执行程序(print)。

所以,如果我们比较一下print.o和print文件的大小,我们就会发现不同的地方

$ size print.o

textdatabssdechexfilename

97009761print.o

$ size print

textdatabssdechexfilename

11815201617176b5print

通过size命令我们大概认识到从目标文件到可执行文件文件大小怎么如何增加的

这都是链接器将外部标准代码与我们的程序连接起来的缘故。

现在,有知道了一个C语言程序在编程可执行程序之前都发生了什么。你知道了

预处理、编译、汇编和链接。链接阶段还有更多内容,我们会在以后的文章中谈到。

原文链接:这里

linux 中输入一个c程序,从c源程序到Linux可执行代码的过程相关推荐

  1. 编写一程序,有2个文本框,在第一个文本框中输入一个整数,当焦点从第一个文本框离开时,第二个文本框将显示这个数的绝对值(使用FocusListener)。

    编写一程序,有2个文本框,在第一个文本框中输入一个整数,当焦点从第一个文本框离开时,第二个文本框将显示这个数的绝对值(使用FocusListener). import javax.swing.*; i ...

  2. Linux上运行一个c程序

    b站的视频链接:Linux虚拟机运行c程序_哔哩哔哩_bilibili希望对大家有所帮助,不对的地方还请多多指教!https://www.bilibili.com/video/BV18Q4y1r7st ...

  3. 在linux中建立一个vim的目录,Linux学习笔记一(目录结构、Vim编辑器、用户管理)...

    1.Linux介绍 linux是一个开源.免费的操做系统,其稳定性.安全性.处理多并发已经获得 业界的承认,目前不少企业级的项目都会部署到Linux/unix系统上. Linux主要的发行版: Ubu ...

  4. 在Linux中运行Nancy应用程序

    最近在研究如何将.NET应用程序移植到非Windows操作系统中运行,逐渐会写一些文章出来.目前还没有太深的研究,所以这些文章大多主要是记录我的一些实验. 这篇文章记录了我如何利用NancyFx编写一 ...

  5. linux 中输入bash,Linux上Bash Shell编程

    Linux下Bash Shell编程 Bash Shell Programming in Linux Linux下Bash Shell编程 Bash what? 进阶的内容是什么? Okay, I g ...

  6. 在浏览器中输入一个域名之后都发生了什么

    当你在浏览器中打入www.baidu.com后,轻轻一敲回车百度输入框就展现在你面前,我们看似很简单很简单的一个操作,背后却有着超级复杂的过程. 其实网络传输跟我们平常说话有许多相似的地方,大脑组织语 ...

  7. 用linux如何用vi编写c程序,linux中VI编写C程序。。。

    在linux中编写C程序时不像编写shell那样开头要#!/bin/bash,但是在C程序中要指定头文件(头文件是指输入输出,宏等,而且要首先声明,也是必须要开始就声明的) 写好C代码后要给C文件赋予 ...

  8. 在 Linux 中把一个网页转换成 PDF的技巧介绍

    你如何在 Linux 中把一个网页转换成 PDF?你可以选择使用每个 Linux 发行版上的网页浏览器(GUI),或者使用终端将网页变成 PDF 文件. 在这里,我将提到这两种方法来帮助你完成工作. ...

  9. c++软件开发面试旋极面试题_经典软件开发面试题:浏览器中输入一个网址后发生了什么?...

    经典软件开发面试题:浏览器中输入一个网址后发生了什么? ​ 大家好, 这一期呢,我们来谈一个经典的面试题.这种题目是在浏览器中输入一个网址以后, 会显示一个网页,这期间到底发生了什么? 答案要求说的越 ...

最新文章

  1. Pytorch多进程最佳实践
  2. 初识canvas,使用canvas做一个百分比加载进度的动画
  3. poj2976 Dropping tests(01分数规划 好题)
  4. Maven公共仓库/镜像站收集及使用技巧
  5. 安装XHProf分析PHP性能瓶颈(原创)
  6. 模拟CMOS集成电路设计学习笔记(一)
  7. oem是代工还是贴牌_OEM与ODM两者之间有什么区别 如何区分代工生产和商标授权...
  8. 《留住好员工》-读后感
  9. HDU 5745 La Vie en rose(简单模拟)
  10. 微信小程序3天刷量开流量主
  11. 数学公式div是什么意思
  12. ccf试题1:数列分段
  13. 每日一算法7--35选7彩票程序
  14. 8.3 有效工作量证明
  15. 六、分享优秀的Armv8 虚拟化技术地址
  16. 【RFC2663 IP 网络地址转换器 (NAT) 术语和注意事项】(翻译)
  17. 《应用程序性能测试的艺术(第2版)》目录—导读
  18. 有关DSP2812与SPI接口DA芯片的通信(AD5640,AD5682)
  19. 【C#】AutoMapper 使用手册
  20. 文件基础处理命令(Linux新手必学)

热门文章

  1. 最近学了个elarning,结尾非要让写问卷,写了一下,发出来共勉
  2. OSPF分解试验部分-LAB3:OSPF各种网络类型试验
  3. mustache模板技术
  4. 将CAD图纸转换出来的图片怎么设置其为高清JPG格式?
  5. ORA-01578和ORA-26040--NOLOGGING操作引起的坏块-错误解释和解决方案(文档ID 1623284.1)...
  6. 项目分享三:页面之间的传值
  7. 车间AP无法接入故障分析处理
  8. 两栏布局,三栏布局,等高布局,流式布局
  9. 关于redis的几件小事(一)redis的使用目的与问题
  10. SSH框架整合-慕课课程