【程序员的自我修养01】编译流程概述
绪论
大家好,欢迎来到【程序员的自我修养】专栏。正如其专栏名,本专栏主要分享学习《程序员的自我修养——链接、装载与库》的知识点以及结合自己的工作经验以及思考。编译原理相关知识本身就比较有难度,我会尽自己最大的努力,争取深入浅出。若你希望与一群志同道合的朋友一起学习,也希望加入到我们的学习群中。文末有加入方式。
简介
本文主要介绍我们熟悉的编译四大流程:预处理,编译,汇编,链接。因为是我们经常会讨论的话题,因此会尽可能详细讨论一下其中的细节。不会太难,大家要跟上脚步哦。
本文讨论的示例代码如下:
//helloworld.h
extern int printf(const char *format, ...);#if 1#define HELLOWORLD "hello world 1\n"
#else#define HELLOWORLD "hello world 2\n"
#endif
//helloworld.c
#include<helloworld.h>
int main()
{printf(HELLOWORLD);return 0;
}
预处理
命令:gcc -E helloworld.c -I. -o helloworld.i。其中-I.表示头文件搜索路径。
预处理主要处理哪些源代码文件中的以"#"开始的预编译指令。比如"#define"、"#include"等。主要的规则有:
- 处理所有条件预编译指令,比如"#if"、"#ifdef"、"#elif"、"#else"、"#endif"等。
- 将所有的"#define"删除,并展开所有的宏定义。
- 处理"#include"预编译指令,将被包含的文件插入到该预编译指令的位置(可以代码中利用这个特性)。注意,这个过程是递归进行的。
- 删除所有的注释"//"和"/* */"。这也就是说明,详细的代码注释,并不会造成目标文件变大。
- 添加行号和文件名标识,比如#2 “hello world.c” 2,以便于编译时编译器产生调试用的行号信息、编译错误或编译警告时显示行号。
- 保留所有的#pragma 编译器指令,因为编译器需要使用它们。
helleworld.i 内容如下,请根据上面规则,仔细对比一下。
# 1 "helloworld.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "helloworld.c"
# 1 "./helloworld.h" 1
extern int printf(const char *format, ...);
# 2 "helloworld.c" 2int main()
{printf("hello world 1\n");return 0;
}
注:工作中经常会遇到类似fatal error: xxxxxx.h: No such file or directory错误,就是在预编译阶段体现的。
但是预编译阶段不会进行语法校验,类似如下的编码,并不会提示错误。
编译
编译阶段是整个程序构建的核心部分,也是最复杂的部分。它主要对文件进行词法分析、语法分析、语义分析及优化生成的汇编代码文件。
命令:gcc -S helloworld.i -o helloworld.s 或者 /usr/lib/gcc/x86_64-linux-gnu/9/cc1 helloworld.i。其中cc1文件的路径,不同的操作系统可能不一致。我的虚拟机是ubunt 18.04 版本。
helloworld.s 内容如下:
.file "helloworld.i".text.section .rodata
.LC0:.string "hello world 1".text.globl main.type main, @function
main:
.LFB0:.cfi_startprocpushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6leaq .LC0(%rip), %rdicall puts@PLTmovl $0, %eaxpopq %rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE0:.size main, .-main.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~18.04) 9.4.0".section .note.GNU-stack,"",@progbits
因为我不是从事自然语言研究方向,编译阶段的词法分析、语法分析、语义分析就不再进一步展开。但是其中的优化还是需要略微深入一下。因为编译器优化,其中也存在一些坑。
编译器优化
编译器优化的目的:优化程序的性能和减少代码的生成。因此有时候提高编译优化等级,可以稍微提高程序的执行效率。常见的方式有以下几种:
1. 常量传播。能够直接计算出结果的变量,将被编译器由直接结果来替代。例如:
{int x = 10;printf("x = %d\n",x);
}
优化后:
{printf("x = %d\n",10);
}
2. 常量折叠。如果有可能,多个变量的计算可以最终替换成为一个变量的计算。例如:
{int a = 1;int b = 2;int c = a + b;printf("c = %d\n",c);
}
优化后:
{int c = 1 + 2;printf("c = %d\n",c);
}
再结合 1.优化方式,得
{printf("c = %d\n",3);
}
3. 复写传播。用一个变量替换两个或多个相同的变量。例如:
{int a = 1;int b = a;printf("b = %d\n",b);
}
优化后:
{int b = 1;printf("b = %d\n",b);
}
4. 公共表达式消除。如果一个表达式已经计算过了,并且从先前的计算到现在的E中的变量都没有发生变化,那么E的此次出现就成为了公共表达式,编译器不需要再次进行计算浪费性能。例如:
{int a = 1;int b = 2;int c = (a+b) * 2 + (b+a) * 6;
}
优化后:
{int a = 1;int b = 2;int E = a + b;int c = E * 2 + E * 6;
}
5. 无用代码消除。将永远不能执行到的代码或没有任何意义的代码清除。比如return 之后的代码、未使用的变量、变量给自己赋值等。
6. 数组范围检查消除。Java这种动态类型安全型的,那在访问数组时比如array[ ]时,Java不会像C/C++那样只是纯粹的裸指针访问,而是会在运行时访问数组元素前进行一次是否越界检查,这将会带来许多开销,如果即时编译器能根据数据流分析出变量的取值范围在[0,array.length]之间,那么在循环期间就可以把数组的上下边界检查消除,以减少不必要的性能损耗。
7. 方法内联。将比较简短的函数或方法直接粘贴到其调用者中,以减少函数调用时的开销。例如:
int test()
{printf("i'm test\n");
}
{int a = 1;test();int b = 2;
}
优化后:
{int a = 1;printf("i'm test\n");int b = 2;
}
8. 逃逸分析。逃逸分析的基本原理就是分析对象动态作用域。如果确定一个方法不会逃逸出方法之外,那让整个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧而销毁。在一般应用中,不会逃逸的局部对象所占用的比例很大,如果能在编译器优化时,为其在栈上分配内存空间,那大量的对象就会随着方法结束而自动销毁了,不用依赖前面讲的GC或者记忆力,系统的压力将会小很多。
编译器优化的坑
编译器优化带来的问题有很多场景,在网上搜索的话,应该有很多实际案例。可以参看这两个案例:
不同优化选项对ARM下C语言编译的影响 - 守夜者 - 博客园
关于O2编译选项的一个过优化问题及其解决方法_o2优化-CSDN博客
汇编
汇编过程就是将汇编文件转换为机器可以执行的指令。因此它的内部逻辑比较单一,仅需要将汇编语句对照表格一一翻译即可。
命令:gcc -c helloworld.s -o helloworld.o或 as helloworld.s -o helloworld.o亦或gcc -c helloworld.c -o helloworld.o。
问题:x86环境的虚拟机为什么想反汇编(objdump -d *.so)arm 平台的动态库,失败呢?
答:我们在工作中,经常会遇到一些问题,需要查看动态库的信息,比如查看依赖库,反汇编,甚至是gdb调试。但是嵌入式平台的资源有限,一般是不会集成相关工具的。常见的方式就是将目标文件copy到本地虚拟环境中,进行调试。但是我们往往用objdump,gdb等工具时,提示如下失败:
其原因:.o、.so、可执行程序都已经是二进制文件,是对应机器可识别的指令。而嵌入式平台一般都是arm架构,开发电脑是x86,两套架构识别的指令不一致,所以不能解析对方的机器码。若想在虚拟机中调试其他平台的动态库或可执行程序,需要使用交叉编译工具链中的对应工具。
链接
链接才是我们整个过程中最为复杂的过程,这个阶段做了很多很多事情。因此出现异常的概率也高。大致可以分为两个范围。
- 静态链接过程。主要指生成动态库或可执行文件的过程。比如:工作中,我们经常遇到工程编译提示错误,大部分都是在链接阶段提示的。
- 动态链接过程。就是程序运行过程。我们知道若程序依赖动态库,那么程序在真正运行前,需要找到对应的动态库并加载。常见的问题就是找不到对应动态库,或对应符号找不到;有时会出现匪夷所思的现象。有兴趣的可以了解我遇到的一个案例:坑惨啦!!!——符号冲突案例分析-CSDN博客
为了不打击大家的信心,本篇文章不会再进一步展开讨论。后续我会慢慢揭开链接过程的面纱,其实也就那么一回事。
不过大家可以先思考以下几个问题,尝试猜测链接做了哪些事情。
一、helloworld.o 中引用了printf方法,程序运行时,它是怎么知道符号地址的呢?该跳转到哪里,继续执行程序呢?
二、程序正真运行前,需要做哪些事情呢?动态库加载、符号重定位?其大致逻辑如何呢?
技巧分享
本章节主要和大家分享一下,我工作中用到的小技巧,也欢迎大家在评论区补充,我会更新到文章中。
一、利用"#include",更好的维护代码。
我们知道#include是预处理过程中将文件内容加载到对应位置的操作。那么我们可以将一些可变的信息保存在文件中,那么就不会更改我们的.c 源文件。
比如:做过单片机LCD显示的朋友,肯定知道每一张照片信息,其实就是一个超大的整型数组。常见的方式如下:
//picture.c
int picture_data[] = {*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,
}
每当图片信息变化时,则需要更新picture_data数组里面的信息,也就要更改picture.c源文件。我觉得这样并不友好。优化如下:
//picture_data.h
{*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,
}//picture_data.c
int picture_data[] =
#include<picture.h>
;
这样每次图片更新,仅需要替换picture_data.h文件即可。
二、如何快速确认"#if"、"#ifdef"、"#elif"、"#else"、"#endif" 的实际分支。
在大型的工程项目中,随着需求的迭代代码分支也比较多,经常会用到类似"if"、"#ifdef"、"#elif"、"#else"、"#endif" 等分支控制方式。这就导致我们有时不知道实际代码编译的是哪一部分。我的快速定位的方式就是在对应的分支中加上乱码。比如:
亦或者加上错误提示:
再多问一下,大家知道上面两种方式的不同吗?(提示一下,不同的阶段。)
总结
以上便是本文的内容,我们回顾了编译的四大过程:预处理,编译,汇编,链接。以及每个过程大致做了哪些事,可能会遇到的问题;也知道了编译器优化带来的可能弊端,因此编译器的优化选项我们要慎重,如果对效率要求不高,建议开启O1即可;链接阶段是一大难点,本文没有展开叙述,但是通过案例和抛出的问题,我相信,大家对链接肯定产生了莫大的兴趣。请别着急,关注专栏,后续一定会娓娓道来。
有任何相关问题欢迎留言讨论,我会尽快回复。
若您正遇到相关问题,苦于没有一群志同道合的朋友交流,探讨。也欢迎加入我们的讨论组群。可通过私聊我,我会尽快拉你进群。
相关文章:

【程序员的自我修养01】编译流程概述
绪论 大家好,欢迎来到【程序员的自我修养】专栏。正如其专栏名,本专栏主要分享学习《程序员的自我修养——链接、装载与库》的知识点以及结合自己的工作经验以及思考。编译原理相关知识本身就比较有难度,我会尽自己最大的努力,争取…...

在PyCharm中正确设置Python项目
大家好,在Mac和Linux都支持Python,但许多开发者发现正确设置Python项目很困难。本文汇总了多平台中运行Python的方法,提高编程的效率,如下所示: 使用命令行运行Python。 在PyCharm(免费社区版)…...
scoop bucket qq脚本分析(qq绿色安装包制作)
url字段 以qq.json为例,其对应的scoop配置文件在$env:scoop\buckets\extras\bucket\qq.json 其中的url字段 "url":"https://webcdn.m.qq.com/spcmgr/download/QQ9.7.17.29230.exe#/dl.7z"为qq安装包下载地址,后缀#/dl.7z为自行添加…...

Elasticsearch:将最大内积引入 Lucene
作者:Benjamin Trent 目前,Lucene 限制 dot_product (点积) 只能在标准化向量上使用。 归一化迫使所有向量幅度等于一。 虽然在许多情况下这是可以接受的,但它可能会导致某些数据集的相关性问题。 一个典型的例子是 Cohere 构建的嵌入&#x…...

