c++ 开发生态环境、工作流程、生命周期-拾遗

拾遗 1  生态环境初识

当您使用Visual Studio 2019进行C++开发时,您将进入C++生态环境。以下是一些重要的概念和步骤:

  1. C++程序的结构: 一个典型的C++程序包括源文件(.cpp)、头文件(.h)、编译后的目标文件(.obj)和最终的可执行文件(.exe)或库文件(.lib、.dll)。

  2. 第三方库的工作方式: 如果您需要使用第三方库,通常会将其源代码或者预编译的库文件(.lib、.dll)包含到您的项目中。第三方库通常会包含头文件和库文件,您需要将这些文件包含到您的项目中,并在编译和链接过程中告诉编译器和链接器去哪里寻找这些文件。

  3. 相互引用和VS配置: 在Visual Studio中,您可以通过项目属性来配置第三方库的引用。具体步骤包括:

    • 将第三方库的头文件路径添加到项目的“包含目录”中,这样您的程序中就可以包含这些头文件。
    • 将第三方库的库文件路径添加到项目的“库目录”中,这样链接器就知道在哪里找到这些库文件。
    • 在“链接器->输入”中添加需要链接的库文件名,告诉链接器在链接时需要使用这些库文件。
  4. C++生态环境的其他方面:

    • C++标准库:C++提供了丰富的标准库,包括容器、算法、输入输出等功能。
    • C++编译器:Visual Studio 2019内置了Microsoft的C++编译器(MSVC),也可以选择其他编译器比如GCC或者Clang。
    • 调试工具:Visual Studio提供了强大的调试工具,可以帮助您调试C++程序。

总的来说,C++生态环境包括了C++编译器、标准库、第三方库、开发工具等,通过合理配置和使用这些工具,您可以进行高效的C++开发。

拾遗 2  从源码到目标码(obj)、可执行码(exe)、文件库(lib,dll)

        当进行C++开发时,源码经历了多个阶段才能最终转化为可执行文件或库文件。以下是这个过程的详细步骤:

  1. 预处理阶段: 在预处理阶段,C++编译器会处理源文件(.cpp)中的预处理指令,比如#include和#define。预处理器会将头文件内容替换到源文件中,并进行宏替换等操作,生成一个经过预处理的源文件(通常是一个临时文件)。

  2. 编译阶段: 经过预处理的源文件会被编译器翻译成汇编代码(.asm文件),这个过程包括词法分析、语法分析、语义分析和优化等步骤。编译器将源码翻译成目标代码,生成一个或多个目标文件(.obj文件),每个目标文件对应源文件中的一个编译单元。

  3. 链接阶段: 在链接阶段,链接器会将多个目标文件以及所需的库文件(静态库或动态库)链接在一起,生成最终的可执行文件(.exe)或者库文件(.lib、.dll)。链接器会解析符号引用,处理重定位等工作,确保最终的可执行文件或库文件可以正确运行。

        目标码(Object Code)是编译器生成的中间文件,包含了机器代码、符号表、重定位信息等。目标码是编译后的源码的中间表示,还不能被直接执行。目标码的作用是为了后续的链接阶段使用,链接器会将多个目标文件合并成最终的可执行文件或库文件。

        总的来说,C++程序从源码到目标码再到可执行文件或库文件经历了预处理、编译和链接三个阶段。目标码是编译器生成的中间文件,用于链接器生成最终的可执行文件或库文件。

拾遗3 一套具体的流程解释:

1. C/C++运行的四个步骤

        编写完成一个C/C++程序后,想要运行起来,必须要经过四个步骤:预处理、编译、汇编和链接。每个步骤都会生成对应的文件,如下图所示(注意后缀名):

 

2.名词解释

为了后面过程的介绍更方便,这里对C++编译过程中涉及的一些常用名词进行解释。

2.1 GCC、GNU、gcc与g++

    GNU:一个操作系统,具体内容不重要,感兴趣可以参考:

