编译之舞:C/C++ 与 GCC 的协作曲

文章目录

  • 一、C/C++ 编译过程的四个阶段
    • 1. 编译之舞的台前幕后
    • 2. 舞台布景的准备——预处理
    • 3. 舞者的基本训练——编译
    • 4. 编舞师的细节调整——汇编
    • 5. 合奏的和谐统一——链接
  • 二、舞姿的动作细——编译详细模式
  • 三、幕后——GCC 的各种选项(Overall Option)
    • 1. 预处理选项
    • 2. 编译选项
    • 3. 汇编选项
    • 4. 链接选项
    • 5. 其他选项

在现代计算的舞台上,编程语言如同舞者,而编译器则是那幕后默默引导的编舞师,每一次代码的编写都像是一场精心设计的舞蹈。在这个舞台上,C 语言和 GCC(GNU Compiler Collection)是一对经典的搭档。它们共同演绎了一场编译的华美舞蹈,从源代码到可执行文件的华丽蜕变。

本文将探索 C 语言与 GCC 之间的这种美妙协作,并深入了解它们是如何共同创造出那些最终运行在计算机上的程序。

一、C/C++ 编译过程的四个阶段

1. 编译之舞的台前幕后

几乎每个学编程的小伙伴,第一个代码都是输出 Hello World!(如下代码,做了一些小小的改动)。

#include <stdio.h>int main(int agrc, char *argv[])
{if (argc >= 2)printf("Hello %s!\n", argv[1]);elseprintf("Hello World!\n");return 0;
}

将这段代码编译成可执行的文件,也只需要简简单单的一条命令:

gcc hello.c -o hello

使用 gcc 命令,配合上 -o 选项,即可生成 hello 这个可执行文件。然而整个编译过程并没有我们看到的那样简单。

一个或多个 C/C++ 文件要经过预处理(preprocessing)编译(compilation)汇编(assembly)链接(linking) 四个阶段才能变成可执行文件。

在这里插入图片描述

如上图所示,扩展名为 .c 的源代码文件,经过预处理之后可以生成扩展名为 .i 的临时文件。而 .i 文件再经过编译后,可以生成扩展名为 .s 的汇编文件。而扩展名为 .s 的汇编文件再经过汇编之后,可生成扩展名为 .o 的二进制文件。最后,这些二进制文件通过链接,合并成一个可执行文件。

接下来,我们将使用 GCC,将这段代码编译成机器能够理解的指令。

2. 舞台布景的准备——预处理

在 C/C++ 源文件中,以 # 开头的命令被称为预处理命令,如包含命令 #include、宏定义命令 #define、条件编译命令 #if#ifndef 等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个扩展名为 .i 的临时文件中,等待下一步处理。

以前面的 hello.c 为例,可以使用 -E 选项让 GCC 仅进行预处理,并输出结果:

gcc -E hello.c -o hello.i

vim 打开 hello.i 文件,会发现这是一个将 hello.c 的头文件完全展开的文件:

在这里插入图片描述

源代码只有 11 行,被展开后多达 803 行,不过源代码的主题内容还能在 hello.i 的结尾处看到。

3. 舞者的基本训练——编译

我们日常所说的 “编译”,其实涵盖了这个四个阶段,并不是特指这四个阶段中的 “编译”。当然,这里还是要特指一下,这里的 “编译”,就是预处理后的下一步动作。编译阶段就是把扩展名为 .i 的临时文件,“翻译” 成汇编代码,所用到的工具为 cc1(不要怀疑,这个工具的名字就是 cc1。不同架构的芯片有自己的 cc1,x86 架构有自己的 cc1 工具,ARM 架构也有自己的 cc1 工具。)。

一步骤可以通过 -S 选项实现:

gcc -S hello.i -o hello.s

同样,可以通过 vim 来打开 hello.s 文件:

在这里插入图片描述