YOLOV7主干改进,使用fasternet轻量化改进主干(完整教程)
1,Pconv(来自Fasternet)(可作为模型中的基础卷积模块使用) 论文链接:https://arxiv.org/abs/2303.03667 2,为了大家方便的使用,这里我对原本的PConv的代码做了部分的改动࿰…...

DALSA.SaperaLT.SapClassBasic无法加载,试图加载格式不正确的程序,c#
情景:用c#wpf写DALSA线扫相机的项目,生成时不报错,运行到DALSA相关的代码就报错找不到dll(DALSA的技术支持没给到任何支持 ) 一.根据框架选择dll 如果是.net framework框架(比如说.net480)&am…...

设计模式-创建型模式-工厂方法模式
一、什么是工厂方法模式 工厂模式又称工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。工厂方法模式是目标是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中。…...

科研/比赛必备工具及系列笔记集合
科研/比赛必备工具及系列笔记集合 零、前言一、常用工具系列1.1 笔记平台使用感受系列1.2 常用开发平台系列 二、论文系列2.1 检索工具系列2.2 投稿调研系列2.3 常见国际期刊/会议2.4 常见中文核心期刊/会议 三、文献系列3.1 画图工具系列3.2 翻译工具系列3.3 英文纠正系列3.4 …...

萨科微举办工作交流和业务分享会
萨科微(www.slkoric.com)举办工作交流和业务分享会,狠抓人才培养团队的基本功建设。萨科微总经理宋仕强先生认为,当下市场经济形势复杂多变,给公司经营带来巨大压力,同时考验着企业自身的发展韧性。萨科微公…...