GCC、GNU到底啥意思?-CSDN博客文章浏览阅读1.9w次,点赞182次,收藏201次。本文目录GCC/GNU名字的由来GCCGNU理查德·斯托曼GPLLinux参考文献GCC/GNU名字的由来今天闲来无事,想要清理一下磁盘,遇到了一个名为 TDM-GCC-64 的文件夹,也就是gcc编译器所在的文件,突然好奇,编译器为什么叫GCC呢?于是Wiki了一下,然后便一发不可收拾……GCC原来GCC是——GNU编译器套装,那么问题来了,GNU又是啥?不知道你有没有好奇过:这..._gnuhttps://blog.csdn.net/qq_43617936/article/details/104504992

  • GCC:GNU Compiler Collection(GNU编译器集合)的缩写,可以理解为一组GNU操作系统中的编译器集合,可以用于编译C、C++、Java、Go、Fortan、Pascal、Objective-C等语言。
  • gcc:GCC(编译器集合)中的GNU C Compiler(C 编译器)
  • g++:GCC(编译器集合)中的GNU C++ Compiler(C++ 编译器)

简单来说,gcc调用了GCC中的C Compiler,而g++调用了GCC中的C++ Compiler。 - 对于 *.c 和 *.cpp 文件,gcc分别当作 c 和 cpp文件编译,而g++则统一当作cpp文件编译。

2.2 代码编译命令

gcc/g++常用指令选项:

2.3 GDB(gdb)

GDB(gdb)全称“GNU symbolic debugger”,是 Linux 下常用的程序调试器。 为了能够使用 gdb 调试,需要在代码编译的时候加上-g,如

g++ -g -o test test.cpp

本文中只演示从源代码生成可执行二进制文件的过程,暂不涉及调试过程。调试的配置会在另一篇文章中专门介绍。

3. C++编译过程详解

        初次使用Visual C++的时候一定有经历过项目的配置,肯定会对预编译头,链接器之类的东西心生疑惑,敲代码就敲代码,怎么有这么多要设置的东西。还有看着Windows版本的程序和Linux版本的编译完成后的文件夹也会疑惑,编译完除了程序怎么还有一堆.obj和.o文件。

        首先我们要明白一个流程,程序要运行起来,必须要经过四个步骤:预处理、编译、汇编和链接。每个步骤都会生成对应的文件。像下图一样,接下来我会分成四个步骤描述一下每个步骤都会做什么事

预编译:

        预编译如其名,就是编译前的一些准备工作。代码文件放到编译器面前,他肯定也不知道怎么去读,看到头文件他也不知道头文件里是什么,所以预编译就要把一些#define的东西完成文本替换,然后#include的文件里的内容复制到.cpp文件里,如果.h文件里还有.h文件,那递归展开,注释这些编辑器也不管的,完全是给程序员看的。简而言之,就是剔除掉编译器看不懂的东西,然后把所有头文件展开的一个过程。

编译:

        编译只是把我们写的代码转为汇编代码,它的工作是检查词法和语法规则,所以,如果程序没有词法或则语法错误,那么不管逻辑是怎样错误的,都不会报错。

汇编:

        汇编代码转换机器码,这个阶段,非底层的程序员不需要考虑, 编译器不会搞错的。也与c/c++开发者无关,但是我们可以利用反汇编来调试代码,学习汇编语言依然是必备的。

链接:

        配置链接库的重要性就体验在这里,配置的静态链接库还是动态链接库都会在这部分和最后的程序链接到一起。

        在这之前引用百度过来的一段话介绍下静态链接和动态链接的区别

    (1)静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。

  (2)动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。

        C语言代码经过编译以后,并没有生成最终的可执行文件(.exe 文件),而是生成了一种叫做目标文件(Object File)的中间文件(或者说临时文件)。目标文件也是二进制形式的,它和可执行文件的格式是一样的。对于 Visual C++,目标文件的后缀是.obj;对于 GCC,目标文件的后缀是.o。这就是一开始所说的编译完一堆.obj和.o文件的来源。

        目标文件经过链接(Link)以后才能变成可执行文件。既然目标文件和可执行文件的格式是一样的,为什么还要再链接一次呢,因为编译只是将我们自己写的代码变成了二进制形式,它还需要和系统组件(比如标准库、动态链接库等)结合起来,这些组件都是程序运行所必须的。链接(Link)其实就是一个“打包”的过程,它将所有二进制形式的目标文件和系统组件组合成一个可执行文件。完成链接的过程也需要一个特殊的软件,叫做链接器(Linker)。

        这里还要讲一个东西,C++程序编译的时候其实只识别.cpp文件。每个cpp文件都会分别编译一次,生成一个.o或者.obj文件。这个时候,链接器除了将目标文件和系统组件组合起来,还需要将编译器生成的多个.o或者.obj文件组合起来。

