.c(.cpp)文件到可执行文件

对于一份简单的.c/.cpp为后缀的源文件,他所使用的语言是人类可以阅读并看懂的,但是对于计算机来说,其可理解并执行的是二进制的机器码。

也就是说,计算机所能运行的是二进制的机器码,而早期为了方便人类阅读,使用一些简单的助记符来代替机器码,比如MOV,LOOP...等,而为了进一步增加编程的可读性,便诞生了c语言以及后续的一众高级语言,如c++,python等。

#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
} // 人类语言,计算机仅可执行01010101....重复的机器码

因此要想让我们写出的c或者cpp代码变为计算机可以执行的二进制机器码,就需要使用一些程序来通过一些手段将源代码编译成可执行的二进制机器码。

一般来说,编译器的工作流程如下:

  • 预处理: 源代码中的宏进行一切的宏展开,宏替换,以及条件编译等操作,此操作后文件中无任何宏定义。(.i文件)
  • 编译:将预处理后的文件通过编译器的词法分析,语法分析,语义分析,优化,代码生成等操作,生成对应的汇编语言文件(.s文件)
  • 汇编:将汇编语言文件通过汇编器的汇编操作,生成对应的机器码文件(.o文件)
  • 链接:将多个.o文件进行链接,生成最终的可执行文件(.exe文件/linux下无后缀)

因此,编译器所做的工作就是通过自己的一套规则将源代码转换为机器码。规则的制定一般是有对应的语言标准来规定,而编译器可以通过不同的实现来实现该标准即可。编译器更像是一个翻译软件,而它的工作就是根据人类的翻译需求来将一种语言翻译成另一种语言。常见的编译器有gcc(开源),clang(jetbrain系列),msvc(微软)等,不同的编译器支持的最新标准也不同。

至于编译器是怎么来的,可以上网搜一搜。

本次培训使用的编译器是gcc,可以使用gcc --version命令查看当前gcc版本。

若没有安装gcc,可以使用sudo apt-get install build-essential命令安装gcc。

下面我们以gcc为例,来看一下如何将.c/.cpp文件一步一步编译成可执行文件。

#include <stdio.h>
#define first 1
int main()
{
printf("Hello, world!\n");
int num = first;
printf("%d", num);
return 0;
} // 假设hello.c

预处理

在终端中输入gcc -E hello.c -o hello.i指令,-E选项表示只进行预处理,-o选项指定输出文件名为hello.i

然后打开hello.i文件,可以看到经过预处理后的源代码如下:

# 0 "hello.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
.....//此处省略一堆东西
int main()
{
printf("Hello, world!\n");
int num = 1;
printf("%d", num);
return 0;
}

如果回到.c文件中,我们查看stdio这个头文件,可以发现其中的内容和hello.i文件前面的内容是一样的,无非是头文件的宏直接变成了头文件所在的路径。而且我们定义的宏first也被替换成了1

编译

在终端中输入gcc -S hello.i指令,-S选项表示将文件进行编译,输出文件为翻译为汇编的.s文件。

然后打开hello.s文件,可以看到经过编译后的源代码如下:

	.file	"hello.c"