由上图可见,此时生成的 hello.s 文件是汇编代码,描述了程序的基本操作。这些指令仍然是人类可读的,但距离计算机执行还需进一步的转换。

4. 编舞师的细节调整——汇编

汇编阶段就是将汇编代码翻译成符合一定格式的机器代码,在 Linux 系统上一般表现为 ELF 目标文件(OBJ 文件),用到的工具为 as

使用 -c 选项,GCC 会将汇编代码转换为目标代码(机器码):

gcc -c hello.s -o hello.o

此时,再用 vim 打开 hello.o 文件,就是一堆完全开不懂得东西了:

在这里插入图片描述

这里我们可以用另一个命令行工具来打开 hello.o 文件,那就是用于显示文件的内容为十六进制(hexadecimal)形式的 hexdump。输入如下命令:

hexdump -C hello.o

如下图所示,第一行就是文件的格式。整个文件包含了程序的机器指令,但尚未完成最终的链接。

在这里插入图片描述

5. 合奏的和谐统一——链接

链接阶段是编译过程的最后一步,就是将 OBJ 文件和系统库的 OBJ 文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件,用到的工具为 ldcollect2。输入如下命令,生成最终的可执行文件:

gcc hello.o -o hello

此时,一个完整的可执行文件 hello 就生成了。当然,这个可执行文件,与 gcc hello.c -o hello 生成的可执行文件,没有任何区别。

在这里插入图片描述

二、舞姿的动作细——编译详细模式

在 GCC 编译过程中,使用 -v 选项可以启用详细模式,显示编译的每个步骤和所调用的各个工具的详细信息。这对于调试和理解编译过程非常有用。具体命令如下:

gcc hello.c -o hello -v

执行这个命令会产出一堆信息,不过,我们也可以从中找到一些关键的信息,来验证我们前面提到的一些内容。

在这里插入图片描述

在这段信息最前面的就是编译器配置信息,我们把其中的主要信息摘出来:

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v ...... # 此处省略不重要的内容
Thread model: posix
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04) 

具体说明如下:

  • Using built-in specs.:表示使用内置的编译器规格。
  • COLLECT_GCC=gcc:表明主编译器是 gcc
  • Target: x86_64-linux-gnu:目标架构是 64 位的 x86 架构,运行在 GNU/Linux 系统上。
  • Configured with:显示了编译器的配置选项。
  • Thread model: posix:使用 POSIX 线程模型。
  • gcc version 7.5.0:编译器版本为 7.5.0。

接下来按前面所提的四个步骤,应该先进行预处理,不过从输出的信息可以看出,GCC 会把预处理和编译两个阶段一起做了。其中也用到了 cc1 工具:

在这里插入图片描述

提取出关键的信息(如下),这里就是用 cc1 工具,将 hello.c 文件编译成临时的 /tmp/ccEwwHut.s,这是个汇编文件。

COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/7/cc1 hello.c -o /tmp/ccEwwHut.s # 去掉了很多参数

[!NOTE]

启用 cc1 工具时使用了很多选项,这些选项对初学者来说,理解起来还是有难度的。不过,为了方便已经入行的小伙伴,这里给出一些选项的介绍,方便大家学习:

  • /usr/lib/gcc/x86_64-linux-gnu/7/cc1:调用了 cc1,这是 GCC 的前端,负责预处理、词法分析和语法分析。
  • -quiet:表示减少输出信息。
  • -imultiarch x86_64-linux-gnu:指定了目标平台。
  • -dumpbase hello.c:指定源文件名。
  • -mtune=generic:优化针对通用架构。
  • -march=x86-64:目标架构为 x86-64。
  • -fstack-protector-strong:启用强堆栈保护。
  • -Wformat:开启格式字符串警告。
  • -Wformat-security:开启格式字符串安全检查。

这还多了一步前面没提到,就是搜索路径,信息如下:

#include "..." search starts here:
#include <...> search starts here:/usr/lib/gcc/x86_64-linux-gnu/7/include/usr/local/include/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed/usr/include/x86_64-linux-gnu/usr/include
End of search list.

其实这个步骤只是为后面的链接阶段做准备,提前找出加载的库。

到了汇编阶段,可以看到是调用 as 汇编器进行汇编,其中的 -v 选项就是详细模式,--64 表示生成 64 位的目标代码,最后生成指定输出对象文件 /tmp/cc8dsQ4D.o,这也是个临时文件。

在这里插入图片描述

最后到了链接阶段,从下图中可以看出,调用 collect2 链接器。链接器后面的参数中,除了一些相关的库之外,最关键的 /tmp/cc8dsQ4D.o 也包含在其中。从中也可以看到有 -o hello 的选项和参数,也就是最终会生成 hello 的可执行文件。

在这里插入图片描述

[!NOTE]

同样有很多选项比较难,稍微解释一下作为提升内容:

  • -plugin:使用 LTO 插件进行链接时优化。
  • -plugin-opt:传递给插件的选项。
  • -m elf_x86_64:指定 ELF 格式为 64 位。
  • -dynamic-linker /lib64/ld-linux-x86-64.so.2:指定动态链接器。
  • -pie:生成位置无关的可执行文件。
  • -z now:使某些符号立即可用。
  • -z relro:创建只读重定位段。
  • -lgcc, -lgcc_s, -lc:链接必要的库。

三、幕后——GCC 的各种选项(Overall Option)

GCC (GNU Compiler Collection) 提供了许多选项来控制编译器的行为。这些选项可以大致分为几个类别,包括预处理选项、编译选项、汇编选项和链接选项等。下面对相对重要的选项进行解释:

1. 预处理选项

  • -E:只进行预处理阶段,然后停止。输出是经过预处理的源代码。
  • -P:不输出行控制信息(例如 #line 指令)。
  • -C:保留所有注释。
  • -M:输出依赖性列表。
  • -MM:输出依赖性列表,并忽略标准头文件。

2. 编译选项

  • -c:只编译并汇编,但不链接。生成一个目标文件。
  • -S:只编译,生成汇编代码。
  • -E:只预处理,不编译。
  • -g:生成调试信息。
  • -O:设置优化等级。-O0 表示无优化,-O1-O3 分别代表不同的优化级别,-O3 是最高级别的优化。
  • -Os:优化以减小代码尺寸。
  • -Og:优化同时保持调试信息的可用性。
  • -Wall:打开所有警告。
  • -Wextra:打开额外的警告。
  • -Werror:将所有警告视为错误。
  • -pedantic:启用所有 ISO C 和 ISO C++ 标准所禁止的语言扩展。
  • -pedantic-errors:如同 -pedantic 但是将扩展视为错误。
  • -std=standard:指定要遵循的标准(如 -std=c99-std=c++11)。
  • -fPIC:生成位置无关代码(Position Independent Code),用于共享库。

3. 汇编选项

  • -Wa,option:将 option 传递给汇编器。
  • -masm=att:选择 AT&T 汇编风格。
  • -masm=intel:选择 Intel 汇编风格。

4. 链接选项

  • -Ldir:添加目录 dir 到链接器的搜索路径。
  • -lfoo:链接名为 libfoo 的库。
  • -static:产生静态链接的可执行文件。
  • -shared:生成共享库。
  • -pie:生成位置无关的可执行文件。
  • -fPIC:与 -pie 类似,用于生成位置无关代码,通常用于共享库。
  • -Wl,option:传递选项给链接器。
  • -T:指定链接器脚本。
  • -nostartfiles:不使用任何启动文件。
  • -nostdlib:不使用标准库。

5. 其他选项

  • -v:显示编译器版本信息和编译过程中的详细信息。
  • -V:显示编译器版本。
  • -Bprefix:指定前缀路径 prefix 来查找编译器相关的工具。
  • -print-file-name=filename:打印指定文件的完整路径。
  • -print-prog-name=program:打印指定程序的完整路径。
  • -print-libgcc-file-name:打印 libgcc 的路径。
  • -dumpversion:打印版本号。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/385321.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity UGUI 之 事件接口

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 本文在发布时间选用unity 2022.3.8稳定版本&#xff0c;请注意分别 1.什么是事件接口&…

使用两台虚拟机分别部署前端和后端项目

使用两台虚拟机分别部署前端和后端项目 1 部署方案2 准备两台虚拟机&#xff0c;并配置网络环境3 部署后端项目3.1 打包服务3.2 上传jar包到服务器3.3 集成Systemd3.3.1 移动端服务集成Systemd3.3.2 后台管理系统集成Systemd 4 配置域名映射5 部署前端项目5.1 移动端5.1.1 打包…

享元模式(结构型)

目录 一、前言 二、享元模式 三、总结 一、前言 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;用于减少大量细粒度对象的内存占用。它通过共享尽可能多的相同数据来节约内存空间。 享元模式由以下角色组成&#xff1a; Flyweight&…

推荐系统三十六式学习笔记:工程篇.常见架构25|Netflix个性化推荐架构

目录 架构的重要性经典架构1.数据流2.在线层3.离线层4.近线层 简化架构总结 你是否曾经觉得算法就是推荐系统的全部&#xff0c;即便不是全部&#xff0c;至少也是嫡长子&#xff0c;然而实际上&#xff0c;工程实现才是推荐系统的骨架。如果没有好的软件实现&#xff0c;算法不…

达梦数据库激活

SSH登录 192.168.0.148 账号&#xff1a;root 密码&#xff1a;xxx 1.上传 dm.key 文件到安装目录 /bin 目录下 cd /home/dmdba/dmdbms/bin rz -E dm.key2.修改 dm.key 文件权限 chown -R dmdba.dinstall dm.key3.打开数据库工具&#xff0c;新建查询&#xff0c;输入 cd /…

亚信安慧AntDB-M负载均衡

负载均衡是分布式系统中常用的技术&#xff0c;主要是将工作任务均衡分布到系统的各个资源点上&#xff0c;可以充分利用系统资源。 AntDB-M分布式内存数据库节点角色可以分为管理节点(MN)、计算节点(CN)和数据节点(DN)三种。管理节点收到客户端连接请求后&#xff0c;会经由负…

视觉巡线小车(STM32+OpenMV)——总结

文章目录 目录 文章目录 前言 一、效果展示 二、完整流程 1、STM32CubeMX配置 2、Keil编辑 3、硬件接线 4、参数调试 5、图像处理调试 三、总结 前言 基于前面的系列文章&#xff0c;已基本介绍完了基于STM32OpenMV的视觉巡线小车&#xff0c;本文将以小编自己的小车…

Visual Studio Code + vue快速安装配置Node.js+Vue+webpack+vscode

第一部分&#xff1a;Node.js 第一步&#xff1a;下载Node.js 方法1&#xff1a;链接 下载 | Node.js 中文网 (nodejs.cn) 方法2&#xff1a;百度网盘 链接&#xff1a;https://pan.baidu.com/s/1zIqu8H9rb_I1i-1OWD7swQ?pwdaurk 提取码&#xff1a;aurk --来自百度网盘…

【React 】开发环境搭建详细指南

文章目录 一、准备工作1. 安装 Node.js 和 npm2. 选择代码编辑器 二、创建 React 项目1. 使用 Create React App2. 手动配置 React 项目 三、集成开发工具1. ESLint 和 Prettier2. 使用 Git 进行版本控制 在现代前端开发中&#xff0c;React 是一个非常流行的框架&#xff0c;用…

【日常记录】【插件】Typed.js:用于创建打字效果的 JavaScript 库

文章目录 1. 引言2. 安装3. 基本使用参考链接 1. 引言 Typed.js是一个用于创建打字效果的 JavaScript 库。这个效果就是 chatgpt、百度的文心一言等其他的大模型&#xff0c;回复用户的问题的时候的效果 typed-js 官网typed 案例 2. 安装 CDN方式 这俩都可以&#xff0c;还有其…

在 Windows 上安装 PostgreSQL

官网下载地址&#xff1a; https://www.enterprisedb.com/downloads/postgres-postgresql-downloadsWindows平台 官网直接提供exe安装包&#xff0c;没有手动安装的压缩包 postgresql-14.4-1-windows-x64.exe几个重要的安装选项 安装界面会指定服务程序和库两个路径&#xf…

【JavaScript】深入理解 `let`、`var` 和 `const`

文章目录 一、var 的声明与特点二、let 的声明与特点三、const 的声明与特点四、let、var 和 const 的对比五、实战示例六、最佳实践 在 JavaScript 中&#xff0c;变量声明是编程的基础&#xff0c;而 let、var 和 const 是三种常用的变量声明方式。本文将详细介绍这三种变量声…

Centos7_Minimal安装Cannot find a valid baseurl for repo: base/7/x86_6

问题 运行yum报此问题 就是没网 解决方法 修改网络信息配置文件&#xff0c;打开配置文件&#xff0c;输入命令&#xff1a; vi /etc/sysconfig/network-scripts/ifcfg-网卡名字把ONBOOTno&#xff0c;改为ONBOOTyes 重启网卡 /etc/init.d/network restart 网路通了

pycharm+pytorch+gpu开发环境搭建

一、安装anacoda 1、下载Anaconda安装包 官网下载地址 https://www.anaconda.com/distribution/ 清华镜像 Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 下载python3.8对应的版本Anaconda3-2021.04-Windows-x86_64.exe 下载完成…

PCB设计应该注意的问题

1.PCB布局与走线 论文 常见的PCB布局要点 1.放置滤波电容时遵循的的原则为&#xff1a; 放置的位置以靠近器件的引脚为最佳&#xff0c;电容的大小以从大到小以次靠近所要滤波的器件连接处&#xff0c;如一般使用的电容为10uF、1uF、0.1uF、0.01uF等&#xff0c;10倍的差额&a…

实验2-1-4 输出菱形图案

#include<stdio.h> int main(){printf(" A \n");printf("A A\n");printf(" A \n");}

C++《类和对象》(中)

一、 类的默认成员函数介绍二、构造函数 构造函数名与类同名内置类型与自定义类型析构函数拷贝构造函数 C《类和对象》(中) 一、 类的默认成员函数介绍 默认成员函数就是⽤⼾没有显式实现&#xff0c;编译器会⾃动⽣成的成员函数称为默认成员函数。 那么我们主要学习的是1&…

等级保护 总结2

网络安全等级保护解决方案的主打产品&#xff1a; HiSec Insight安全态势感知系统、 FireHunter6000沙箱、 SecoManager安全控制器、 HiSecEngine USG系列防火墙和HiSecEngine AntiDDoS防御系统。 华为HiSec Insight安全态势感知系统是基于商用大数据平台FusionInsight的A…

概率论--矩估计

目录 简介 矩估计法的基本步骤 延伸 矩估计法在大样本情况下的准确性和有效性如何评估&#xff1f; 在实际应用中&#xff0c;矩估计法的局限性有哪些具体例子&#xff1f; 如何处理矩估计法在某些情况下可能出现的不合理解或无法唯一确定参数的问题&#xff1f; …

日常开发记录分享——C#控件ToolTip实现分栏显示内容

文章目录 需求来源实现思路实施请看VCR等等别走&#xff0c;有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息&#xff0c;一开始使用的tooltip实现&#xff0c;但是里面的内容效果并不理想&#xff0c;需要有条理性&#xff0c;于是就想到能不能将展示的东西分列…