本节内容用下面的简单C++工程做演示。示例的文件结构如下:

|—— include|—— func.h
|—— src|—— func.cpp
|—— main.cpp

其中,main.cpp是主要代码,include/func.h是自定义函数的头文件,src/func.cpp是函数的具体实现

各个文件的内容如下:

// main.cpp
#include <iostream>
#include "func.h"using namespace std;int main(){int a = 1;int b = 2;cout << "a + b = " << sum(a, b) << endl;; return 0;
}
// func.h
#ifndef FUNC_H
#define FUNC_Hint sum(int a, int b);#endif
// func.cpp
#include "func.h"int sum(int a, int b) {return a + b;
}

3.1 预处理(Preprocess)

        预处理,顾名思义就是编译前的一些准备工作。

        预编译把一些#define的宏定义完成文本替换,然后将#include的文件里的内容复制到.cpp文件里,如果.h文件里还有.h文件,就递归展开。在预处理这一步,代码注释直接被忽略,不会进入到后续的处理中,所以注释在程序中不会执行。

        gcc/g++的预处理实质上是通过预处理器cpp(应该是c preprocess的缩写?)来完成的,所以我们既可以通过g++ -E,也可以通过cpp命令对main.cpp进行预处理:

g++ -E -I include/ main.cpp -o main.i
# 或者直接调用 cpp 命令
cpp -I include/ main.cpp -o main.i

        上述命令中: - g++ -E 是让编译器在预处理之后就退出,不进行后续编译过程,等价于cpp指令 - -I include/用于指定头文件目录 - main.cpp是要预处理的源文件 - -o main.i用于指定生成的文件名

        预处理之后的程序格式为 *.i,仍是文本文件,可以用任意文本编辑器打开。

执行完预处理后的文件结构如下:

|—— include|—— func.h
|—— src|—— func.cpp
|—— main.cpp
|—— main.i

 3.2 编译(Compile)

编译 只是把我们写的代码转为汇编代码它的工作是检查词法和语法规则,所以,如果程序没有词法或则语法错误,那么不管逻辑是怎样错误的,都不会报错
编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程

编译的指令如下:

g++ -S -I include/ main.cpp -o main.s

与预处理类似,上述命令中: - g++ -S是让编译器在编译之后停止,不进行后续过程 - -I include/用于指定头文件目录 - main.cpp是要编译的源文件 - -o main.s用于指定生成的文件名

编译完成后,会生成程序的汇编代码main.s这也是文本文件,可以直接用任意文本编辑器查看。

执行完编译后的文件结构如下:

|—— include|—— func.h
|—— src|—— func.cpp
|—— main.cpp
|—— main.i
|—— main.s

3.3 汇编(Assemble)

汇编过程将上一步的 汇编代码( main.s) 转换成机器码(machine code), 这一步产生的文件叫做目标文件( main.o), 是二进制格式

gcc/g++的汇编过程通过 as 命令完成,所以我们可以通过g++ -cas命令完成汇编:

g++ -c -I include/ main.cpp -o main.o
# 或者直接调用 as 命令
as main.s -o main.o

上述指令中: - g++ -c让编译器在汇编之后退出,等价于 as - -I include/仍是用于指定头文件目录 - main.cpp是要汇编的源文件 - -o main.o用于指定生成的文件名

汇编这一步需要为每一个源文件(本文示例代码中为main.cppfunc.cpp)产生一个目标文件。因此func.cpp也需要执行一次这个汇编过程产生一个func.o文件:

# 可以用 g++ -c 命令一步生成 func.o
g++ -c -I include/ src/func.cpp -o src/func.o
# 当然也可以按照上面的预处理、编译、汇编三个步骤生成func.o

到了这一步,代码的文件结构如下:

|—— include|—— func.h
|—— src|—— func.cpp|—— func.o
|—— main.cpp
|—— main.i
|—— main.s
|—— main.o

 3.4 链接(Link)

C/C++代码经过汇编之后生成的目标文件(*.o)并不是最终的可执行二进制文件,而仍是一种中间文件(或称临时文件),目标文件仍然需要经过链接(Link)才能变成可执行文件