.text
.section .rodata
.LC0:
.string "hello world!"
.LC1:
.string "%d"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
leaq .LC0(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl $1, -4(%rbp)
........;省略一堆

可以看出,c语言通过编译器变为了可读性更差的汇编语言,但是还有一些东西是人可以看懂的,比如movcall等指令。

汇编

在终端中输入gcc -c hello.s 指令,-c选项表示将文件进行汇编,输出文件为二进制文件hello.o

可以看到确实变成二进制文件了,而且由于没有对应的解码器,无法直接看到其中内容。

链接

二进制文件链接

所谓的链接,就是将多个.o文件进行链接,生成最终的可执行文件。对于链接,针对的是多文件项目的编译,因为一个可执行文件中可能包含多个源文件,而这些源文件可能包含不同的函数,因此需要将这些源文件中的函数进行链接,生成最终的可执行文件。

对于上述的hello.o文件,由于是单文件,因此可以简单的使用

gcc hello.o -o hello生成可执行文件hello然后运行

./hello
Hello, world!

对于多文件来说并非如此,比如我们有两个个文件:

int sub(int a, int b)
{
return a - b;
} // sub.c
#include <stdio.h>
int sub(int a, int b); // 声明sub函数
int main()
{
int a = 10, b = 5;
int result = sub(a, b);
printf("%d", result);
} // main.c

如果直接使用gcc -c main.c生成可执行文件,回报出如下错误:

main.c:(.text+0x25): undefined reference to `sub'

即找不到sub函数的定义,这是显然的,sub函数的实现在sub.c中,main.c中我们只是告诉了编译器有一个函数sub,这个行为在预处理->编译->汇编的过程中不会报出任何错误,但是在链接过程中,编译器无法在参与链接的文件中找到sub的实现,因此报出了链接错误。

因此要想不报错,可以这样:

gcc main.c sub.c -o main,这样编译器会将两个文件编译为二进制文件,并将sub.o参与到链接过程中,生成最终的可执行文件main。要注意,链接的是二进制文件,而不是源文件。

对于使用多个二进制文件参与链接生成可执行文件的方法,其好处就是,将项目拆分为多个模块,当要对项目的某一部分进行修改时,只需要重新编译该模块为新的.o文件即可,而不需要重新编译整个项目。

库文件链接

库文件是指一些预先编译好的二进制文件,可以被多个程序共用。分为动态库(linux下的.so文件/windows下的.dll文件)和静态库(linux下的.a文件/windows下的.lib文件)之分。它们的区别就是静态库是连接时全部链接进可执行文件,而动态库是运行时才链接进可执行文件。这里就不演示使用gcc生成和链接库文件的过程了,可以参考相关资料。

至此,我们就走遍了如何从源代码到可执行文件的整个流程。

cmake

可以看到,对于比较大的项目,使用gcc的命令来进行编译会很繁琐和麻烦,就像由机器码到汇编再到c语言一样,为了简化编译流程,便有了从gcc -> makefile -> CMakeLists这样的变化。

简单介绍makefile

Makefile 是用于管理项目构建过程的工具,广泛用于 C/C++ 等语言的编译。它通过定义规则和指令,自动化编译、链接等步骤,大大简化了开发者的工作。也就是说,makefile通过一些规则来告诉编译器如何编译源代码,如何链接库文件,如何生成可执行文件。通过编写规则即可完成编译,而不需要手动指定每个编译选项。(我也不会makefile,这里简单介绍一下)当编写完makefile后,只需要运行make命令,makefile就会自动按照规则编译生成对应的可执行文件。注意make命令后接的是makefile的路径。

cmake与CMakeLists.txt

对于一些复杂的项目,makefile的规则编写起来会比较麻烦,因此出现了cmake。cmake是一种跨平台的编译工具,可以用来管理项目的构建,通过编写cmake,可以自动生成对应的makefile,然后运行make命令编译生成可执行文件。

cmake的配置文件是CMakeLists.txt,它是cmake的核心配置文件,主要用于定义项目的源文件、头文件、库文件等信息,以及编译选项等。

最简单的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.6)
# 指定项目所需要的cmake最小版本
project(hello)
# 指定项目名称
add_executable(hello hello.cpp)
# 添加可执行文件hello,并指定源文件为hello.cpp

结束后,在终端中运行cmake.命令,cmake会自动生成对应的makefile,然后运行make命令编译生成可执行文件。但是在生成的时候会出现很多配置文件,因此习惯上建立一个名字为build的文件夹,然后在该文件夹下运行cmake..命令,生成的makefile就在build文件夹中,然后运行make命令编译生成可执行文件。

后面关于cmake的使用,转如下链接

cmake的简单使用

安装opencv库并使用

所谓的库,即为我们提供了头文件以及一些预编译好的二进制文件,我们可以通过安装库文件来使用一些功能。

编译安装opencv

这里我们采用下载源码编译安装的方式来安装opencv。所谓的安装,其实就是将别人写好的代码编译为库,然后将头文件和编译好的库文件拷贝到系统的指定目录,同时还附带着一些说明文件,方便编译时找到头文件和库文件。

源码下载链接[https://github.com/opencv/opencv/releases]

安装教程参考:

[https://blog.csdn.net/weixin_44796670/article/details/115900538]

使用opencv

参考cmake的简单使用后半部分

从编译链接到cmake的更多相关文章

  1. 编译驱动链接到了Kernel32库问题

    最近开始学习驱动编程,根据网上的配置方法配置了驱动开发环境,用了一个简单的例子测试发现驱动居然链接到了kerner32库里面去了如图 : 显然是把Kernel.lib添加到了附加依赖库 如图 : 去掉 ...

  2. SQL SERVER 2012/2014 链接到 SQL SERVER 2000的各种坑

    本文总结一下SQL SERVER 2012/2014链接到SQL SERVER 2000的各种坑,都是在实际应用中遇到的疑难杂症.可能会有人说怎么还在用SQL SERVER 2000,为什么不升级呢? ...

  3. 使用Powershell链接到Office 365

    今天主要讲使用Powershell管理Office 365 可以分为office365用户管理,Exchange Online的管理等 1. 使用Powershell 链接到office 365 用户 ...

  4. java程序链接到sql server数据库

    package jianhua; import java.sql.*; public class ConDatabase { public static void main(String[] args ...

  5. MVC Controller 链接到 API Controller 以及反向链接

    MVC Controller 链接到 API Controller 以及反向链接 问题 想创建一个从 ASP.NET MVC controller 到 ASP.NET Web API controll ...

  6. Oracle中使用透明网关链接到Sqlserver[Z]

    Oracle中使用透明网关链接到Sqlserver 在最近项目中需要从Oracle中访问SQL Server数据库, 自然想到了透明网关. 因为Oracle数据库是Linux上的, 而Linux上的O ...

  7. SharePoint 2010 中创建超链接到Pop-Up对话框

    SharePoint 2010 中创建超链接到Pop-Up对话框         SharePoint 2010 推出了新式的带有阴影的弹出对话框,你感觉怎么样?我感觉倒是挺酷的.这样少打开了一个页面 ...

  8. 海思编译链编译出现__aeabi_unwind_cpp_pr1重定义怎么回事

    1.用arm-hisiv100nptl-linux-gcc编译代码,结果发现报错,__aeabi_unwind_cpp_pr1重定义,在librt.a先定义,使用的海思芯片是hi3520d. 2.本来 ...

  9. Oracle中使用透明网关链接到Sqlserver(转)

    测试环境介绍 1.ORACLEServer   Database version:10.2.0 IP:192.168.1.5 ORACLE_HOME:D:\oracle\product\10.2.0\ ...

  10. Windows Azure Web Site (19) Azure Web App链接到VSTS

    <Windows Azure Platform 系列文章目录> 之前遇到一个问题,客户在海外使用 我参考了一下国内Azure China的文档:https://school.azure.c ...

随机推荐

  1. 简简单单教你如何用C语言实现获取当前所有可用网口!

    一.获取本机所有可用网卡名 原理: 在 Linux 系统中,/proc 目录是一个位于内存中的伪文件系统. /proc目录是内核提供给我们的查询中心,通过查询该目录下的文件内容,可以获取到有关系统硬件 ...

  2. Linux驱动 | 从0写一个设备树节点实例

    一.前言 设备树是每一个Linux驱动工程师都必须掌握的一个知识点,有很多之前做单片机的朋友刚接触Linux驱动时,会一脸懵! 其实设备树的使用并没有大家想像的那么复杂,对于大部分工程师来说,只要会修 ...

  3. centos7.3离线安装和配置NFS

    概述 # NFS为 Network FileSystem 的简称,它的目的就是想让不同的机器.不同的操作系统可以彼此分享个别的档案啦! 目前在 Unix Like 当中用来做为文件服务器是相当不错的一 ...

  4. MySQL数据库基本操作包括MySQL过程、MySQL声明

    MySQL数据库 操纵数据库 查看数据库 show databases; 创建数据库 create database <database_name>; 删除数据库 drop databas ...

  5. jenkins动态切换环境

    一.代码层实现动态切换 1.首先在conftest.py下声明pytest_addoption钩子函数,写法如下 def pytest_addoption(parser): # 设置要接收的命令行参数 ...

  6. 一文搞懂 == 、equals和hashCode

    面试的时候,经常会被问到==和equals()的区别是什么?以及我们也知道重写equals()时候必须重新hashCode().这是为什么?既然有了hashCode()方法了,JDK又为什么要提供eq ...

  7. 报错解决:partially initialized module 'charset_normalizer' has no attribute 'md__mypyc' (most likely due to a circular import)

    在运行jupyter 时候报错'partially initialized module 'charset_normalizer' has no attribute 'md__mypyc' (most ...

  8. vue 消息订阅与发布

    vue 消息订阅与发布 一.场景 vue中非父子组件之间通信时,使用vuex有时间会很麻烦,这时候可以通过bus总线来实现 消息的订阅与发布 二.实现方法 1.main.js //main.js Vu ...

  9. CSS – Monospaced font & ch unit 等宽字体与 ch 单位

    前言 在做 Statistics Counter 时, 发现总是会跳, 研究后才发现原来是等宽搞的鬼, 这篇就来说说等宽字体. 参考 等宽字体在web布局中应用以及CSS3 ch单位嘿嘿 不等宽字体 ...

  10. ComfyUI 基础教程(五) —— 应用 IP-Adapter 实现图像风格迁移

    中秋假期,又可以玩玩 AI 了.前面介绍了 ComfyUI 的 Lora 模型以及 ControlNet,本文介绍另一个非常重要且使用的节点,IP-Adapter. 一. IP-Adapter 概念 ...