1. 编译和链接----你真的了解HelloWord吗

2024-04-15 03:04

本文主要是介绍1. 编译和链接----你真的了解HelloWord吗,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 平时的应用程序开发,很少需要关注编译和链接的过程,因为通常的开发环境都是流行的集成开发环境(IDE),比如VS等,这些IDE通常将编译和链接的过程一步完成,我们通常将这种合并到一起的过程称为构建。其实即使用命令行来编译一个源代码文件,简单的一句gcc hello.c 命令就包含了非常复杂的过程。
  • 虽然这些默认配置、编译和链接参数对于大部分的应用程序开发已经足够了,但是在开发过程中,我们会被这种便捷所迷惑,软件的运行机制和机理被掩盖了,一些程序莫名其妙的错误我们无法解决,性能瓶颈也束手无策。只能看到现象,看不到本质,所以要深入了解这些机制。

1.编译器到底隐藏了哪些东西

// hello.c
#include <stdio.h>int main()
{printf("Hello World\n");return 0;
}

在Linux下,当我们使用GCC来编译该程序时,只需要最简单的命令

gcc hello.c
./a.out

在这里插入图片描述

事实上,上述过程可以分解为4个步骤:预处理、编译、汇编、链接

在这里插入图片描述

1.1 预编译

  • 首先是源代码文件hello.c 和相关的头文件,如stdio.h 等被预编译器cpp 预编译成一个.i 文件。对于c++ 来说,它的源文件扩展名可能是.cpp 或者.cxx,头文件的扩展名可能是.hpp ,而预编译后的文件扩展名是.ii

  • 第一步的预编译过程相当于如下命令:其中-E 表示只进行预编译

    gcc -E hello.c -o hello.i

在这里插入图片描述

或者

cpp hello.c > hello.i

  • 预编译过程主要处理源文件中以# 开始的预编译指令,比如#include, #define 等。规则主要如下:
    • 将所有的#define 删除,并且展开所有的宏定义
    • 处理所有的条件编译指令,比如#if, #ifdef, #elif, #else, #endif 条件编译
    • 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置,注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
    • 删除所有的注释//. /* */
    • 添加行号和文件名标识,比如 #2 "hello.c" 2 ,以便编译时编译器产生调试用的行号的信息,及用于编译时产生编译错误或警告时能够显示行号。
    • 保留所有的#progma编译器指令,因为编译器需要使用他们

在这里插入图片描述

在这里插入图片描述

可以看到简单的几行代码,预编译之后变成了733行!!!

行号也互相匹配

在这里插入图片描述

经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中,所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题

1.2 编译

编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。这个过程是整个程序构建的核心部分,也是最复杂的部分。

  • gcc -S hello.i -o hello.s 或者 gcc -S hello.c -o hello.s

在这里插入图片描述

  • 现在版本的GCC把预编译和编译两个步骤合并成一个步骤,使用一个叫做ccl 的程序来完成这两个步骤。/usr/lib/gcc/x86_64-linux-gnu/9/cc1 ,因此也可以直接调用ccl 来完成

    /usr/lib/gcc/x86_64-linux-gnu/9/cc1 hello.c

对于C语言来说,这个预编译和编译的程序是cc1 ,对于C++来说,有对应的程序叫做cc1plus ;Objective-C是cc1objfortranf771 ;Java是jc1

所以实际上gcc命令只是这些后台程序的包装,它会根据不同的参数要求去调用预编译程序cc1, 汇编器as,链接器ld

在这里插入图片描述

1.3 汇编

汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,没有复杂的语法,也没有语义,也不需要指令优化,按照对照表一一翻译就行。

汇编过程可以调用汇编器as 来完成

as hello.s -o hello.o

或者

gcc -c hello.s -o hello.o

或者直接使用gcc命令,从C源代码文件开始,经过预编译、编译和汇编直接输出目标文件

gcc -c hello.c -o hello.o

在这里插入图片描述

在这里插入图片描述

2.1.4 链接

  • 为什么汇编器不直接输出可执行文件而是输出一个目标文件呢?
  • 链接过程到底包含了什么内容?
  • 为什么要链接?

下面我们来看看怎样调用ld 才可以产生一个能够正常运行的HelloWorld 程序

ld -static /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginT.o-L/usr/lib/gcc/x86_64-linux-gnu/9  -L/usr/lib  -L/lib hello.o--start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-linux-gnu/9crtend.o/usr/lib/crtn.o

如果把所有的路径都省略掉,那么就是

ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc -end-group crtend.o crtn.o

也就是说,我们需要将一大堆文件链接起来才可以看到a.out,也就是最终的可执行文件。

  • 那么crt1.o, crti.o .... 这些文件是什么?
  • 做什么用?
  • -lgcc -lgcc_eh -lc 这些是什么参数?为什么使用?
  • 为什么要将他们和hello.o 链接起来才能得到可执行文件?

这些需要后续的章节一点点看了,未完待续。。。

这篇关于1. 编译和链接----你真的了解HelloWord吗的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/904724

相关文章

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

idea maven编译报错Java heap space的解决方法

《ideamaven编译报错Javaheapspace的解决方法》这篇文章主要为大家详细介绍了ideamaven编译报错Javaheapspace的相关解决方法,文中的示例代码讲解详细,感兴趣的... 目录1.增加 Maven 编译的堆内存2. 增加 IntelliJ IDEA 的堆内存3. 优化 Mave

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

一文带你了解SpringBoot中启动参数的各种用法

《一文带你了解SpringBoot中启动参数的各种用法》在使用SpringBoot开发应用时,我们通常需要根据不同的环境或特定需求调整启动参数,那么,SpringBoot提供了哪些方式来配置这些启动参... 目录一、启动参数的常见传递方式二、通过命令行参数传递启动参数三、使用 application.pro

一文带你深入了解Python中的GeneratorExit异常处理

《一文带你深入了解Python中的GeneratorExit异常处理》GeneratorExit是Python内置的异常,当生成器或协程被强制关闭时,Python解释器会向其发送这个异常,下面我们来看... 目录GeneratorExit:协程世界的死亡通知书什么是GeneratorExit实际中的问题案例

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定