既然目标文件和可执行文件的格式是一样的(都是二进制格式),为什么还要再链接一次呢?
因为 编译只是将我们自己写的代码变成了二进制形式,它还需要和系统组件(比如标准库、动态链接库等)结合起来,这些组件都是程序运行所必须的
链接(Link)其实就是一个“打包”的过程,它将所有二进制形式的目标文件(.o)和系统组件组合成一个可执行文件。完成链接的过程也需要一个特殊的软件,叫做 链接器(Linker)
此外需要注意的是: C++程序编译的时候其实只识别.cpp文件,每个cpp文件都会分别编译一次,生成一个.o文件。这个时候,链接器除了将目标文件和系统组件组合起来,还需要将编译器生成的多个.o或者.obj文件组合起来生成最终的可执行文件(Executable file)

以本文中的代码为例,将func.omain.o链接成可执行文件main.out,指令如下:

g++ src/func.o main.o -o main.out
  • -o main.out用于指定生成的可执行二进制文件名
  • 由于g++自动链接了系统组件,所以我们只需要把自定义函数的目标文件与main.o链接即可。

运行main.out,结果如下:

./main.out
a + b = 3

3.5 小结

从上面的介绍可以看出,从C++源代码到最终的可执行文件的中间过程并不简单。了解预处理、编译、汇编、链接各个步骤的作用有助于我们处理更加复杂的项目工程。

不过也不必被这么麻烦的编译过程劝退,当我们编译简单.cpp代码时,

// hello.cpp
#include <iostream>
using namespace std;int main(){cout << "Hello, world!" << endl;return 0;
}

仍然可以直接使用g++命令生成可执行文件,而不必考虑中间过程:

g++ hello.cpp -o hello
./hello
Hello, world!

4. 参考教程

  1. GCC、GNU到底啥意思?
  2. C++ 预编译,编译,汇编,链接
  3. C/C++语言编译链接过程

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

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

相关文章

Java 并发性和多线程3

七、线程安全及不可变性 当多个线程同时访问同一个资源&#xff0c;并且其中的一个或者多个线程对这个资源进行了写操作&#xff0c;才会产生竞态条件。多个线程同时读同一个资源不会产生竞态条件。 我们可以通过创建不可变的共享对象来保证对象在线程间共享时不会被修改&…

实战之-Redis代替session实现用户登录

一、设计key的结构 首先我们要思考一下利用redis来存储数据&#xff0c;那么到底使用哪种结构呢&#xff1f;由于存入的数据比较简单&#xff0c;我们可以考虑使用String&#xff0c;或者是使用哈希&#xff0c;如下图&#xff0c;如果使用String&#xff0c;注意他的value&…

AI老照片修复-Bringing-Old-Photos-Back-to-Life

&#x1f3e1; 个人主页&#xff1a;IT贫道-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;私聊博主加WX好友&#xff0c;获取更多资料哦~ &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录 1. AI老照片修复原理-…

第18课 移植FFmpeg和openCV到Android环境

要在Android下从事音视频开发&#xff0c;同样也绕不开ffmpegopencv&#xff0c;不管是初学者还是有一定经验的程序&#xff0c;面临的首要问题就是环境的搭建和库文件的编译配置等问题&#xff0c;特别是初学者&#xff0c;往往会在实际开发前浪费大量的时间来编译ffmpeg及ope…

nvm管理多版本Node.js

nvm管理多版本Node.js 可能大家都曾苦恼于Node环境问题&#xff0c;某个项目需要升版本&#xff0c;某项目又需要降&#xff0c;甚至还出现npm版本与Node对不上的情况。 通过nvm进行版本管理&#xff0c;即可解决。 卸载Node 通过命令行输入node -v命令查看是否已安装Node&…

认知觉醒(九)

认知觉醒(九) 专注力——情绪和智慧的交叉地带 第一节 情绪专注&#xff1a;一招提振你的注意力 用元认知来观察自己的注意力是一件很有意思的事情&#xff0c;相信你可以轻易观察到这种现象&#xff1a;身体做着A&#xff0c;脑子却想着B。 跑步的时候&#xff0c;手脚在…

10.9.2 std::function 存储函数对象 Page184