一篇文章让你入门python集合和字典
嗨喽~大家好呀,这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 一、集合: 增加 add 删除 del 删除集合 discard(常用)删除集合中的元素 ,删除一个不存在的元素不会报错 remove 删除一个不存在的元素会报错 pop随…...

各种工具的快捷键或命令
前言 这里就存放自己存有的一些小工具的地址以及工具的命令。 正文 零、各种小工具 1、wizTree:磁盘分析工具-分析磁盘的文件夹存储 2、稻壳阅读器:有黑色背景 3、youtube 视频下载:https://zh.savefrom.net/226/ 4、视频录制:Bandica…...

【Web】preg_match绕过相关例题wp
目录 ①[FBCTF 2019]rceservice ②[ctfshow]web130 ③[ctfshow]web131 ④[NISACTF 2022]middlerce 简单回顾一下基础 参考文章 p牛神文 preg_match绕过总的来讲就三块可利用 数组绕过、PCRE回溯次数限制、换行符 ①[FBCTF 2019]rceservice 先贴出附件给的源码 &l…...
XSLVGL2.0 User Manual 主题管理器(v2.0)
XSLVGL2.0 开发手册 XSLVGL2.0 Brief 1、概述2、特性3、APIs3.1、xs_page_theme_register3.2、xs_page_get_theme_current3.3、xs_page_set_theme_current3.4、xs_page_get_theme_count3.5、xs_page_get_theme_id3.6、xs_page_get_theme_name3.7、xs_page_get_theme4、使用方法…...

visionOS空间计算实战开发教程Day 2 使用RealityKit显示3D素材
我们在Day1中学习了如何创建一个visionOS应用,但在第一个Demo应用中我们的界面内容还是2D的,看起来和其它应用并没有什么区别。接下来我们先学习如何展示3D素材,苹果为方便开发人员,推出了RealityKit,接下来看…...

【图解系列】一张图带你了解 DevOps 生态工具
一张图带你了解 DevOps 生态工具 ✅ 协作(Collaborate):JIRA、Confluence 大家肯定不陌生了,我之前也写过利用 Jekyll 搭建个人博客的帖子。✅ 构建(Build):常用的 SCM(Software Con…...

Oracle的安装及使用流程
Oracle的安装及使用流程 1.Win10安装Oracle10g 1.1 安装与测试 安装版本: OracleXEUniv10.2.1015.exe 步骤参考:oracleXe下载与安装 安装完成后测试是否正常 # 输入命令连接oracle conn sys as sysdba; # 无密码,直接按回车 # 测试连接的s…...
CMakeLists.txt:打印find_package变量;判断库文件路径设定是否正确;install文件设置
CMake打印find_package变量;install文件设置 打印find_package找到的各种变量判断库文件是否被找到install文件设置install详细说明 打印find_package找到的各种变量 目的:find_package后,想使用找到的include/lib文件夹。 find_package(Yo…...

Mysql 解决Invalid default value for ‘created_at‘
在mysql版本 8.0 和 5.* 之间数据互导的过程中,老是会出现各种错误,比如 这个created_at 一定要有一个默认值, 但是我加了 default null 还是会报错,于是对照了其他的DDL 发现,需要再加 null default null 才行&#…...

Linux【安全 01】云服务器主机安全加固(修改SSHD端口、禁用登陆失败的IP地址、使用密钥登录)
云服务器主机安全加固 1.SSH登录尝试的系统日志信息2.安全加固方法2.1 修改SSHD端口2.2 禁用登陆失败的IP地址2.3 使用密钥登录 3.总结 1.SSH登录尝试的系统日志信息 Last failed login: Sat Oct 7 14:10:39 CST 2023 from xxx.xx.xx.xxx on ssh:notty There were 10 failed …...

nginx的n种用法(nginx安装+正向代理+反向代理+透明代理+负载均衡+静态服务器)
nginx的安装 一、安装依赖 # 一键安装四个依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel二、安装nginx yum install nginx三、检查是否安装成功 nginx -v四、启动/停止nginx /etc/init.d/nginx start /etc/init.d/nginx stop五、编辑配置文件…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...