41行&#xff0c;pending只是inc的复制品&#xff0c;所以43&#xff0c;44行&#xff0c;不会改变inc()的值 demo_function2()的运行结果&#xff1a; 59行&#xff0c;pending是inc的引用&#xff0c;所以61,62行将会改变inc()的值

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK实现相机的高速图像保存(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK实现相机的高速图像保存&#xff08;C&#xff09;&#xff09; Baumer工业相机Baumer工业相机的图像高速保存的技术背景Baumer工业相机通过NEOAPI SDK函数图像高速保存在NEOAPI SDK里实现线程高速图像保存&#xff1a;工业相机高…

Vue-13、Vue绑定css样式

1、绑定css样式字符串写法&#xff0c;适用于&#xff1a;样式的类名不确定 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>绑定css样式</title><!--引入vue--><script type"tex…

2024三掌柜赠书活动第三期:Rust系统编程

目录 前言 Rust语言概念 关于《Rust系统编程》 Rust系统编程的核心点 Rust系统编程的关键技术和工具 编辑推荐 内容简介 作者简介 图书目录 书中前言/序言 《Rust系统编程》全书速览 结束语 前言 在技术圈&#xff0c;最近的编程语言新秀当属Rust莫属&#xff0c;R…

Java Swing 图书借阅系统 窗体项目 期末课程设计 窗体设计

视频教程&#xff1a; 【课程设计】图书借阅系统 功能描述&#xff1a; 图书管理系统有三个角色&#xff0c;系统管理员、图书管理员、借阅者&#xff1b; 系统管理员可以添加借阅用户&#xff1b; ​图书管理员可以添加图书&#xff0c;操作图书借阅和归还&#xff1b; 借…

希尔排序和计数排序

&#x1f4d1;前言 本文主要是【排序】——希尔排序、计数排序的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句…

ElasticSearch(1):Elastic Stack简介

1 简介 ELK是一个免费开源的日志分析架构技术栈总称&#xff0c;官网https://www.elastic.co/cn。包含三大基础组件&#xff0c;分别是Elasticsearch、Logstash、Kibana。但实际上ELK不仅仅适用于日志分析&#xff0c;它还可以支持其它任何数据搜索、分析和收集的场景&#xf…

linux创建文件夹命令

我们可以使用mkdir命令在 Linux 或类似 Unix 的操作系统中创建新目录或文件夹。本文将介绍如何在 Linux 或 Unix 系统中创建文件夹&#xff08;也称为“目录”&#xff09;。 操作步骤如下&#xff1a;1.在 Linux 中打开终端应用程序。2.输入mkdir命令。3.输入文件夹名称。 m…

Unity与Android交互通信系列(4)

上篇文章我们实现了模块化调用&#xff0c;运用了模块化设计思想和简化了调用流程&#xff0c;本篇文章讲述UnityPlayerActivity类的继承和使用。 在一些深度交互场合&#xff0c;比如Activity切换、程序启动预处理等&#xff0c;这时可能会需要继承Application和UnityPlayerAc…

服务器 conda update 失败解决方法

1. 强制 conda update 租借一台服务器&#xff0c;发现 conda 版本是4.10.3&#xff0c;需要升级&#xff0c;使用了如下命令都没有效果&#xff0c;仍然是一样的版本 conda update conda update --all conda update -n base -c defaults conda最后强制用conda-forge通道更新…

Python3.10安装教程

Python3.10安装 Python的安装按照下面几步进行即可&#xff0c;比较简单。 下载Python安装文件&#xff0c;打开Python的下载页面&#xff0c;我这里选择安装的版本是3.10.11&#xff0c;根据自己电脑版本选择对应安装包 安装包下载完毕后&#xff0c;按照步骤开始安装。选择…

[GN] 微服务开发框架 --- Docker的应用 (24.1.9)

文章目录 前言Docekr镜像命令 Docekr镜像命令容器操作创建容器查看容器日志查看容器状态进入容器 数据卷数据集操作命令给nginx挂载数据卷给MySQL挂载本地目录 Dockerfile自定义镜像镜像结构 使用Dockerfile构建Java项目基于Ubuntu构建Java项目基于java8构建Java项目 Docker-Co…

SQLServer设置端口,并设置SQLServer和SQLServer Browser服务

SQLServer默认使用动态端口&#xff0c;即每次启动sqlserver.exe时&#xff0c;端口port都会动态变化。若要使用静态端口&#xff0c;比如port1433&#xff0c;则需要在SQL Server Configuration Manager(简称SSMS&#xff09;里配置。这里以SQL Server 2005 Configuration Man…