1 Star 0 Fork 0

时光 / C-Practice

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
vscode_C++.md 251.67 KB
一键复制 编辑 原始数据 按行查看 历史
时光 提交于 2023-07-26 13:01 . update

目录:

  1. VSCode是什么?
  2. VScode的下载和安装?
  3. VScode配置 C/C++ 开发环境
  4. 在VSCode上编写C语言代码并编译成功
  5. VSCode的调试

1. VScode是什么?

Visual Studio Code 简称 VS Code,是一款跨平台的、免费且开源的现代轻量级代码编辑器,支持几乎主流开发语言的语法高亮、智能代码补全、自定义快捷键、括号匹配和颜色区分、代码片段提示、代码对比等特性,也拥有对git的开箱即用的支持。同时,它还支持插件扩展,通过丰富的插件,用户能获得更多高效的功能。

一般有【稳定版】和【体验版】两个版本

img

2.VScode的下载和安装?

2.1 下载和安装

下载:

下载地址:https://code.visualstudio.com/

img

根据自己的电脑的实际情况,下载对应的稳定版本,如果担心自己下载的有问题,可以加鹏哥微信:xxxxx,领取,这样更加方便靠谱。

安装:

下载下来的安装包直接双击,照着我的视频安装就行,安装完成后,正常打开,如下如图所示:

img

2.2 环境的介绍

环境介绍:

img

安装中文插件:

毕竟这个VScode 默认是英文的,我们使用不习惯的话,可以安装中文插件,将VSCode的界面汉化的,在左边的侧边栏中点击插件,就可以搜索:Chinese,显示的第一个插件就是汉化包,直接安装即可。

img

安装完汉化包后,立马就在右下角提示,如下窗口,点击restart,会自动重启VSCode,即可汉化使用,非常方便。

img

汉化之后的界面如下:

img

3. VScode配置 C/C++ 开发环境

VSCode 安装好之后,我们还是要知道,VSCode毕竟是一个高级的编辑器,只能用来写C/C++代码,不能直接编译代码。所以,如果我们要能使用VSCode搭建C/C++的编译和调试环境,还必须有编译器使用。为了方便,我们就使用MinGW-w64MinGW-w64移植到windows平台的一个gcc编译器,使用起来也是非常方便的。下面我们就演示怎么下载和配置MinGW-w64

3.1 下载和配置MinGW-w64 编译器套件

注:在苹果电脑上不需要MinGW-W64套件,直接跳转到第6点,然后可以跳过3.1

下载:

下载地址:https://sourceforge.net/projects/mingw-w64/files/

点击这个链接后,往下翻,找到对应的版本,下载,别搞错了!

img

下载下来后,你得到一个如图所示的文件(我的电脑上安装了360解压缩软件,所以显示可能跟你的有所差异,但是名字相同)。

img

接下来就是解压,如果你电脑上有解压缩软件,直接解压,如果没有的话,建议安装一个360解压缩(下载链接:https://yasuo.360.cn/),很方便。

解压出来后,进到文件夹中,拷贝mingw64这个文件夹到一个最简单的目录下,路径的名字不要有中文、空格、特殊字符等,比如:C盘或者D盘的根部目录,这里我放在C盘,拷贝过来后,如下图所示:

img

此时mingw64的编译器的路径就是:C:\mingw64

img

配置MinGW64:

在Windows电脑上,按win+s 快捷键,或者直接在在搜索框中搜:环境变量,就能看到:

img

进入系统属性,点击【环境变量】

img

进入环境变量管理页面:

img

新加一个环境变量值,我们前面已经将mingw64拷贝到了C:\mingw64目录下了,在这个目录下有一个bin的文件夹,这个文件夹下是gcc等编译器的可执行文件,所以我们就将:C:\mingw64\bin添加到path的环境变量中就行,点击确定就行。如下图所示:

imgimg

验证一下:

在windows电脑上打开cmd窗口,输入: gcc --version或者where gcc,要是能看到下面的界面,就说明MinGW-w64的编译器套件已经配置好了。

img

到此为止,windows版本的gcc/g++编译器已经配置好了,如果你会使用gcc/g++命令行编译代码的话,已经可以正常使用了,但是我们如果使用VSCode来写代码,并且完成编译,还是不够的。

3.2 安装C/C++插件

要在VSCode上编译代码,还得配置C/C++插件,再次在VSCode的插件中搜索:C/C++,点击安装。

img

3.3 重启VSCode

这一步很重要,让前面的设置生效,要不然后面会出现问题的。

4.在VSCode上编写C语言代码并编译成功

4.1 打开文件夹

在VSCode上写代码都是首先要打开文件夹的,这样也方便管理代码和编译器产生的可执行程序,和VSCode生成的配置文件等。

在写代码前,我们想清楚想把代码放在什么地方管理,比如:在C盘下,我创建一个code的文件夹,我希望以后我写的代码都管理在code目录下,然后每天写的代码我再使用一个文件夹管理,比如:test_12_14,如下图:

img

在我们写代码前先创建好文件夹,那假如在12月14日写代码就,打开test_12_14的文件夹。

imgimg

继续下一步,勾选并信任:

img

这样我们就打开了test_12_14的文件夹,在VSCode这里显示的都是大写字母。

img

4.2 新建C语言文件,编写C语言代码

创建C语言文件:

img

编写C语言代码:

img

代码写好了,为了编译代码,还没有完,我们继续下一步。

4.3 设置C/C++编译的选项:c_cpp_properties.json

鼠标点在.c的源文件内部,按ctrl+shift+p快捷键,在弹出的界面中选择:【C/C++:编辑配置(UI)】,然后进入设置页面。

img

配置【编译器路径】为你安装的MinDW-w64目录下的gcc

img

配置【IntelliSense模式】为gcc-x64(legacy)。

img

其他的选项就不再修改。当修改完以上内容后,就在你打开的文件夹下生成了一个.vscode的文件夹,这个文件夹很重要,里边放的都是配置文件,以后是可以复用的。同时你看到在.vscode文件夹下生一个新的文件c_cpp_properties.json,这个文件中是关于C/C++的配置属性。

简单看一下文件c_cpp_properties.json文件的内容:

{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "windowsSdkVersion": "10.0.22621.0",
            "compilerPath": "C:/mingw64/bin/gcc.exe",
            "cStandard": "c17",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}

继续下一步。

4.4 创建执行任务:tasks.json

要编译代码,我们接下来,在终端中:配置任务,如下图:

img

在弹出的页面中,选择【C/C++:gcc.exe 生成活动文件】

img

这时就生成了一个tasks.json文件,这个文件是专门是用来执行代码的编译任务的。

img

这里我们可以简单看一下tasks.json文件内容

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: gcc.exe 生成活动文件",
            "command": "C:/mingw64/bin/gcc.exe",    //这里是指定编译器
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "${file}", //被编译的文件
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe"//指定生成的可执行文件的路径
            ],
            "options": {
                "cwd": "C:/mingw64/bin"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build",
            "detail": "编译器: C:/mingw64/bin/gcc.exe"
        }
    ]
}

4.5 编译+执行

4.5.1 编译

完成了上述的步骤,我们就准备好了所有的准备,接下来就可以编译并生成可执行程序了。

具体操作如下:

  1. 鼠标点在要编译的C语言代码中
  2. 然后执行【终端】->【运行生成任务】

img

如果一切正常的话,我们能看到【终端】提示,就说明编译成功了。

img

这是我们在资源管理器中也能看到生成的.exe文件了。

img

4.5.2 执行

按键盘的`ctrl+``快捷键,打开VSCode自带的终端,就可以运行代码了。

这时输入:.\可执行程序的名字,即可以执行代码,如下图:

img

到这里单个文件的编译和运行就已经成功搞定了。

4.6 再写一个代码怎么办?

方法1:

  1. 新建文件

如果你的代码还是单独的一个.c文件,在当前打开文件夹中就可以编写,那直接在当前打开的文件中新建.c源文件,编写代码,对当前的代码,直接执行【终端】->【运行生成任务】

img

  1. 然后,执行【终端】->【运行生成任务】

img

可执行程序生成:

img

按照4.4.2的方式运行即可。

方法2:

如果你要写的代码需要单独文件夹管理,那就再次打开新的文件夹来编写代码,编译和执行了。

比如:新的代码需要放在test_12_15的文件夹中管理

1. 新建文件夹test_12_15

img

2.打开文件夹选择test_12_15

img

  1. 将之前项目中的.vscode文件夹拷贝到test_12_15文件夹下

img

拷贝进去后资源管理器中的展示信息:

img

  1. 新建.c文件写代码

img

  1. 然后,执行【终端】->【运行生成任务】

img

img

这样就可以生成对应的可执行程序了。

按照4.4.2的方式运行即可。

4.7 一个工程中有多个.c文件需要编译怎么办?

假设我们有一个工程multiple_files,下面有多个文件需要编译,如下图:

img

代码如下:

img

要对这样的工程编译那怎么做呢?

1.打开文件夹

img

2.拷贝旧项目中的一份.vscode文件夹

拷贝放在文件夹下:

img

在VSCode中的展示效果:

img

3. 改造tasks.json并构建验证结果

要能够编译一个文件中的多个文件,需要定制tasks.json文件的内容

编译多个源代码文件:

  • 通过把"${file}"修改为"${workspaceFolder}\*.c",可以构建当前工作区中的所有C文件。
  • 通过把"${file}"修改为"${workspaceFolder}\*.cpp",可以构建当前工作区中的所有C++文件。

修改编译输出的文件名:

  • 通过把"${fileDirname}\\${fileBasenameNoExtension}.exe",修改为:"${workspaceFolder}\myProgram.exe",可以把编译输出的文件名硬编码为myProgram.exe,而不是与C文件同名。参看演示效果1。
  • 通过把"${fileDirname}\\${fileBasenameNoExtension}.exe",修改为:"${workspaceFolder}\${workspaceRootFolderName} .exe",可以把编译输出的文件名为:打开的工作区文件夹名字.exe。参看演示效果2。

演示效果1:

img

演示效果2:

img

4. 执行

按照4.4.2的方式运行即可。

5.VSCode 怎么写C++的代码呢?

5.1 配置 g++.exe 为编译器

鼠标点在.cpp 的源文件内部,按ctrl+shift+p快捷键,在弹出的界面中选择:【C/C++:编辑配置(UI)】,然后进入设置页面。

找到配置名称,这里我们再添加一个C++的配置,点击【添加配置】

img

输入:C++,当然你也可以根据自己的喜好命名,然后点击【确定】。

img

编译器路径,根据自己的mingw64的路径,g++.exe,我的环境如下所示:

img

这时在c_cpp_properties.json文件中就能看到一个名字叫:C++的配置。

img

5.2 生成C++源文件编译的tasks信息

5.2.1 方法一:

点中.cpp的文件,菜单中在终端下:配置任务,如下图:

img

在弹出的页面中,选择【C/C++:g++.exe 生成活动文件】

img

这时你就看到了,在tasks.json文件中多了一个g++的编译配置信息。如下图:

img

5.2.2 方法二:

vscode中打开C++项目,ctrl+shift+p 输入tasks然后根据提示选择 Tasks: Configure Task , 选择【C/C++:g++.exe 生成活动文件】。

{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "cppbuild",
			"label": "C/C++: gcc.exe 生成活动文件",
			"command": "D:\\APP\\VSCode\\plugins\\MingGW64\\mingw64\\bin\\gcc.exe",
			"args": [
				"-fdiagnostics-color=always",
				"-g",
				"${file}",
				"-o",
				// "${fileDirname}\\${fileBasenameNoExtension}.exe"       // 生成的exe与当前cpp文件并列且同名
				// "${fileDirname}\\exe\\${fileBasenameNoExtension}.exe", // 生成的exe在当前的cpp文件的目录下的exe目录下,且同名。
				"${workspaceFolder}\\exe\\${fileBasenameNoExtension}.exe",// 生成的exe在根目录下的exe文件夹下,与当前cpp文件名同名。 				
				"-lstdc++" // 编译时添加标准库的链接选项 ,使用g++编译器编译源文件my.cpp并将其链接到标准库。
			],
			"options": {
				"cwd": "${fileDirname}"
			},
			"problemMatcher": [
				"$gcc"
			],
			"group": "build",
			"detail": "编译器: D:\\APP\\VSCode\\plugins\\MingGW64\\mingw64\\bin\\gcc.exe"
		}
	]
}

5.3 编译C++ 的代码

完成了上述的步骤,我们就准备好了所有的准备,接下来就可以编译并生成可执行程序了。

具体操作如下:

  1. 鼠标点在要编译的C++代码中
  2. 然后执行【终端】->【运行生成任务】

img

弹出下面的界面,再选择:【C/C++: g++.exe 生成活动文件】

img

出现下图,就说明生成了可执行程序,就可以运行了

img

6.VSCode的调试

前面讲解的都是如何去编译代码,当我们生成一个可支持程序的时候,如果遇到了运行时的问题,我们就要想办法调试代码,那VSCode如何调试代码呢?接下来听我细细道来~

其实在VSCode上调试关键也是依赖一个配置文件的叫:launch.json,那我们看看怎么调试。

6.1 创建launch.json文件

首先点击调试,创建launch.json文件

img

然后选择【C++(GDB/LLDB)】:

img

产生了launch.json文件,并默认打开了

img

点击laucch.json文件的右下方的【添加配置】,选择【C/C++:(gdb)启动】

img

这是生成了默认的调试配置信息,需要自定一些信息

默认信息如下:

//默认生成的launch.json文件
{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 启动",
            "type": "cppdbg",
            "request": "launch",
            "program": "输入程序名称,例如 ${workspaceFolder}/a.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "/path/to/gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        }

    ]
}

img

  • 修改"program"为:"${workspaceFolder}\${workspaceRootFolderName} .exe",这个内容和tasks.json中的可支持文件的路径匹配。
  • 修改"miDebuggerPath"为:"C:\mingw64\bin\gdb.exe",这个路径根据自己实际放MinGW-w64的文件路径调整。
//修改后的launch.json文件
{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 启动",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}\\${workspaceRootFolderName} .exe", // 要和tasks.json中的args下的-o参数的输出的exe文件路径保持一致。
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "C:\\mingw64\\bin\\gdb.exe",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

6.2 开始调试

当launch.json文件生成后,我们只要打断点开始调试就行。

调试的快捷键和VS系列的IDE是一样的,如:

F9 - 打断点/取消断点

F5 - 启动调试

F11 - 逐语句调试

F10 - 逐过程调试

启动调试后,也是可以使用监视窗口查看变量等程序执行过程中的信息。

img

更多关于调试的细节,可以看,之前讲解的VS2019的调试视频,大同小异。

VS调试技巧:

https://www.bilibili.com/video/BV1oi4y1g7CF?p=28

https://www.bilibili.com/video/BV1oi4y1g7CF?p=29

7.如果你的电脑是Mac(苹果)电脑

如果你的电脑是Mac(苹果)电脑,不需要像windows电脑上需要安装MinGW-64,首先得在电脑上安装一下Mac环境的开发环境和路径。

命令如下:

xcode-select --install

img

输入命令后按回车,安装好后,就可以配置C/C++的编译选项了

按键盘的comand+shift+p快捷键,然后在弹出的输入栏中输入:C/C++,然后选择【C/C++:编辑配置(UI)】

img

就可以配置Mac系统上的编译选项了 如下:配置编辑器路径和IntellSense模式

img

其他的使用方式和前面的操作一模一样。

.vscode下的文件讲解

.vscode中各种替换变量的意思

  • ${workspaceFolder}:工作区根目录的绝对路径。

  • ${workspaceFolderBasename}:工作区根目录的名称(不包含路径)。

  • ${file}:当前打开文件的绝对路径。

  • ${fileDirname}:当前打开文件的目录的绝对路径。

  • ${fileBasename}:当前打开文件的名称(不包含路径)。

  • ${fileExtension}:当前打开文件的扩展名(包括点)。

  • ${env:VARIABLE_NAME}:系统环境变量VARIABLE_NAME的值。


  • ${fileBasenameNoExtension} 当前打开的文件的文件名,不包括路径和后缀名,不带后缀的可执行文件的名称

  • ${workspaceRoot} 当前打开的文件夹的绝对路径+文件夹的名字

  • ${workspaceRootFolderName} 当前打开的文件夹的名字

  • ${file}当前打开正在编辑的文件名,包括绝对路径,文件名,文件后缀名

  • ${relativeFile}从当前打开的文件夹到当前打开的文件的路径如 当前打开的是test文件夹,当前的打开的是main.c,并有test /first / second / main.c那么此变量代表的是 first / second / main.c

  • ${fileBasename} 当前打开的文件名+后缀名,不包括路径

  • ${fileDirname} 当前打开的文件所在的绝对路径,不包括文件名

  • ${fileExtname} 当前打开的文件的后缀名

  • ${cwd} the task runner's current working directory on startup 不知道怎么描述,这是原文解释,跟 cmd 里面的 cwd 是一样的

  • ${lineNumber} 当前打开的文件,光标所在的行数

g++的一些操作

g++ -g file1.cpp file2.cpp -o target

1. c_cpp_properties.json

{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${workspaceFolder}/**",
                "${workspaceFolder}/demo/include"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "compilerPath": "D:\\APP\\VSCode\\plugins\\MingGW64\\mingw64\\bin\\gcc.exe",
            "cStandard": "c17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "windows-gcc-x64"
        },
        {
            "name": "C++",
            "includePath": [
                "${workspaceFolder}/**",
                "${workspaceFolder}/demo/include"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "compilerPath": "D:/APP/VSCode/plugins/MingGW64/mingw64/bin/g++.exe",
            "cStandard": "c17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "gcc-x64",
            "mergeConfigurations": false,
            "browse": {
                "path": [
                    "${workspaceFolder}/**",
                    "${workspaceFolder}/demo/include"
                ],
                "limitSymbolsToIncludedHeaders": true
            }
        }
    ],
    "version": 4
}

windows下的vscode中的c_cpp_properties.json中的intelliSenseMode该选择哪个配置。

对于Windows下的VSCode,可以在c_cpp_properties.json文件中通过设置"intelliSenseMode"来配置IntelliSense模式。可选的模式有以下几种:

  1. "msvc-x64":使用Visual Studio的MSVC编译器和x64体系结构。
  2. "msvc-x86":使用Visual Studio的MSVC编译器和x86体系结构。
  3. "gcc-x64":使用GCC编译器和x64体系结构。
  4. "gcc-x86":使用GCC编译器和x86体系结构。
  5. "clang-x64":使用Clang编译器和x64体系结构。
  6. "clang-x86":使用Clang编译器和x86体系结构。

选择哪个模式应该取决于你的项目的编译器和目标平台。例如,如果你正在使用Visual Studio的MSVC编译器,并且你的目标平台是x64,你应该选择"msvc-x64"模式。

请注意,如果你的项目使用多种编译器或目标平台,请在c_cpp_properties.json文件中为每个编译器或目标平台设置一个不同的配置。

windows下使用mingw64,vscode中的c_cpp_properties.json中的intelliSenseMode该选择哪个配置。

在使用mingw64时,建议将intelliSenseMode配置为"gcc-x64"。这是因为mingw64d是以gcc为基础的,而"x64"则表示使用64位的编译器。因此,将intelliSenseMode配置为"gcc-x64"能够更好地适配mingw64d,并提供更好的代码提示和补全功能。

c_cpp_properties.json中的compilerPathInCppPropertiesJson属性怎么使用

compilerPathInCppPropertiesJson属性用于指定编译器的路径。如果在系统中安装了多个版本的编译器,需要使用该属性来指定所需的编译器。

c_cpp_properties.json文件中,添加以下代码:

Copy code

{    
"configurations": [
	{
		"name": "Win32",
        "intelliSenseMode": "msvc-x86",
        "compilerPathInCppPropertiesJson": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.28.29333\\bin\\Hostx64\\x64\\cl.exe",
        "includePath": [
        	"${workspaceFolder}/**"
        ],
        "defines": [],
        "compilerPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC\\14.28.29333\\bin\\Hostx64\\x64\\cl.exe",
        "cStandard": "c11",
        "cppStandard": "c++17"
    }
],
"version": 4 
}

上述代码中,compilerPathInCppPropertiesJson属性指定了编译器的路径,覆盖了compilerPath属性中指定的编译器路径。如果不指定compilerPathInCppPropertiesJson属性,则compilerPath属性中指定的编译器路径将被使用。

请注意,Windows下的文件路径使用反斜杠\,在JSON文件中需要使用两个反斜杠\\来转义。

2. launch.json---->for debug

tasks.json中的task的名字label一定要和launch.json中的preLaunchTask的名字一一对应。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",// 配置名称,将会在启动配置的下拉菜单中显示  
            "type": "cppdbg",      // 配置类型,这里只能为cppdbg 
            "request": "launch",   //请求配置类型,可以为launch(启动)或attach(附加)   
            // 默认:"program": "enter program name, for example ${workspaceFolder}/a.exe", 
            // program字段指向你的C++程序的可执行文件路径。${workspaceFolder}代表你正在工作的文件夹路径
            // "program": "${workspaceFolder}/${fileBasenameNoExtension}.exe",  // 将要进行调试的程序的路径
            // "program": "${workspaceRoot}/${fileBasenameNoExtension}.exe",    // 将要进行调试的程序的路径,workspaceRoot已被弃用,现改为workspaceFolder
            // "program": "${fileDirname}/${executorMap}.exe",                  // 你要调试的那个可执行文件所在的路径
            "program": "${workspaceFolder}/${fileBasenameNoExtension}.exe", 
            "args": [],           // 程序调试时传递给程序的命令行参数,一般设为空即可
            "stopAtEntry": false, // 设为true时程序将暂停在程序入口处,一般设置为false
            // "cwd": "${workspaceFolder}"  调试程序时的工作目录,一般为${workspaceRoot}即代码所在目录。workspaceRoot已被弃用,现改为workspaceFolder    
            "cwd": "${fileDirname}", // ${workspaceFolder} ${workspaceRoot}弃用了 ${fileDirname}
            "environment": [],
            "externalConsole": true, // 调试时是否显示控制台窗口,一般设置为true显示控制台    
            "MIMode": "gdb",
            // "miDebuggerPath": "path/to/gdb.exe",   // miDebugger的路径,注意这里要与MinGw的路径对应  
            "miDebuggerPath": "D:/APP/VSCode/plugins/MingGW64/mingw64/bin/gdb.exe",
            "preLaunchTask": "g++", // 调试会话开始前执行的任务,一般为编译程序,c++为g++, c为gcc。这里指的是task的名字。在调试前,执行tasks.json中对应的task名称下的定义的任务。
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb", //  gdb 启用整齐打印
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set Disassembly Flavor to Intel", // 将反汇编风格设置为 Intel
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        },

    ]
}

在launch.json中,configurations下的name属性

在launch.json中,configurations下的name属性是一个字符串,用于标识启动配置的名称。它可用于在VS Code的启动配置选择器中与其他配置进行区分,以便更轻松地选择要运行的配置。

建议为每个启动配置提供一个唯一的名称,以便在维护和调试时更方便地查看和管理配置。可以使用任何描述性名称,例如“调试服务器”、“运行测试”、“启动开发服务器”等。

注意,该名称不会影响启动配置本身的行为,只是用于标识和区分不同的配置。

在launch.json中,configurations下的type属性指定为cppvsdbg和cppdbg的区别

在launch.json中,configurations下的type属性指定为cppvsdbg和cppdbg的区别如下:

  • cppvsdbg是Visual Studio调试器(VS Code插件)的类型,它是基于Visual Studio的调试器实现的。它支持Windows平台和Linux平台上的本地调试,以及Windows Remote Debugger。
  • cppdbg是C++调试器的类型,它是基于LLDB或GDB调试器实现的。它支持多种平台和框架,例如:Linux,macOS,Windows,WSL,Android等。

因此,如果您正在开发使用Visual Studio编译器的C++应用程序,推荐使用cppvsdbg,因为它可以提供更好的调试体验和易用性。如果您正在使用其他编译器,或者需要跨平台调试C++程序,则应该使用cppdbg。

在launch.json中,configurations下的stopAtEntry属性

在launch.json中,configurations下的stopAtEntry属性是一个布尔值,用于指定是否在程序入口处停止调试器。如果设置为true,则调试器会在程序运行时立即中断。如果设置为false,则调试器会跳过入口处的中断点并继续执行程序。

通常情况下,调试器会在程序入口处设置一个中断点,这样我们可以在程序的早期阶段观察程序的状态并进行调试。但是,在某些情况下,我们希望调试器不要在入口处中断,而是一直执行到我们设置的下一个中断点处,这时就可以将stopAtEntry属性设置为false。

需要注意的是,stopAtEntry属性只在启动时生效,如果已经启动了调试会话,则需要手动停止并重新启动调试器才能使其生效。

在launch.json中,configurations下的cwd属性可以指定为哪些${}

在launch.json中,configurations下的cwd属性可以使用以下${}占位符来指定:

  • ${workspaceFolder}:工作区根目录的绝对路径。
  • ${workspaceFolderBasename}:工作区根目录的名称(不包含路径)。
  • ${file}:当前打开文件的绝对路径。
  • ${fileDirname}:当前打开文件的目录的绝对路径。
  • ${fileBasename}:当前打开文件的名称(不包含路径)。
  • ${fileExtension}:当前打开文件的扩展名(包括点)。
  • ${env:VARIABLE_NAME}:系统环境变量VARIABLE_NAME的值。

使用这些占位符可以创建更加通用的launch.json配置,以便在不同的环境中使用。例如,使用${workspaceFolder}可以将工作目录指定为工作区根目录,而不需要硬编码路径。

在launch.json中,configurations下的miDebuggerPath属性

是指定使用的调试器路径。这个属性通常用于指定使用的GDB或LLDB版本。如果不设置此属性,则VS Code将尝试自动检测可用的调试器路径。miDebuggerPath属性的示例值如下:

"configurations": [
	{
		"type": "cppdbg",
	    "request": "launch",
        "name": "Launch",
        "program": "${workspaceFolder}/example.exe",
        "miDebuggerPath": "/usr/bin/gdb"    // 指定使用的调试器路径
     }
]

上面的示例使用GDB作为调试器,并将其路径设置为"/usr/bin/gdb"。如果您使用的是LLDB,则需要将miDebuggerPath属性设置为lldb的路径。

launch.json中的configurations下的${executorMap}

在VS Code中,${executorMap}是任务执行器映射的变量。任务执行器映射是一个JavaScript对象,它定义了一系列可用于执行任务的执行器,并将它们与任务类型相关联。每个执行器负责一个特定的任务类型或一组任务类型。

${executorMap}变量可用于在任务定义中引用执行器映射的属性。例如,以下任务定义使用vscode.executeWorkspaceSymbolProvider执行器执行searchWorkspace任务:

{  
"label": "Search Workspace",
"type": "shell",
"command": "echo 'Searching workspace...'",
"problemMatcher": "$msCompile",
"group": {
	"kind": "build",
    "isDefault": true
 },
 "presentation": {
 	"reveal": "always",
    "panel": "new",
    "focus": true  
 },
 "isBackground": true,
 "execution": {
 	"executor": "${executorMap}.searchWorkspace"  
 } 
}

${executorMap}变量允许开发人员轻松访问和使用VS Code中定义的执行器映射,从而更好地控制任务的执行方式。

launch.json中的configurations下的program属性

在 Visual Studio Code 中,launch.json 文件用于配置调试环境。其中,configurations 下的 program 属性用于指定要运行的可执行文件的路径。如:${workspaceFolder}/bin/myprogram.exe

如果可执行文件不在工作区文件夹内,可以使用绝对路径来指定 program 属性。

3. settings.json

{
    "code-runner.executorMap": {
        "c": "cd $dir && gcc -std=c11 $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        // "cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        // "cpp": "cd $dir && chcp 65001 && g++ -std=c++14 $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        // "cpp": "cd $dir && chcp 65001 && g++ -std=c++14   *.cpp -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        // "cpp": "cd $dir && chcp 65001 && g++ -std=c++14 -I \"${workspaceFolder}/demo/include\"  $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        // "cpp": "cd $dir && chcp 65001 && g++ -std=c++14 -I \"${workspaceFolder}/demo/include\"  $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        // "cpp": "cd ${workspaceFolder}/demo/src && g++ -I${workspaceFolder}/demo/include -L${workspaceFolder}/demo/lib $fileName -o $fileNameWithoutExt.out  && ${workspaceFolder}/demo/src/$fileNameWithoutExt.out",
        // "cpp": "cd $dir && chcp 65001 && g++ -I${workspaceFolder}/demo/include -L${workspaceFolder}/demo/lib  $fileName -o $fileNameWithoutExt.out  && ${workspaceFolder}/demo/src/$fileNameWithoutExt.out",
        // "cpp": "cd $dir && chcp 65001 && g++ -I$workspaceFolder/demo/include -L$workspaceFolder/demo/lib $fileName -o $fileNameWithoutExt.out   && $dir$fileNameWithoutExt",
        // "cpp": "cd $dir && chcp 65001 && g++ -std=c++14 -I$workspaceFolder/demo/include -L$workspaceFolder/demo/lib $fileName -o a.exe && ./a.exe"
        // "cpp":"chcp 65001 && cd $dir && g++ -std=c++14 -I \"${workspaceFolder}/demo/include\" -L \"${workspaceFolder}/demo/lib\" $fileName -o $fileNameWithoutExt.exe && ${workspaceFolder}/demo/src/$fileNameWithoutExt.exe"
        // "cpp":"chcp 65001 && cd $dir && g++ -std=c++14 -I \"$workspaceFolder/demo/include\" -L \"$workspaceFolder/demo/lib\" $fileName -o $fileNameWithoutExt.exe && $dir$fileNameWithoutExt"
        // "cpp":"chcp 65001 && cd $dir && g++ -std=c++14 -I$workspaceFolder/demo/include -L$workspaceFolder/demo/lib $fileName -o $fileNameWithoutExt.exe && $dir$fileNameWithoutExt"
        // "cpp": "cd $dir && g++ $fileName -I${workspaceFolder}/demo/include ${workspaceFolder}/demo/src/*.cpp ${workspaceFolder}/demo/lib/*.cpp -o $fileNameWithoutExt && $dir$fileNameWithoutExt" 
        // "cpp": "cd $dir && g++ $fileName ${workspaceFolder}/demo/lib/*.cpp -I${workspaceFolder}/demo/include   -o $fileNameWithoutExt && $dir$fileNameWithoutExt" 
        // "cpp": "cd $dir && g++ $fileName ${workspaceFolder}/demo/lib/utils.cpp -I${workspaceFolder}/demo/include   -o $fileNameWithoutExt && $dir$fileNameWithoutExt" 
        // "cpp":"cd $dir && g++ -o $fileNameWithoutExt.exe ${workspaceFolder}/demo/src/test.cpp ${workspaceFolder}/demo/lib/utils.cpp -I ${workspaceFolder}/demo/include"
        
        // 不知什么原因我的${workspaceFolder}无法被解析为绝对路径,所以这里用相对路径进行一个替换。
        // cpp: cd $dir && g++ $filename xxx.cpp /xx/yyy.cpp -I xxx/include -o test|test.exe|test.out  && $dir$fileNameWithoutExt
        "cpp":"cd $dir && g++  $fileName ../lib/utils.cpp -I ../include -o $fileNameWithoutExt.exe && $dir$fileNameWithoutExt"

        // "cpp":"cd $dir ; if ($?) { g++ -I../include -L../lib -o test.out test.cpp } ; if ($?) { ./test.out }"
    },
    "files.autoGuessEncoding": true,
    "terminal.integrated.defaultProfile.windows": "PowerShell"

}
// Ctrl+Shift+P ----> Preferences:Open Workspace Settings(JSON)
{
    "files.associations": {
        "iostream": "cpp",
        "iomanip": "cpp",
        "ostream": "cpp",
        "initializer_list": "cpp",
        "array": "cpp",
        "atomic": "cpp",
        "*.tcc": "cpp",
        "cctype": "cpp",
        "clocale": "cpp",
        "cmath": "cpp",
        "cstdarg": "cpp",
        "cstddef": "cpp",
        "cstdint": "cpp",
        "cstdio": "cpp",
        "cstdlib": "cpp",
        "ctime": "cpp",
        "cwchar": "cpp",
        "cwctype": "cpp",
        "deque": "cpp",
        "unordered_map": "cpp",
        "vector": "cpp",
        "exception": "cpp",
        "algorithm": "cpp",
        "memory": "cpp",
        "memory_resource": "cpp",
        "optional": "cpp",
        "string": "cpp",
        "string_view": "cpp",
        "system_error": "cpp",
        "tuple": "cpp",
        "type_traits": "cpp",
        "utility": "cpp",
        "fstream": "cpp",
        "iosfwd": "cpp",
        "istream": "cpp",
        "limits": "cpp",
        "new": "cpp",
        "sstream": "cpp",
        "stdexcept": "cpp",
        "streambuf": "cpp",
        "typeinfo": "cpp"
    },
    // 更换终端类型: 打开 VS Code,点击左下角的“设置”图标,选择“设置”选项。在搜索栏中输入“terminal.integrated.defaultProfile.windows”
    "files.autoGuessEncoding": true,// 令VSCode自动选择合适编码打开文件
    "cmake.configureOnOpen": false,
    "code-runner.terminalCommand": "PowerShell -ExecutionPolicy RemoteSigned -Command \"& {Set-ExecutionPolicy RemoteSigned -force;chcp 65001;npm.cmd run-script %s}\"", // 防止乱码的一个设置。
    "code-runner.runInTerminal": true,
    "code-runner.executorMap": {
        // 把UTF-8文件保存在GBK代码文件,就让他默认和终端一样保持GBK编码。
        // "c": "cd $dir && gcc -fexec-charset=GBK $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        // "cpp": "cd $dir && g++ -fexec-charset=GBK $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        
        //"cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "cpp": "cd $dir && chcp 65001 && g++ $fileName -I 'C:\\Users\\Tomorrow\\Desktop\\C\\part4\\code\\functions\\demo\\headers' -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "command": "powershell.exe -Command \"& {&'${workspaceFolder}\\${fileBasenameWithoutExtension}.exe'}\"", // ${fileBasenameWithoutExtension}表示当前打开文件的文件名(不包括扩展名)
        "args": [],
        "cwd": "${workspaceFolder}",
        "terminal": "integratedTerminal"
    },
    
    /*
        第一次尝试 /K chcp 65001 >nul,失败
         此项已弃用,配置默认 shell 的新推荐方法是在 `#terminal.integrated.profiles.windows#` 中创建一个终端配置文件,并将其配置文件名称设置为 `#terminal.integrated.defaultProfile.windows#` 中的默认值。此操作当前将优先于新的配置文件设置,但将来会发生更改。(2)
         "terminal.integrated.shellArgs.windows": {"/K chcp 65001 >nul"},
         "terminal.integrated.shellArgs.windows": ["-NoExit", "/c", "chcp 65001"],
         "terminal.integrated.fontFamily": "Lucida Console",
         "editor.fontSize": 16,
    */

    /*
        第二次尝试 /K chcp 65001 >nul,失败
        "terminal.integrated.profiles.windows": {
            "PowerShell": {
                "path": "C:/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe",
                "args": [
                    "-NoExit", "/c", "chcp 65001"
                ],
                
                //    /K chcp 65001或-NoExit", "/c", "chcp 65001的含义是在运终端时候将编码设置为65001
                
            },
        },
        "terminal.integrated.defaultProfile.windows": "PowerShell",
    */

    /*
        第三次尝试 /K chcp 65001 >nul,失败
        "terminal.integrated.profiles.windows": {
            "PowerShell -NoProfile": {
                "source": "PowerShell",
                "args": ["-NoProfile"]
            }
        },
        "terminal.integrated.profiles.linux": {
            "zsh (login)": {
                "path": "zsh",
                "args": ["-l"]
            }
        }
    */

    /*
        第四次尝试
        "terminal.integrated.profiles.windows": {
            "Command Prompt": {
                "path": "C:\\Windows\\System32\\cmd.exe", // path直接指向 shell 可执行文件
                "args": ["-NoExit", "/K", "chcp 65001"]
            },
            "PowerShell": {
                "source": "PowerShell", // source可以让 VS Code 检测安装PowerShell或Git Bash
                "args": ["-NoExit", "/C", "chcp 65001"]
            }
        },
    */
    
    /*
        第五次尝试  chatgpt回答
        "terminal.integrated.profiles.windows": {
            "PowerShell": {
            "path": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
            "args": [
                "-NoExit",
                "/c",
                "chcp 65001"
            ]
            },
            "Command Prompt": {
            "path": "C:\\Windows\\System32\\cmd.exe",
            "args": [
            //   "/K",
            //   "chcp 65001 >nul"
            ["-NoExit", "/K", "chcp 65001"]
            ]
            }
        },
        "terminal.integrated.defaultProfile.windows": "Command Prompt",
        // 注意,你需要重新启动VSCode才能使新的配置生效。
    */
    
    /*
        第六次尝试  chatgpt回答
        "terminal.integrated.shellArgs.windows": [
            "-ExecutionPolicy", "Bypass",
            "-NoLogo",
            "-NoProfile",
            "-Command", "chcp 65001"
        ]
    */



}

code runner 运行c++程序出现错误找不到头文件,但是此项目可以直接再vscode中终端选项下运行生成任务ctrl+shift+b。 我的项目${workspaceFolder}/demo/include下是头文件,${workspaceFolder}/demo/lib下是头文件的实现文件.cpp,${workspaceFolder}/demo/src是源文件.cpp.如何指定头文件的路径和${workspaceFolder}/demo/lib下的.cpp文件的路径以方便coderunner编译运行

你可以在 Code Runner 的配置中添加编译器选项,来指定头文件和实现文件的路径。例如:

"code-runner.executorMap": {
	"cpp": "cd $dir && g++ -I${workspaceFolder}/demo/include ${workspaceFolder}/demo/src/*.cpp ${workspaceFolder}/demo/lib/*.cpp -o $fileNameWithoutExt && $dir$fileNameWithoutExt" 
	// "cpp": "cd $dir && g++ -I${workspaceFolder}/demo/include ${workspaceFolder}/demo/src/*.cpp ${workspaceFolder}/demo/lib/*.cpp -o $fileNameWithoutExt && $dir$fileNameWithoutExt && start cmd \"/k;$fileNameWithoutExt\" " // 启动cmd
}

这个配置中 -I${workspaceFolder}/demo/include 指定了头文件的路径,${workspaceFolder}/demo/src/*.cpp 指定了源文件的路径,${workspaceFolder}/demo/lib/*.cpp 指定了实现文件的路径。在编译命令中同时指定源文件和实现文件,可以让编译器将它们编译成一个可执行文件。注意,如果你的实现文件中包含了头文件,编译器会自动找到它们。

在上面的命令中,我们使用*通配符来匹配所有的源文件和实现文件,如果你只有一个源文件和一个实现文件,可以将它们直接指定。

注意,代码中的路径是 Linux 风格的路径,如果你的操作系统是 Windows,需要将路径的分隔符修改为反斜杠\

Code-runner: Executor Map设置针对不同文件夹下的cpp文件采用不同的g++编译方式

Code-runner插件的Executor Map功能可以让你为不同文件类型和文件夹设置不同的编译、运行方式。对于针对不同文件夹下的cpp文件采用不同的g++编译方式的需求,你可以按照以下步骤进行设置:

在VS Code中打开要编辑的文件夹(注意是文件夹,而不是单个文件)。

打开VS Code的用户设置(File -> Preferences -> Settings),搜索“code-runner.executorMap”。

在executorMap下新建一个记录,key值为“/your_folder//*.cpp”,其中“your_folder”为你想要设置的文件夹名。

将value值设置为你想要运行的命令,例如:

"code-runner.executorMap": { "/folder1//.cpp": "g++ -std=c++11 $fullFileName -o $fileNameWithoutExt && ./$fileNameWithoutExt", "/folder2//.cpp": "g++ -std=c++14 $fullFileName -o $fileNameWithoutExt && ./$fileNameWithoutExt" }

在这个例子中,我们为不同的文件夹设置了不同的编译方式:folder1下的cpp文件采用C++11标准编译,folder2下的cpp文件采用C++14标准编译,并且都进行了运行操作。

保存设置,重新打开你想要编译运行的cpp文件所在的文件夹。当你运行代码时,Code-runner会自动采用你所设置的编译方式对代码进行编译并执行。

需要注意的是,在设置executorMap时,你需要使用“/your_folder//*.cpp”这种通配符方式来指定文件夹路径。其中,“”表示匹配任意深度的子文件夹,“your_folder”表示你要设置的文件夹名,“/*.cpp”表示匹配所有后缀为.cpp的文件。

code-runner.executorMap中如何设置两套g++规则

要实现此功能,您可以在Code-runner设置中打开Executor Map选项卡,并添加以下内容:

{
    "cmd": "g++",
    "args": [
        "${file}",
        "-o",
        "${fileDirname}/${fileBasenameNoExtension}",
        "-std=c++11",
        "-Wall"
    ],
    "workingDir": "${fileDirname}",
    "language": "c++"
},
{
    "cmd": "g++",
    "args": [
        "${file}",
        "-o",
        "${fileDirname}/${fileBasenameNoExtension}",
        "-std=c++14",
        "-Wall"
    ],
    "workingDir": "${fileDirname}",
    "language": "c++14",
    "problemMatcher": {
        "owner": "cpp",
        "fileLocation": "absolute",
        "pattern": {
            "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
            "file": 1,
            "line": 2,
            "column": 3,
            "severity": 4,
            "message": 5
        }
    }
}

在这个例子中,我们为c++和c++14分别设置了两个Executor Map。第一个Executor Map使用标准c++11来编译cpp文件,而第二个Executor Map则使用标准c++14来进行编译。请注意,我们添加了一个problemMatcher来捕获编译时的警告和错误信息。

如果您希望针对不同的文件夹设置不同的Executor Map,请使用文件夹路径作为键来添加映射。例如:

{
    "/path/to/folder1": {
        "cmd": "g++",
        "args": [
            "${file}",
            "-o",
            "${fileDirname}/${fileBasenameNoExtension}",
            "-std=c++11",
            "-Wall"
        ],
        "workingDir": "${fileDirname}",
        "language": "c++"
    },
    "/path/to/folder2": {
        "cmd": "g++",
        "args": [
            "${file}",
            "-o",
            "${fileDirname}/${fileBasenameNoExtension}",
            "-std=c++14",
            "-Wall"
        ],
        "workingDir": "${fileDirname}",
        "language": "c++14",
        "problemMatcher": {
            "owner": "cpp",
            "fileLocation": "absolute",
            "pattern": {
                "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
                "file": 1,
                "line": 2,
                "column": 3,
                "severity": 4,
                "message": 5
            }
        }
    }
}

在这个例子中,我们为文件夹路径“/path/to/folder1”和“/path/to/folder2”分别设置了不同的Executor Map。如果打开的文件在“/path/to/folder1”中,则使用第一个Executor Map进行编译。如果打开的文件在“/path/to/folder2”中,则使用第二个Executor Map进行编译。

vscode中的settings.json中的code-runner.executorMap设置针对不同文件夹下的cpp文件采用不同的g++编译方式

可以使用 "code-runner.executorMapByFileDirectory" 来实现。在 settings.json 中添加如下代码:

"code-runner.executorMapByFileDirectory": {
    "/path/to/directory1": {
        "cpp": "cd $dir && g++ -o $fileNameWithoutExt $fileName"
    },
    "/path/to/directory2": {
        "cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt"
    }
}

其中,"/path/to/directory1""/path/to/directory2" 分别表示不同的目录。在每个目录下,指定 "cpp" 文件的编译方式。"$dir" 表示当前文件所在的目录,"$fileName" 表示当前文件的完整文件名(包括扩展名),"$fileNameWithoutExt" 表示当前文件的文件名(不包括扩展名)。在编译命令中可以根据需要添加其他参数,比如 -std=c++11 等。

注意:如果你已经在 settings.json 中指定了 "code-runner.executorMap" 的值,则它将覆盖 "code-runner.executorMapByFileDirectory" 中的设置。因此,要使设置生效,请确保在 "code-runner.executorMapByFileDirectory" 中指定所有需要的设置。

code-runner.executorMap中的cwd

code-runner.executorMap 中的 cwd 属性是 code-runner 插件提供的一个可选属性。它用于指定编译、运行代码的工作目录,即执行命令的起始目录。使用 cwd 属性后,code-runner 插件会在运行命令时先进入指定的工作目录,再执行编译、运行等操作。

以下是示例代码:

"code-runner.executorMap": {
    "cpp": {
        "command": "g++ $fileName -o $fileNameWithoutExt && ./fileNameWithoutExt",
        "cwd": "$dir"
    }
}

在此示例中,我们使用 cwd 属性将工作目录设置为 $dir,即当前文件所在目录。这样,当执行 g++ 命令编译当前文件时,会在当前文件所在目录执行。然后使用编译后的可执行文件 ./fileNameWithoutExt 运行程序。

需要注意的是,cwd 属性并不是必须的,如果没有设置 cwd 属性,code-runner 插件会默认使用当前文件所在目录作为工作目录,因此可以按照自己的需求设置使用或不使用此属性。

vscode 中设置c++程序的coderunner,在settings.json中的code-runner.executorMap中该如何设置才能实现运行根目录下的demo1和demo2用两套不一样的g++编译规则,

已知 ${workspaceFolder}/demo1/include下存放的是demo1的头文件, ${workspaceFolder}/demo1/lib下存放的是demo1的库文件, ${workspaceFolder}/demo1/src下存放的是demo1的源文件, ${workspaceFolder}/demo2/inc下存放的是demo2的头文件, ${workspaceFolder}/demo2/lib下存放的是demo2的库文件, ${workspaceFolder}/demo/source下存放的是demo2的源文件。

vscode 中设置c++程序的coderunner,在settings.json中的code-runner.executorMap中该如何设置才能实现运行根目录下的demo1和demo2用两套不一样的g++编译规则,已知${workspaceFolder}/demo1/include下存放的是demo1的头文件,${workspaceFolder}/demo1/lib下存放的是demo1的库文件,${workspaceFolder}/demo1/src下存放的是demo1的源文件,${workspaceFolder}/demo2/inc下存放的是demo2的头文件,${workspaceFolder}/demo2/lib下存放的是demo2的库文件,${workspaceFolder}/demo/source下存放的是demo2的源文件。

GPT3.5回答: 可以在settings.json中的code-runner.executorMap中设置以下内容来实现:

``` "code-runner.executorMap": { "cpp-demo1": "cd $dir && g++ -std=c++11 -I${workspaceFolder}/demo1/include -L${workspaceFolder}/demo1/lib -o $fileNameWithoutExt.out $fileName -lxxx", "cpp-demo2": "cd $dir && g++ -std=c++11 -I${workspaceFolder}/demo2/inc -L${workspaceFolder}/demo2/lib -o $fileNameWithoutExt.out $fileName -lyyy", }, "code-runner.executorMapByFileExtension": { ".cpp": "cpp-demo1", ".demo2.cpp": "cpp-demo2" }, ```

其中,code-runner.executorMap定义了两个编译规则:cpp-demo1和cpp-demo2,分别对应demo1和demo2的编译规则。其中,-I参数指定头文件路径,-L参数指定库文件路径,-l参数指定链接的库文件名(xxx和yyy为实际的库文件名)。$dir表示当前文件所在目录,$fileName表示当前文件名,$fileNameWithoutExt表示当前文件名去除扩展名后的部分。

code-runner.executorMapByFileExtension定义了文件扩展名和编译规则的对应关系。".cpp": "cpp-demo1"表示扩展名为.cpp的文件使用cpp-demo1编译规则编译,".demo2.cpp": "cpp-demo2"表示扩展名为.demo2.cpp的文件使用cpp-demo2编译规则编译。

4. tasks.json---->for build before debug

默认是编译单个文件,如果需要编译多个文件需要自己去配置launch.json和tasks.json这俩文件。

// Ctrl+Shift+P ---->Tasks:Configure Task ---->C/C++: gcc.exe build active file
/*
    tasks.json主要作用是添加构建(编译、链接等)任务。也就是说要想编译、链接生成可执行文件,必须要配置这个文件。当然了,后面说	  到的CMakeLists.txt可以替代这个文件。
*/
{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: gcc.exe build active file",
            "command": "D:\\APP\\VSCode\\plugins\\MingGW64\\mingw64\\bin\\g++.exe", // 这里还可以指定为make
            "args": [
                "-fdiagnostics-color=always",
                // "-I${fileDirname}/headers", 
                "-g",// -g 表示为了gdb调试用;
                "${file}",  // 调试模式的话需要设置 tasks.json中的 ${file} 改成 “${fileDirname}/*.cpp” 									// ${fileDirname}/*.cpp"用于批量编译文件夹中所有文件,也可以指定为具体的某个文件就是不够灵活了那样就。
                "-o",// -o 表示将生成的执行程序 可以为.exe文件可以为.out文件可以不带后缀。
                // "${fileDirname}\\${fileBasenameNoExtension}.exe", // 生成的exe与当前cpp文件并列且同名
				// "${fileDirname}\\bin\\${fileBasenameNoExtension}.exe",// 生成的exe在当前的cpp文件的目录下的bin目录下,且同名。
				// "${workspaceFolder}/demo/bin/${fileBasenameNoExtension}.exe",// 生成的exe在根目录下的bin文件夹下,与当前cpp文件名同名。 	
                "${fileDirname}/${fileBasenameNoExtension}.exe",
                // "-I", "${workspaceFolder}/headers",// -I headers 表示将当前目录下的 headers 子目录添加到搜索路径中 "-I"指定链接库
                // "-std=c++11"
                // "-Wall",//显示警告信息
                 "-finput-charset=UTF-8",//输入编译器文本编码 默认为UTF-8
                 "-fexec-charset=GBK"    //编译器输出文本编码 自行选择
            ],
            // "environment": [],
            // "externalConsole": true,
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            // "command":"echo '<test>' ${fileDirname}", // 终端----运行任务即可将值打印出来。
            "detail": "Task generated by Debugger."
        },
       
    ],
    "version": "2.0.0"
}
// Ctrl+Shift+P ---->Tasks:Configure Task ---->C/C++: gcc.exe build active file
/*
    tasks.json主要作用是添加构建(编译、链接等)任务。也就是说要想编译、链接生成可执行文件,必须要配置这个文件。当然了,后面说	  到的CMakeLists.txt可以替代这个文件。
*/
{
    "optinons":{
        "cwd":"${workspaceFolder}/build"// 提前进入build目录
	},
    "tasks": [
        {
            // cmake编译上级目录
            "type":"shell",
            "label":"cmake",
            "command":"cmake",
            "args":[
                ".."
            ]
        },
        {
            // 在windows下执行windows下的make命令:mingw32-make.exe
            "label":"make",
            "group":{
                "kind":"build",
                "isDefault":true
            },
            // "command":"make",
            "command":"mingw32-make.exe",
            "args":[
                
            ]
        },
        {
            // 先cmake上级目录,再执行make
            "label":"Build",  // 然后去launch.json中设置====>"preLaunchTask":"Build"
            "dependsOn":[
                "cmake",
                "make"
            ]
        }
    ],
    
    "version": "2.0.0"
}
// Ctrl+Shift+P ---->Tasks:Configure Task ---->C/C++: gcc.exe build active file

/*
    tasks.json主要作用是添加构建(编译、链接等)任务。也就是说要想编译、链接生成可执行文件,必须要配置这个文件。当然了,后面说到的CMakeLists.txt可以替代这个文件。
*/

/*
vscode默认生成的tasks.json如下所示:
{
	"tasks": [
		{
			"type": "shell",#指定编译环境为shell
			"label": "C/C++: gcc-7 build active file",#指定编译的标签
			"command": "/usr/bin/gcc-7",#指定编译的gcc
			"args": [#指定编译的参数
				"-g",
				"${file}",#指定需要编译的文件
				"-o","${fileDirname}/${fileBasenameNoExtension}",#指定生成的bin文件的相关路径
			],
			"options": {#一些额外的选项
				"cwd": "${workspaceFolder}"
			},
			"problemMatcher": ["$gcc"],
			"group": {"kind": "build","isDefault": true}
		}
	],
	"version": "2.0.0"
}

自动生成的tasks.json按照vscode的官方文档介绍其实是只支持编译单个文件的。
如果需要通过一个task文件编译多个文件,那么就需要对tasks下面的args参数做一下修改。
实际上,tasks下面的args参数可以认为是我们在使用命令行进行编译时所执行的命令,修改为如下就可以完成多文件编译:
{
    "tasks": [
        {
            "type": "shell",
            "label": "C/C++: gcc-7 build active file",
            "command": "/usr/bin/gcc-7",
            "args": [
                "-g",
                "${workspaceFolder}/src/*.c",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}",
                "-I",
                "${workspaceFolder}/include",
                "-pthread"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": ["$gcc"],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ],
    "version": "2.0.0"
}

*/


{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: gcc.exe build active file",
            "command": "D:\\APP\\VSCode\\plugins\\MingGW64\\mingw64\\bin\\g++.exe",
            "args": [
                "-fdiagnostics-color=always",
                // "-I${fileDirname}\\part4\\code\\functions\\demo\\headers",//添加
                "-g",// -g 表示为了gdb调试用;
                "${file}", // 调试模式的话需要设置 tasks.json中的 ${file} 改成 “${fileDirname}\\*.cpp”
                //"C:\\Users\\Tomorrow\\Desktop\\C\\part4\\code\\functions\\demo\\sources\\test.cpp",
                "-o",// -o 表示将生成的执行程序改名为其他
                "${fileDirname}\\\\${fileBasenameNoExtension}.exe",
                // "-I", // -I headers 表示将当前目录下的 headers 子目录添加到搜索路径中
                // "${workspaceFolder}/headers",
                 "-finput-charset=UTF-8",//输入编译器文本编码 默认为UTF-8
                 "-fexec-charset=GBK"    //编译器输出文本编码 自行选择
            ],
            // "environment": [],
            // "externalConsole": true,
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            // "command":"echo '<test>' ${fileDirname}", // 终端----运行任务即可将值打印出来。
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}

.vscode中的tasks.json中的type属性

tasks.json是用来配置VS Code中的任务(task)的文件,其中包含了任务的名称、命令、环境变量等信息。而tasks.json中的type属性指定了任务的类型。

常见的task类型有:

  • shell:在终端中运行shell命令。
  • process:在后台运行进程。
  • npm:运行npm命令。
  • grunt:运行grunt任务。
  • gulp:运行gulp任务。

除了常见的类型外,也可以自定义类型,例如:

"type": "custom",
"command": "mytask",
"problemMatcher": []

在这个例子中,type被定义为"custom",command指定了要运行的命令,problemMatcher用于在任务执行期间匹配错误和警告等信息。

总的来说,type属性定义了任务的类型,帮助VS Code识别如何执行和显示该任务。

.vscode中的tasks.json中的type属性指定为cppbuild什么意思

在VS Code中,"cppbuild"是一种自定义的任务类型,用于执行具有C++源代码的项目的构建操作。通过定义"cppbuild"类型的任务,可以方便地执行构建操作,例如编译源代码、链接二进制文件等操作。

在tasks.json文件中,定义了"cppbuild"类型的任务后,还需要指定任务所需要执行的命令、工作目录、环境变量等属性,使其能够按照预期执行。这些属性可以在tasks.json文件中进行配置,例如:

{    
	"label": "Build C++",
    "type": "cppbuild",
    "command": "make",
    "args": [
    	"-C",
        "${workspaceFolder}/build"
     ],
     "problemMatcher": {
     	"owner": "cpp",
        "fileLocation": [
        	"relative",
            "${workspaceFolder}/build"
          ],
          "pattern": {
          	"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
            "file": 1,
            "line": 2,
            "column": 3,
            "severity": 4,
            "message": 5
          }
     }
}

在这个例子中,定义了一个名为"Build C++"的任务,类型为"cppbuild",使用了"make"命令进行构建,在参数中指定了工作目录为"${workspaceFolder}/build",同时还定义了一个问题匹配器,用于在任务执行过程中匹配错误和警告等信息。

问题

${workspaceFolder}/demo1/include 存放头文件 ${workspaceFolder}/demo1/lib 存放库文件 ${workspaceFolder}/demo1/src 存放源文件main.cpp ${workspaceFolder}/demo2/headers 存放头文件 ${workspaceFolder}/demo2/lib 存放库文件 ${workspaceFolder}/demo2/sources 存放源文件main.cpp demo1和demo2中都是c++的项目,我想两个目录下的main.cpp都能右键运行coderunner 如何设置code-runner.executorMap

在 VS Code 的设置中,找到 code-runner.executorMap 属性,将其设为以下内容:

"code-runner.executorMap": {
    "cpp": "if [ -d ${workspaceFolder}/demo1 ]; then cd ${workspaceFolder}/demo1 && g++ -o ${workspaceFolder}/demo1/bin/${fileBasenameNoExtension} ${file} -I ${workspaceFolder}/demo1/include -L ${workspaceFolder}/demo1/lib -lmylib && ${workspaceFolder}/demo1/bin/${fileBasenameNoExtension}; elif [ -d ${workspaceFolder}/demo2 ]; then cd ${workspaceFolder}/demo2 && g++ -o ${workspaceFolder}/demo2/bin/${fileBasenameNoExtension} ${file} -I ${workspaceFolder}/demo2/headers -L ${workspaceFolder}/demo2/lib -lmylib && ${workspaceFolder}/demo2/bin/${fileBasenameNoExtension}; else g++ -o ${fileBasenameNoExtension} ${file} && ./${fileBasenameNoExtension}; fi"
}

这里定义了一个命令,它会先判断当前打开的文件所在的文件夹是不是 demo1 或 demo2,如果是,则进入到对应的项目目录中,使用 g++ 命令编译源文件,并将生成的目标文件存放在 bin 目录中。同时,我们指定了头文件和库文件的路径,以便编译器能够正确地找到它们。最后,我们使用生成的可执行文件运行程序。

如果不在 demo1 或 demo2 目录中,则使用默认的编译命令 g++ -o ${fileBasenameNoExtension} ${file} ,生成的可执行文件会保存在源文件所在的目录中,并直接运行程序。

请注意,这种方法可能存在一些问题,例如如果两个项目中都有相同的头文件或库文件,编译器可能会出现冲突。建议将两个项目中的头文件和库文件保持独立,避免冲突。

可是往上查到的是

VSCode中使用Code runner配置C++单文件多文件编译,并自动创建bin文件夹把.exe文件放入其中
https://blog.csdn.net/Dayrker/article/details/117702784?ops_request_misc=&request_id=4b8a6cc67fa9419aae06cded38d55985&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2~all~koosearch~default-7-117702784-null-null.142^v88^koosearch_v1,239^v2^insert_chatgpt&utm_term=code-runner.executorMap%E6%A0%B9%E6%8D%AE%E4%B8%8D%E5%90%8C%E7%9B%AE%E5%BD%95%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%90%8C%E7%9A%84g%2B%2B%E7%BC%96%E8%AF%91%E6%96%B9%E5%BC%8F&spm=1018.2226.3001.4187


"code-runner.executorMap": {
	// 多文件cpp编译
	"cpp": "(if not exist $dirbin (mkdir $dirbin)) && cd $dir && g++ *.cpp -o .\\bin\\$fileNameWithoutExt && $dirbin\\\\$fileNameWithoutExt", 
	// 此为单文件编译:
	"cpp": "(if not exist $dirbin (mkdir $dirbin)) && cd $dir && g++ $fileName -o .\\\bin\\\\$fileNameWithoutExt && $dirbin\\$fileNameWithoutExt", 
}

// 我根据上面的编写:
"cpp":"(if exists $workspaceRoot/part4/code/6_function/demo ) && cd $dir && g++  $fileName  -I ../include ../lib/*.cpp -o $fileNameWithoutExt.exe && $dir$fileNameWithoutExt"

基于g++命令

  • g++编译单个文件,生成带调试信息的可执行文件、并调试
g++ -g main.cpp -o my_single_swap
  • g++编译多个文件,生成带有调试信息的可执行文件、并调试
g++ -g main.cpp swap.cpp -o my_multi_swap

基于cmake

  • 编写最简单的CMakeLists.txt
project(项目名)
add_executable(要生成的可执行程序,需要用到的cpp文件1,需要用到的cpp文件2)

进行多文件编译、调试

mkdir build
cd build
# 如果在电脑上安装了vs,可能会调用微软MSVC编译器,使用(cmake -G "MinGW Makefiles" ..)代替( cmake ..) 即可
# 仅第一次使用cmake时使用(cmake -G "MinGW Makefiles" .. )后面可使用( cmake  ..)
cmake ..
mingw32-make.exe

8. Todo list

g++编译${workspaceFolder}/demo/src/test.cpp,头文件在${workspaceFolder}/demo/include/utils.h,头文件的实现文件在${workspaceFolder}/demo/lib/utils.cpp。要编译为test.exe该如何编译

可以使用以下命令编译为test.exe:

g++ -o test.exe ${workspaceFolder}/demo/src/test.cpp ${workspaceFolder}/demo/lib/utils.cpp -I ${workspaceFolder}/demo/include

其中:

-o选项指定输出的可执行文件名为test.exe。
${workspaceFolder}/demo/src/test.cpp是源代码文件的路径。
${workspaceFolder}/demo/lib/utils.cpp是头文件的实现文件的路径。
-I选项指定头文件所在的目录为${workspaceFolder}/demo/include。
如果程序中使用了其他的库文件,则需要使用-L选项指定库文件的搜索路径,并使用-l选项指定需要链接的库文件。例如,如果使用了OpenCV库文件,则需要添加以下两个选项:

-L/path/to/opencv/lib -lopencv_core

其中,/path/to/opencv/lib为OpenCV库文件所在的路径,-lopencv_core指定链接OpenCV的core库。

总的来说,编译指令可能会根据实际情况而有所变化,但是以上命令可以提供一个基本的指令模板。

PS C:\Users\Tomorrow\Desktop\C2\demo\src> g++ -o test.exe ${workspaceFolder}cd "c:\Users\Tomorrow\Desktop\C2\demo\src" ; if ($?) { g++ -o test.exe ${workspaceFolder}/demo/src/test.cpp ${workspaceFolder}/demo/lib/utils.cpp -I ${workspaceFolder}/demo/include }/demo/src/test.cpp ${workspaceFolder}/demo/lib/utils.cpp -I ${workspaceFolder}/demo/include g++.exe: error: cd: No such file or directory/demo/src/test.cpp : 无法将“/demo/src/test.cpp”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。所在位置 行:1 字符: 218+ ... ls.cpp -I ${workspaceFolder}/demo/include }/demo/src/test.cpp ${works ...+ ~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (/demo/src/test.cpp:String) [], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException

It seems like there are some errors with the command you are trying to run. The error message states that it cannot recognize "/demo/src/test.cpp" as a valid cmdlet or program. This could be because the path specified is incorrect or there is a typo in the command.

One possible solution is to replace the forward slash (/) with a backslash () in the path of the test.cpp file. Alternatively, you can try navigating to the src folder using the cd command before running the g++ command.

Here's an example command you can try:


Copy code
cd "c:\Users\Tomorrow\Desktop\C2\demo\src\"
if ($?) { 
    g++ -o test.exe test.cpp ../lib/utils.cpp -I ../include   <----重点
}
This should navigate to the src folder and compile the test.cpp file along with utils.cpp while including the header files from the include folder.

.vscode目录下的settings.json中的"code-runner.executorMap"属性值可以识别${workspaceFolder}吗?

是的,VS Code中的${workspaceFolder}会被解析为当前打开的工作区文件夹的路径。因此,可以在.vscode目录下的settings.json文件中使用${workspaceFolder}来指定code-runner.executorMap属性的值。

.vscode目录下的settings.json中的"code-runner.executorMap"属性值包含了${workspaceFolder},${workspaceFolder}能被解析出来吗?为什么我的控制台输出路径错了,最后我将"code-runner.executorMap"属性值改为了相对路径才编译成功。

如果在code-runner.executorMap属性值中包含${workspaceFolder},则在使用该属性值时,${workspaceFolder}将会被解析为当前工作区文件夹的绝对路径。因此,如果您在code-runner.executorMap属性中使用了${workspaceFolder},并且在控制台输出中看到了路径错误的情况,可能是因为您的${workspaceFolder}所代表的绝对路径与您预期的不同。

在这种情况下,您可以尝试使用相对路径来替代${workspaceFolder}。例如,如果您在当前工作区文件夹中有一个名为src的目录,您可以将code-runner.executorMap的值设置为:

jsonCopy code

{    "cpp": "cd src && g++ -o main ${file} && ./main" }

这样,当您运行C++代码时,Code Runner将先进入src目录,然后编译并执行您的代码。

请注意,如果您确实需要使用${workspaceFolder},则应确保它确实代表了您所期望的工作区文件夹的绝对路径。如果${workspaceFolder}解析出来的路径不正确,则可能需要检查工作区文件夹的配置或VS Code配置文件中的其他属性值。

9. VSCode的设置

10. VSCode中如何使用CMake编译代码

windows下的vscode中如何使用cmake,请按照步骤从新建工程到,写一个c++头文件和源文件分别放在不同目录下的小案例,用cmake编译项目,展示出CMakeLists.txt中的内容,展示出makefile中的内容。必要时可以展示settings.json,launch.json,tasks.json文件。

以下是使用CMake在Windows下建立一个C++项目的步骤:

  1. 安装CMake

在CMake官网上下载并安装CMake。

  1. 新建工程目录

在任意位置新建一个目录,作为工程目录。

  1. 新建头文件和源文件

在工程目录下新建一个名为include的目录,用于存放头文件。在include目录下新建一个名为myheader.h的头文件,内容如下:

#ifndef MYHEADER_H
#define MYHEADER_H

void printHello();

#endif

在工程目录下新建一个名为src的目录,用于存放源文件。在src目录下新建一个名为mysource.cpp的源文件,内容如下:

#include <iostream>
#include "myheader.h"

void printHello()
{
    std::cout << "Hello, CMake!" << std::endl;
}
  1. 新建CMakeLists.txt文件

在工程目录下新建一个名为CMakeLists.txt的文件,内容如下:

cmake_minimum_required(VERSION 3.0)

project(myproject)

set(CMAKE_CXX_STANDARD 11)

# 设置头文件搜索路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

# 添加源文件
add_executable(myexe ${CMAKE_CURRENT_SOURCE_DIR}/src/mysource.cpp)

# 链接库文件
target_link_libraries(myexe)

其中,cmake_minimum_required指定CMake的最低版本,project指定项目名称,set指定C++标准版本,include_directories指定头文件搜索路径,add_executable添加可执行文件,target_link_libraries指定链接的库文件。

  1. 生成Makefile

在工程目录下新建一个名为build的目录,用于存放生成的Makefile文件。在终端中进入build目录,执行以下命令:

cmake ..

该命令会在build目录下生成Makefile文件。

  1. 编译项目

在终端中进入build目录,执行以下命令:

make

该命令会根据Makefile文件编译项目。

  1. 运行程序

在终端中进入build目录,执行以下命令:

./myexe

该命令会运行可执行文件,输出"Hello, CMake!"。

以下是CMakeLists.txt文件的详细内容:

cmake_minimum_required(VERSION 3.0)

project(myproject)

set(CMAKE_CXX_STANDARD 11)

# 设置头文件搜索路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

# 添加源文件
add_executable(myexe ${CMAKE_CURRENT_SOURCE_DIR}/src/mysource.cpp)

# 链接库文件
target_link_libraries(myexe)

以下是Makefile文件的详细内容:

# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.0)

# 项目信息
project(myproject)

# 指定C++标准版本
set(CMAKE_CXX_STANDARD 11)

# 设置头文件搜索路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

# 添加源文件
add_executable(myexe ${CMAKE_CURRENT_SOURCE_DIR}/src/mysource.cpp)

# 链接库文件
target_link_libraries(myexe)

CMake 保姆级教程(上) https://subingwen.cn/cmake/CMake-primer/

1 CMake 概述

CMake 是一个项目构建工具,并且是跨平台的。关于项目构建我们所熟知的还有 Makefile(通过 make 命令进行项目的构建),大多是 IDE 软件都集成了 make,比如:VS 的 nmake、linux 下的 GNU make、Qt 的 qmake 等,如果自己动手写 makefile,会发现,makefile 通常依赖于当前的编译平台,而且编写 makefile 的工作量比较大,解决依赖关系时也容易出错。

而 CMake 恰好能解决上述问题, 其允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需 make 编译即可,所以可以把 CMake 看成一款自动生成 Makefile 的工具,其编译流程如下图:

  • 蓝色虚线表示使用 makefile 构建项目的过程
  • 红色实线表示使用 cmake 构建项目的过程
  • 介绍完 CMake 的作用之后,再来总结一下它的优点:
  • 跨平台
  • 能够管理大型项目
  • 简化编译构建过程和编译过程
  • 可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功

2 CMake 的使用

CMake 支持大写、小写、混合大小写的命令。如果在编写 CMakeLists.txt 文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意。

2.1 注释

2.1.1 注释行

CMake 使用 # 进行行注释,可以放在任何位置。

# 这是一个 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.0.0)

2.1.2 注释块

CMake 使用 #[[ ]] 形式进行块注释。

#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
cmake_minimum_required(VERSION 3.0.0)

2.1 只有源文件

2.1.1 共处一室

准备工作,为了方便测试,在我本地电脑准备了这么几个测试文件

add.c

#include <stdio.h>
#include "head.h"

int add(int a, int b)
{
    return a+b;
}

sub.c

#include <stdio.h>
#include "head.h"

// 你好
int subtract(int a, int b)
{
    return a-b;
}

mult.c

#include <stdio.h>
#include "head.h"

int multiply(int a, int b)
{
    return a*b;
}

div.c

#include <stdio.h>
#include "head.h"

double divide(int a, int b)
{
    return (double)a/b;
}

head.h

#ifndef _HEAD_H
#define _HEAD_H
// 加法
int add(int a, int b);
// 减法
int subtract(int a, int b);
// 乘法
int multiply(int a, int b);
// 除法
double divide(int a, int b);
#endif

main.c

#include <stdio.h>
#include "head.h"

int main()
{
    int a = 20;
    int b = 12;
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", subtract(a, b));
    printf("a * b = %d\n", multiply(a, b));
    printf("a / b = %f\n", divide(a, b));
    return 0;
}

2 上述文件的目录结构如下:

$ tree
.
├── add.c
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c

3 添加 CMakeLists.txt 文件

在上述源文件所在目录下添加一个新文件 CMakeLists.txt,文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALC)
add_executable(app add.c div.c main.c mult.c sub.c)

接下来依次介绍一下在 CMakeLists.txt 文件中添加的三个命令:

  • cmake_minimum_required:指定使用的 cmake 的最低版本

    • 可选,非必须,如果不加可能会有警告
  • project:定义工程名称,并可指定工程的版本、工程描述、web 主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。

    # PROJECT 指令的语法是:
    project(<PROJECT-NAME> [<language-name>...])
    project(<PROJECT-NAME>
           [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
           [DESCRIPTION <project-description-string>]
           [HOMEPAGE_URL <url-string>]
           [LANGUAGES <language-name>...])
    • add_executable:定义工程会生成一个可执行程序

      add_executable(可执行程序名 源文件名称)
      • 这里的可执行程序名和 project 中的项目名没有任何关系
    • 源文件名可以是一个也可以是多个,如有多个可用空格或 ; 间隔

      # 样式1
      add_executable(app add.c div.c main.c mult.c sub.c)
      # 样式2
      add_executable(app add.c;div.c;main.c;mult.c;sub.c)

4 执行CMake命令

万事俱备只欠东风,将 CMakeLists.txt 文件编辑好之后,就可以执行 cmake 命令了。

# cmake 命令原型
$ cmake CMakeLists.txt文件所在路径
$ tree
.
├── add.c
├── CMakeLists.txt
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c

0 directories, 7 files
robin@OS:~/Linux/3Day/calc$ cmake .

当执行 cmake 命令之后,CMakeLists.txt 中的命令就会被执行,所以一定要注意给 cmake 命令指定路径的时候一定不能出错。

执行命令之后,看一下源文件所在目录中是否多了一些文件:

$ tree -L 1
.
├── add.c
├── CMakeCache.txt         # new add file
├── CMakeFiles             # new add dir
├── cmake_install.cmake    # new add file
├── CMakeLists.txt
├── div.c
├── head.h
├── main.c
├── Makefile               # new add file
├── mult.c
└── sub.c

我们可以看到在对应的目录下生成了一个 makefile 文件,此时再执行 make 命令(如果是在Windows下请到mingw64安装目录的bin文件夹下找一般会有一个ming32-make.exe,windows环境下一般是执行这个ming32-make.exe),就可以对项目进行构建得到所需的可执行程序了。

$ make
Scanning dependencies of target app
[ 16%] Building C object CMakeFiles/app.dir/add.c.o
[ 33%] Building C object CMakeFiles/app.dir/div.c.o
[ 50%] Building C object CMakeFiles/app.dir/main.c.o
[ 66%] Building C object CMakeFiles/app.dir/mult.c.o
[ 83%] Building C object CMakeFiles/app.dir/sub.c.o
[100%] Linking C executable app
[100%] Built target app

# 查看可执行程序是否已经生成
$ tree -L 1
.
├── add.c
├── app					# 生成的可执行程序
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── CMakeLists.txt
├── div.c
├── head.h
├── main.c
├── Makefile
├── mult.c
└── sub.c

最终可执行程序 app 就被编译出来了(这个名字是在 CMakeLists.txt 中指定的)。

2.1.2 VIP 包房

通过上面的例子可以看出,如果在 CMakeLists.txt 文件所在目录执行了 cmake 命令之后就会生成一些目录和文件(包括 makefile 文件),如果再基于 makefile文件执行 make 命令,程序在编译过程中还会生成一些中间文件和一个可执行文件,这样会导致整个项目目录看起来很混乱,不太容易管理和维护,此时我们就可以把生成的这些与项目源码无关的文件统一放到一个对应的目录里边,比如将这个目录命名为 build:

$ mkdir build
$ cd build
$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/Linux/build

现在 cmake 命令是在 build 目录中执行的,但是 CMakeLists.txt 文件是 build 目录的上一级目录中,所以 cmake 命令后指定的路径为..,即当前目录的上一级目录。

当命令执行完毕之后,在 build 目录中会生成一个 makefile 文件

$ tree build -L 1
build
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
└── Makefile

1 directory, 3 files

这样就可以在 build 目录中执行 make 命令编译项目,生成的相关文件自然也就被存储到 build 目录中了。这样通过 cmake 和 make 生成的所有文件就全部和项目源文件隔离开了,各回各家,各找各妈。

2.2 私人订制

2.2.1 定义变量

在上面的例子中一共提供了 5 个源文件,假设这五个源文件需要反复被使用,每次都直接将它们的名字写出来确实是很麻烦,此时我们就需要定义一个变量,将文件名对应的字符串存储起来,在 cmake 里定义变量需要使用 set。

# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

VAR:变量名 VALUE:变量值

# 方式1: 各个源文件之间使用空格间隔
# set(SRC_LIST add.c  div.c   main.c  mult.c  sub.c)

# 方式2: 各个源文件之间使用分号 ; 间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
add_executable(app  ${SRC_LIST})

2.2.2 指定使用的 C++ 标准

在编写 C++ 程序的时候,可能会用到 C++11、C++14、C++17、C++20 等新特性,那么就需要在编译的时候在编译命令中制定出要使用哪个标准:

$ g++ *.cpp -std=c++11 -o app

上面的例子中通过参数 -std=c++11 指定出要使用 c++11 标准编译程序,C++ 标准对应有一宏叫做 DCMAKE_CXX_STANDARD。在 CMake 中想要指定 C++ 标准有两种方式:

1 在 CMakeLists.txt 中通过 set 命令指定

#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)

2 在执行 cmake 命令的时候指定出这个宏的值

#增加-std=c++11
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11
#增加-std=c++14
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=14
#增加-std=c++17
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17

在上面例子中 CMake 后的路径需要根据实际情况酌情修改。

2.2.3 指定输出的路径

在 CMake 中指定可执行程序输出的路径,也对应一个宏,叫做 EXECUTABLE_OUTPUT_PATH,它的值还是通过 set 命令进行设置:

set(HOME /home/robin/Linux/Sort)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)
  • 第一行:定义一个变量用于存储一个绝对路径
  • 第二行:将拼接好的路径值设置给 EXECUTABLE_OUTPUT_PATH 宏
    • 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建
  • 由于可执行程序是基于 cmake 命令生成的 makefile 文件然后再执行 make 命令得到的,所以如果此处指定可执行程序生成路径的时候使用的是相对路径 ./xxx/xxx,那么这个路径中的 ./ 对应的就是 makefile 文件所在的那个目录。

2.3 搜索文件

如果一个项目里边的源文件很多,在编写 CMakeLists.txt 文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦也不现实。所以,在 CMake 中为我们提供了搜索文件的命令,可以使用 aux_source_directory 命令或者 file 命令。

2.3.1 方式 1

在 CMake 中使用 aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为:

aux_source_directory(< dir > < variable >)
  • dir:要搜索的目录
  • variable:将从 dir 目录下搜索到的源文件列表存储到该变量中
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(app  ${SRC_LIST})

2.3.2 方式 2

如果一个项目里边的源文件很多,在编写 CMakeLists.txt 文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦了。所以,在 CMake 中为我们提供了搜索文件的命令,他就是 file(当然,除了搜索以外通过 file 还可以做其他事情)。

file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
  • GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
  • GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。

搜索当前目录的 src 目录下所有的源文件,并存储到变量中

file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)

CMAKE_CURRENT_SOURCE_DIR 宏表示当前访问的 CMakeLists.txt 文件所在的路径。

关于要搜索的文件路径和类型可加双引号,也可不加:

file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")

2.4 包含头文件

在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在 CMake 中设置要包含的目录也很简单,通过一个命令就可以搞定了,他就是 include_directories:

include_directories(headpath)

举例说明,有源文件若干,其目录结构如下:

$ tree
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h
└── src
    ├── add.cpp
    ├── div.cpp
    ├── main.cpp
    ├── mult.cpp
    └── sub.cpp

3 directories, 7 files

CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALC)
set(CMAKE_CXX_STANDARD 11)
set(HOME /home/robin/Linux/calc)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin/)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
add_executable(app  ${SRC_LIST})

其中,第六行指定就是头文件的路径,PROJECT_SOURCE_DIR 宏对应的值就是我们在使用 cmake 命令时,后面紧跟的目录,一般是工程的根目录。

2.5 制作动态库或静态库

有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成一些静态库或动态库提供给第三方使用,下面来讲解在 cmake 中生成这两类库文件的方法。

2.5.1 制作静态库

在 cmake 中,如果要制作静态库,需要使用的命令如下:

add_library(库名称 STATIC 源文件1 [源文件2] ...) 

在 Linux 中,静态库名字分为三部分:lib+ 库名字 +.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。

在 Windows 中虽然库名和 Linux 格式不同,但也只需指定出名字即可。

下面有一个目录,需要将 src 目录中的源文件编译成静态库,然后再使用:

.
├── build
├── CMakeLists.txt
├── include           # 头文件目录
│   └── head.h
├── main.cpp          # 用于测试的源文件
└── src               # 源文件目录
    ├── add.cpp
    ├── div.cpp
    ├── mult.cpp
    └── sub.cpp

根据上面的目录结构,可以这样编写 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})

这样最终就会生成对应的静态库文件 libcalc.a。

2.5.2 制作动态库

在 cmake 中,如果要制作动态库,需要使用的命令如下:

add_library(库名称 SHARED 源文件1 [源文件2] ...) 

在 Linux 中,动态库名字分为三部分:lib+ 库名字 +.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。 在 Windows 中虽然库名和 Linux 格式不同,但也只需指定出名字即可。 根据上面的目录结构,可以这样编写 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc SHARED ${SRC_LIST})

这样最终就会生成对应的动态库文件 libcalc.so。

2.5.3 指定输出的路径

方式 1 - 适用于动态库

对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录:

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})

对于这种方式来说,其实就是通过 set 命令给 EXECUTABLE_OUTPUT_PATH 宏设置了一个路径,这个路径就是可执行文件生成的路径。

方式 2 - 都适用

由于在 Linux 下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用 EXECUTABLE_OUTPUT_PATH 宏了,而应该使用 LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})

2.6 包含库文件

在编写程序的过程中,可能会用到一些系统提供的动态库或者自己制作出的动态库或者静态库文件,cmake 中也为我们提供了相关的加载动态库的命令。

2.6.1 链接静态库

src
├── add.cpp
├── div.cpp
├── main.cpp
├── mult.cpp
└── sub.cpp

现在我们把上面 src 目录中的 add.cpp、div.cpp、mult.cpp、sub.cpp 编译成一个静态库文件 libcalc.a。

Linux 静态库和动态库 https://subingwen.cn/linux/library/#1-1-%E7%94%9F%E6%88%90%E9%9D%99%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93

测试目录结构如下:

$ tree 
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h
├── lib
│   └── libcalc.a     # 制作出的静态库的名字
└── src
    └── main.cpp

4 directories, 4 files

在 cmake 中,链接静态库的命令如下:

link_libraries(<static lib> [<static lib>...])

参数 1 :指定出要链接的静态库的名字 可以是全名 libxxx.a 也可以是掐头(lib)去尾(.a)之后的名字 xxx

参数 2-N:要链接的其它静态库的名字

如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:

link_directories(<lib path>)

这样,修改之后的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})

添加了第 8 行的代码,就可以根据参数指定的路径找到这个静态库了。

2.6.2 链接动态库

在程序编写过程中,除了在项目中引入静态库,好多时候也会使用一些标准的或者第三方提供的一些动态库,关于动态库的制作、使用以及在内存中的加载方式和静态库都是不同的,在此不再过多赘述,如有疑惑请参考 Linux 静态库和动态库https://subingwen.cn/linux/library/#1-1-%E7%94%9F%E6%88%90%E9%9D%99%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93

在 cmake 中链接动态库的命令如下:

target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • target:指定要加载动态库的文件的名字

    • 该文件可能是一个源文件

    • 该文件可能是一个动态库文件

    • 该文件可能是一个可执行文件

  • PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为 PUBLIC

    • 如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。

    • 动态库的链接具有传递性,如果动态库 A 链接了动态库 B、C,动态库 D 链接了动态库 A,此时动态库 D 相当于也链接了动态库 B、C,并可以使用动态库 B、C 中定义的方法。

      target_link_libraries(A B C)
      target_link_libraries(D A)
  • PUBLIC:在 public 后面的库会被 Link 到前面的 target 中,并且里面的符号也会被导出,提供给第三方使用。

  • PRIVATE:在 private 后面的库仅被 link 到前面的 target 中,并且终结掉,第三方不能感知你调了啥库

  • INTERFACE:在 interface 后面引入的库不会被链接到前面的 target 中,只会导出符号。

链接系统动态库

动态库的链接和静态库是完全不同的:

  • 静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。

  • 动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存

因此,在 cmake 中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后:

cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)

在 target_link_libraries(app pthread) 中:

  • app: 对应的是最终生成的可执行程序的名字

  • pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为 libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。

链接第三方动态库

现在,自己生成了一个动态库,对应的目录结构如下:

$ tree 
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h            # 动态库对应的头文件
├── lib
│   └── libcalc.so        # 自己制作的动态库文件
└── main.cpp              # 测试用的源文件

3 directories, 4 files

假设在测试文件 main.cpp 中既使用了自己制作的动态库 libcalc.so 又使用了系统提供的线程库,此时 CMakeLists.txt 文件可以这样写:

cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(app ${SRC_LIST})
target_link_libraries(app pthread calc)

在第六行中,pthread、calc 都是可执行程序 app 要链接的动态库的名字。当可执行程序 app 生成之后并执行该文件,会提示有如下错误信息:

$ ./app 
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

这是因为可执行程序启动之后,去加载 calc 这个动态库,但是不知道这个动态库被放到了什么位置

解决动态库无法加载的问题 https://subingwen.cn/linux/library/#2-4-1-%E5%BA%93%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86

所以就加载失败了,在 CMake 中可以在生成可执行程序之前,通过命令指定出要链接的动态库的位置,指定静态库位置使用的也是这个命令:

link_directories(path)

所以修改之后的 CMakeLists.txt 文件应该是这样的:

cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库
target_link_libraries(app pthread calc)

通过 link_directories 指定了动态库的路径之后,在执行生成的可执行程序的时候,就不会出现找不到动态库的问题了。

温馨提示:使用 target_link_libraries 命令就可以链接动态库,也可以链接静态库文件。

2.7 日志

在 CMake 中可以用用户显示一条消息,该命令的名字为 message:

message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
  • (无) :重要消息

  • STATUS :非重要消息

  • WARNING:CMake 警告,会继续执行

  • AUTHOR_WARNING:CMake 警告 (dev), 会继续执行

  • SEND_ERROR:CMake 错误,继续执行,但是会跳过生成的步骤

  • FATAL_ERROR:CMake 错误,终止所有处理过程

CMake 的命令行工具会在 stdout 上显示 STATUS 消息,在 stderr 上显示其他所有消息。CMake 的 GUI 会在它的 log 区域显示所有消息。

CMake 警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。

# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

2.8 变量操作

2.8.1 追加

有时候项目中的源文件并不一定都在同一个目录中,但是这些源文件最终却需要一起进行编译来生成最终的可执行文件或者库文件。如果我们通过 file 命令对各个目录下的源文件进行搜索,最后还需要做一个字符串拼接的操作,关于字符串拼接可以使用 set 命令也可以使用 list 命令。

使用 set 拼接

如果使用 set 进行字符串拼接,对应的命令格式如下:

set(变量名1 ${变量名1} ${变量名2} ...)

关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖。

cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
# 追加(拼接)
set(SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
message(STATUS "message: ${SRC_1}")

使用 list 拼接

如果使用 list 进行字符串拼接,对应的命令格式如下:

list(APPEND <list> [<element> ...])

list 命令的功能比 set 要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND 表示进行数据追加,后边的参数和 set 就一样了。

cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp)
file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp)
# 追加(拼接)
list(APPEND SRC_1 ${SRC_1} ${SRC_2} ${TEMP})
message(STATUS "message: ${SRC_1}")

在 CMake 中,使用 set 命令可以创建一个 list。一个在 list 内部是一个由分号; 分割的一组字符串。例如,set(var a b c d e) 命令将会创建一个 list:a;b;c;d;e,但是最终打印变量值的时候得到的是 abcde。

set(tmp1 a;b;c;d;e)
set(tmp2 a b c d e)
message(${tmp1})
message(${tmp2})

输出的结果:

abcde
abcde

2.8.2 字符串移除

我们在通过 file 搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,比如:

$ tree
.
├── add.cpp
├── div.cpp
├── main.cpp
├── mult.cpp
└── sub.cpp

0 directories, 5 files

在当前这么目录有五个源文件,其中 main.cpp 是一个测试文件。如果我们想要把计算器相关的源文件生成一个动态库给别人使用,那么只需要 add.cpp、div.cp、mult.cpp、sub.cpp 这四个源文件就可以了。此时,就需要将 main.cpp 从搜索到的数据中剔除出去,想要实现这个功能,也可以使用 list

list(REMOVE_ITEM <list> <value> [<value> ...])

通过上面的命令原型可以看到删除和追加数据类似,只不过是第一个参数变成了 REMOVE_ITEM。

cmake_minimum_required(VERSION 3.0)
project(TEST)
set(TEMP "hello,world")
file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp)
# 移除前日志
message(STATUS "message: ${SRC_1}")
# 移除 main.cpp
list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp)
# 移除后日志
message(STATUS "message: ${SRC_1}")

可以看到,在第8行把将要移除的文件的名字指定给 list 就可以了。但是一定要注意通过 file 命令搜索源文件的时候得到的是文件的绝对路径(在 list 中每个文件对应的路径都是一个 item,并且都是绝对路径),那么在移除的时候也要将该文件的绝对路径指定出来才可以,否是移除操作不会成功。

关于 list 命令还有其它功能,但是并不常用,在此就不一一进行举例介绍了。

1 获取 list 的长度。

list(LENGTH <list> <output variable>)
  • LENGTH:子命令 LENGTH 用于读取列表长度
  • :当前操作的列表
  • :新创建的变量,用于存储列表的长度。

2 读取列表中指定索引的的元素,可以指定多个索引

list(GET <list> <element index> [<element index> ...] <output variable>)
  • :当前操作的列表

  • :列表元素的索引

    • 从 0 开始编号,索引 0 的元素为列表中的第一个元素;

    • 索引也可以是负数,-1 表示列表的最后一个元素,-2 表示列表倒数第二个元素,以此类推

    • 当索引(不管是正还是负)超过列表的长度,运行会报错

  • :新创建的变量,存储指定索引元素的返回结果,也是一个列表。

3 将列表中的元素用连接符(字符串)连接起来组成一个字符串

list (JOIN <list> <glue> <output variable>)
  • :当前操作的列表
  • :指定的连接符(字符串)
  • :新创建的变量,存储返回的字符串

4 查找列表是否存在指定的元素,若果未找到,返回 - 1

list(FIND <list> <value> <output variable>)
  • :当前操作的列表
  • :需要再列表中搜索的元素
  • :新创建的变量
    • 如果列表 中存在 ,那么返回 在列表中的索引
    • 如果未找到则返回 - 1。

5 将元素追加到列表中

list (APPEND <list> [<element> ...])

6 在 list 中指定的位置插入若干元素

list(INSERT <list> <element_index> <element> [<element> ...])

7 将元素插入到列表的 0 索引位置

list (PREPEND <list> [<element> ...])

8 将列表中最后元素移除

list (POP_BACK <list> [<out-var>...])

9 将列表中第一个元素移除

list (POP_FRONT <list> [<out-var>...])

10 将指定的元素从列表中移除

list (REMOVE_ITEM <list> <value> [<value> ...])

11 将指定索引的元素从列表中移除

list (REMOVE_AT <list> <index> [<index> ...])

12 移除列表中的重复元素

list (REMOVE_DUPLICATES <list>)

13 列表翻转

list(REVERSE <list>)

14 列表排序

list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
  • COMPARE:指定排序方法。有如下几种值可选:
    • STRING: 按照字母顺序进行排序,为默认的排序方法
    • FILE_BASENAME:如果是一系列路径名,会使用 basename 进行排序
    • NATURAL:使用自然数顺序排序
  • CASE:指明是否大小写敏感。有如下几种值可选:
    • SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
    • INSENSITIVE:按照大小写不敏感方式进行排序
  • ORDER:指明排序的顺序。有如下几种值可选:
    • ASCENDING: 按照升序排列,为默认值
    • DESCENDING:按照降序排列

2.9 宏定义

在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,如下所示:

#include <stdio.h>
#define NUMBER  3

int main()
{
    int a = 10;
#ifdef DEBUG
    printf("我是一个程序猿, 我不会爬树...\n");
#endif
    for(int i=0; i<NUMBER; ++i)
    {
        printf("hello, GCC!!!\n");
    }
    return 0;
}

在程序的第七行对 DEBUG 宏进行了判断,如果该宏被定义了,那么第八行就会进行日志输出,如果没有定义这个宏,第八行就相当于被注释掉了,因此最终无法看到日志输入出(上述代码中并没有定义这个宏)。

为了让测试更灵活,我们可以不在代码中定义这个宏,而是在测试的时候去把它定义出来,其中一种方式就是在 gcc/g++ 命令中去指定,如下:

$ gcc test.c -DDEBUG -o app

在 gcc/g++ 命令中通过参数 -D 指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏,其名字为 DEBUG。

在 CMake 中我们也可以做类似的事情,对应的命令叫做 add_definitions:

add_definitions(-D宏名称)

针对于上面的源文件编写一个 CMakeLists.txt,内容如下:

cmake_minimum_required(VERSION 3.0)
project(TEST)
# 自定义 DEBUG 宏
add_definitions(-DDEBUG)
add_executable(app ./test.c)

通过这种方式,上述代码中的第八行日志就能够被输出出来了。

3 预定义宏

下面的列表中为大家整理了一些 CMake 中常用的宏:

宏 功能 PROJECT_SOURCE_DIR 使用 cmake 命令后紧跟的目录,一般是工程的根目录 PROJECT_BINARY_DIR 执行 cmake 命令的目录 CMAKE_CURRENT_SOURCE_DIR 当前处理的 CMakeLists.txt 所在的路径 CMAKE_CURRENT_BINARY_DIR target 编译目录 EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置 LIBRARY_OUTPUT_PATH 重新定义目标链接库文件的存放位置 PROJECT_NAME 返回通过 PROJECT 指令定义的项目名称 CMAKE_BINARY_DIR 项目实际构建路径,假设在 build 目录进行的构建,那么得到的就是这个目录的路径

文章作者: 苏丙榅 文章链接: https://subingwen.cn/cmake/CMake-primer/ 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 爱编程的大丙!

CMake 保姆级教程(下)

https://subingwen.cn/cmake/CMake-advanced/

1 嵌套的 CMake

如果项目很大,或者项目中有很多的源码目录,在通过 CMake 管理项目的时候如果只使用一个 CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个 CMakeLists.txt 文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。

先来看一下下面的这个的目录结构:

$ tree
.
├── build
├── calc
│   ├── add.cpp
│   ├── CMakeLists.txt
│   ├── div.cpp
│   ├── mult.cpp
│   └── sub.cpp
├── CMakeLists.txt
├── include
│   ├── calc.h
│   └── sort.h
├── sort
│   ├── CMakeLists.txt
│   ├── insert.cpp
│   └── select.cpp
├── test1
│   ├── calc.cpp
│   └── CMakeLists.txt
└── test2
    ├── CMakeLists.txt
    └── sort.cpp

6 directories, 15 files
  • include 目录:头文件目录

  • calc 目录:目录中的四个源文件对应的加、减、乘、除算法

    • 对应的头文件是 include 中的 calc.h
  • sort 目录 :目录中的两个源文件对应的是插入排序和选择排序算法

    • 对应的头文件是 include 中的 sort.h
  • test1 目录:测试目录,对加、减、乘、除算法进行测试

  • test2 目录:测试目录,对排序算法进行测试

可以看到各个源文件目录所需要的 CMakeLists.txt 文件现在已经添加完毕了。接下来庖丁解牛,我们依次分析一下各个文件中需要添加的内容。

1.1 准备工作

1.1.1 节点关系

众所周知,Linux 的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:

  • 根节点 CMakeLists.txt 中的变量全局有效
  • 父节点 CMakeLists.txt 中的变量可以在子节点中使用
  • 子节点 CMakeLists.txt 中的变量只能在当前节点中使用

1.1.2 添加子目录

接下来我们还需要知道在 CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令:

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
  • source_dir:指定了 CMakeLists.txt 源文件和代码文件的位置,其实就是指定子目录

  • binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。

  • EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的 ALL 目标里,并且也会被排除在 IDE 工程文件之外。用户必须显式构建在子路径下的目标。

通过这种方式 CMakeLists.txt 文件之间的父子关系就被构建出来了。

1.2 解决问题

在上面的目录中我们要做如下事情:

  • 通过 test1 目录中的测试文件进行计算器相关的测试
  • 通过 test2 目录中的测试文件进行排序相关的测试

现在相当于是要进行模块化测试,对于 calc 和 sort 目录中的源文件来说,可以将它们先编译成库文件(可以是静态库也可以是动态库)然后在提供给测试文件使用即可。库文件的本质其实还是代码,只不过是从文本格式变成了二进制格式。

1.2.1 根目录

根目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(test)
# 定义变量
# 静态库生成的路径
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 测试程序生成的路径
set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
# 头文件目录
set(HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 静态库的名字
set(CALC_LIB calc)
set(SORT_LIB sort)
# 可执行程序的名字
set(APP_NAME_1 test1)
set(APP_NAME_2 test2)
# 添加子目录
add_subdirectory(calc)
add_subdirectory(sort)
add_subdirectory(test1)
add_subdirectory(test2)

在根节点对应的文件中主要做了两件事情:定义全局变量和添加子目录。

  • 定义的全局变量主要是给子节点使用,目的是为了提高子节点中的 CMakeLists.txt 文件的可读性和可维护性,避免冗余并降低出差的概率。

  • 一共添加了四个子目录,每个子目录中都有一个 CMakeLists.txt 文件,这样它们的父子关系就被确定下来了。

1.2.2 calc 目录

calc 目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${CALC_LIB} STATIC ${SRC})

第 3 行 aux_source_directory:搜索当前目录(calc 目录)下的所有源文件 第 4 行 include_directories:包含头文件路径,HEAD_PATH 是在根节点文件中定义的 第 5 行 set:设置库的生成的路径,LIB_PATH 是在根节点文件中定义的 第 6 行 add_library:生成静态库,静态库名字 CALC_LIB 是在根节点文件中定义的

1.2.3 sort 目录

sort 目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${SORT_LIB} SHARED ${SRC})

第 6 行 add_library:生成动态库,动态库名字 SORT_LIB 是在根节点文件中定义的

这个文件中的内容和 calc 节点文件中的内容类似,只不过这次生成的是动态库。

在生成库文件的时候,这个库可以是静态库也可以是动态库,一般需要根据实际情况来确定。如果生成的库比较大,建议将其制作成动态库。

1.2.4 test1 目录

test1 目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALCTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
# include_directories(${HEAD_PATH})
link_libraries(${CALC_LIB})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
add_executable(${APP_NAME_1} ${SRC})
  • 第 4 行 include_directories:指定头文件路径,HEAD_PATH 变量是在根节点文件中定义的

  • 第 6 行 link_libraries:指定可执行程序要链接的静态库,CALC_LIB 变量是在根节点文件中定义的

  • 第 7 行 set:指定可执行程序生成的路径,EXEC_PATH 变量是在根节点文件中定义的

  • 第 8 行 add_executable:生成可执行程序,APP_NAME_1 变量是在根节点文件中定义的

此处的可执行程序链接的是静态库,最终静态库会被打包到可执行程序中,可执行程序启动之后,静态库也就随之被加载到内存中了。

1.2.5 test2 目录

test2 目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(SORTTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
# link_directories(${LIB_PATH})
add_executable(${APP_NAME_2} ${SRC})
target_link_libraries(${APP_NAME_2} ${SORT_LIB})
  • 第四行 include_directories:包含头文件路径,HEAD_PATH 变量是在根节点文件中定义的

  • 第五行 set:指定可执行程序生成的路径,EXEC_PATH 变量是在根节点文件中定义的

  • 第六行 link_directories:指定可执行程序要链接的动态库的路径,LIB_PATH 变量是在根节点文件中定义的

  • 第七行 add_executable:生成可执行程序,APP_NAME_2 变量是在根节点文件中定义的

  • 第八行 target_link_libraries:指定可执行程序要链接的动态库的名字

在生成可执行程序的时候,动态库不会被打包到可执行程序内部。当可执行程序启动之后动态库也不会被加载到内存,只有可执行程序调用了动态库中的函数的时候,动态库才会被加载到内存中,且多个进程可以共用内存中的同一个动态库,所以动态库又叫共享库。

1.2.6 构建项目

一切准备就绪之后,开始构建项目,进入到根节点目录的 build 目录中,执行 cmake 命令,如下:

$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/cmake/calc/build

可以看到在 build 目录中生成了一些文件和目录,如下所示:

$ tree build -L 1     
build
├── calc                  # 目录
├── CMakeCache.txt        # 文件
├── CMakeFiles            # 目录
├── cmake_install.cmake   # 文件
├── Makefile              # 文件
├── sort                  # 目录
├── test1                 # 目录
└── test2                 # 目录

然后在 build 目录下执行 make 命令:

通过上图可以得到如下信息:

  • 在项目根目录的 lib 目录中生成了静态库 libcalc.a

  • 在项目根目录的 lib 目录中生成了动态库 libsort.so

  • 在项目根目录的 bin 目录中生成了可执行程序 test1

  • 在项目根目录的 bin 目录中生成了可执行程序 test2

最后再来看一下上面提到的这些文件是否真的被生成到对应的目录中了:

$ tree bin/ lib/
bin/
├── test1
└── test2
lib/
├── libcalc.a
└── libsort.so

由此可见,真实不虚,至此,项目构建完毕。

写在最后:

在项目中,如果将程序中的某个模块制作成了动态库或者静态库并且在CMakeLists.txt 中指定了库的输出目录,而后其它模块又需要加载这个生成的库文件,此时直接使用就可以了,如果没有指定库的输出路径或者需要直接加载外部提供的库文件,此时就需要使用 link_directories 将库文件路径指定出来。

2 流程控制

在 CMake 的 CMakeLists.txt 中也可以进行流程控制,也就是说可以像写 shell 脚本那样进行条件判断和循环。

2.1 条件判断

关于条件判断其语法格式如下:

if(<condition>)
  <commands>
elseif(<condition>) # 可选快, 可以重复
  <commands>
else()              # 可选快
  <commands>
endif()

在进行条件判断的时候,如果有多个条件,那么可以写多个 elseif,最后一个条件可以使用 else,但是开始和结束是必须要成对出现的,分别为:if 和 endif。

2.1.1 基本表达式

if(<expression>)

如果是基本表达式,expression 有以下三种情况:常量、变量、字符串。

  • 如果是 1, ON, YES, TRUE, Y, 非零值,非空字符串时,条件判断返回 True

  • 如果是 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串时,条件判断返回 False

2.1.2 逻辑判断

  • NOT

    • ​ if(NOT ) 其实这就是一个取反操作,如果条件 condition 为 True 将返回 False,如果条件 condition 为 False 将返回 True。
  • AND

    • if( AND ) 如果 cond1 和 cond2 同时为 True,返回 True 否则返回 False。
  • OR

    • if( OR ) 如果 cond1 和 cond2 两个条件中至少有一个为 True,返回 True,如果两个条件都为 False 则返回 False。

2.1.3 比较

基于数值的比较

if(<variable|string> LESS <variable|string>)
if(<variable|string> GREATER <variable|string>)
if(<variable|string> EQUAL <variable|string>)
if(<variable|string> LESS_EQUAL <variable|string>)
if(<variable|string> GREATER_EQUAL <variable|string>)
  • LESS:如果左侧数值小于右侧,返回 True

  • GREATER:如果左侧数值大于右侧,返回 True

  • EQUAL:如果左侧数值等于右侧,返回 True

  • LESS_EQUAL:如果左侧数值小于等于右侧,返回 True

  • GREATER_EQUAL:如果左侧数值大于等于右侧,返回 True

基于字符串的比较

if(<variable|string> STRLESS <variable|string>)
if(<variable|string> STRGREATER <variable|string>)
if(<variable|string> STREQUAL <variable|string>)
if(<variable|string> STRLESS_EQUAL <variable|string>)
if(<variable|string> STRGREATER_EQUAL <variable|string>)
  • STRLESS:如果左侧字符串小于右侧,返回 True

  • STRGREATER:如果左侧字符串大于右侧,返回 True

  • STREQUAL:如果左侧字符串等于右侧,返回 True

  • STRLESS_EQUAL:如果左侧字符串小于等于右侧,返回 True

  • STRGREATER_EQUAL:如果左侧字符串大于等于右侧,返回 True

2.1.4 文件操作

1 判断文件或者目录是否存在

if(EXISTS path-to-file-or-directory)

如果文件或者目录存在返回 True,否则返回 False。

2 判断是不是目录

if(IS_DIRECTORY path)
  • 此处目录的 path 必须是绝对路径

  • 如果目录存在返回 True,目录不存在返回 False。

3 判断是不是软连接

if(IS_SYMLINK file-name)
  • 此处的 file-name 对应的路径必须是绝对路径
  • 如果软链接存在返回 True,软链接不存在返回 False。
  • 软链接相当于 Windows 里的快捷方式

4 判断是不是绝对路径

if(IS_ABSOLUTE path)
  • 关于绝对路径:
    • 如果是 Linux,该路径需要从根目录开始描述
    • 如果是 Windows,该路径需要从盘符开始描述
  • 如果是绝对路径返回 True,如果不是绝对路径返回 False。

2.1.5 其它

判断某个元素是否在列表中

if(<variable|string> IN_LIST <variable>)
  • CMake 版本要求:大于等于 3.3
  • 如果这个元素在列表中返回 True,否则返回 False。

比较两个路径是否相等

if(<variable|string> PATH_EQUAL <variable|string>)
  • CMake 版本要求:大于等于 3.24
  • 如果这个元素在列表中返回 True,否则返回 False。

关于路径的比较其实就是另个字符串的比较,如果路径格式书写没有问题也可以通过下面这种方式进行比较:

if(<variable|string> STREQUAL <variable|string>)

我们在书写某个路径的时候,可能由于误操作会多写几个分隔符,比如把 /a/b/c 写成 /a//b///c,此时通过 STREQUAL 对这两个字符串进行比较肯定是不相等的,但是通过 PATH_EQUAL 去比较两个路径,得到的结果确实相等的,可以看下面的例子:

cmake_minimum_required(VERSION 3.26)
project(test)

if("/home//robin///Linux" PATH_EQUAL "/home/robin/Linux")
    message("路径相等")
else()
    message("路径不相等")
endif()

message(STATUS "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")

if("/home//robin///Linux" STREQUAL "/home/robin/Linux")
    message("路径相等")
else()
    message("路径不相等")
endif()

输出的日志信息如下:

路径相等
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
路径不相等

通过得到的结果我们可以得到一个结论:在进行路径比较的时候,如果使用 PATH_EQUAL 可以自动剔除路径中多余的分割线然后再进行路径的对比,使用 STREQUAL 则只能进行字符串比较。

关于if更多的多条件判断,请参考官方文档 https://cmake.org/cmake/help/latest/command/if.html

2.2 循环

在 CMake 中循环有两种方式,分别是:foreach 和 while。

2.2.1 foreach

使用 foreach 进行循环,语法格式如下:

foreach(<loop_var> <items>)
    <commands>
endforeach()

通过 foreach 我们就可以对 items 中的数据进行遍历,然后通过 loop_var 将遍历到的当前的值取出,在取值的时候有以下几种用法:

方法 1

foreach(<loop_var> RANGE <stop>)
  • RANGE:关键字,表示要遍历范围
  • stop:这是一个正整数,表示范围的结束值,在遍历的时候从 0 开始,最大值为 stop。
  • loop_var:存储每次循环取出的值

举例说明:

cmake_minimum_required(VERSION 3.2)
project(test)
# 循环
foreach(item RANGE 10)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

输出的日志信息是这样的:

$ cmake ..
-- 当前遍历的值为: 0
-- 当前遍历的值为: 1
-- 当前遍历的值为: 2
-- 当前遍历的值为: 3
-- 当前遍历的值为: 4
-- 当前遍历的值为: 5
-- 当前遍历的值为: 6
-- 当前遍历的值为: 7
-- 当前遍历的值为: 8
-- 当前遍历的值为: 9
-- 当前遍历的值为: 10
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

再次强调:在对一个整数区间进行遍历的时候,得到的范围是这样的 【0,stop】,右侧是闭区间包含 stop 这个值。

方法 2

foreach(<loop_var> RANGE <start> <stop> [<step>])

这是上面方法1 的加强版,我们在遍历一个整数区间的时候,除了可以指定起始范围,还可以指定步长。

  • RANGE:关键字,表示要遍历范围

  • start:这是一个正整数,表示范围的起始值,也就是说最小值为 start

  • stop:这是一个正整数,表示范围的结束值,也就是说最大值为 stop

  • step:控制每次遍历的时候以怎样的步长增长,默认为1,可以不设置

  • loop_var:存储每次循环取出的值

举例说明:

cmake_minimum_required(VERSION 3.2)
project(test)

foreach(item RANGE 10 30 2)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

输出的结果如下:

$ cmake ..
-- 当前遍历的值为: 10
-- 当前遍历的值为: 12
-- 当前遍历的值为: 14
-- 当前遍历的值为: 16
-- 当前遍历的值为: 18
-- 当前遍历的值为: 20
-- 当前遍历的值为: 22
-- 当前遍历的值为: 24
-- 当前遍历的值为: 26
-- 当前遍历的值为: 28
-- 当前遍历的值为: 30
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

再次强调:在使用上面的方式对一个整数区间进行遍历的时候,得到的范围是这样的 【start,stop】,左右两侧都是闭区间,包含 start 和 stop 这两个值,步长 step 默认为 1,可以不设置。

方法 3

foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])

这是 foreach 的另一个变体,通过这种方式我们可以对更加复杂的数据进行遍历,前两种方式只适用于对某个正整数范围内的遍历。

  • IN:关键字,表示在 xxx 里边

  • LISTS:关键字,对应的是列表 list,通过 set、list 可以获得

  • ITEMS:关键字,对应的也是列表

  • loop_var:存储每次循环取出的值

cmake_minimum_required(VERSION 3.2)
project(test)
# 创建 list
set(WORD a b c d)
set(NAME ace sabo luffy)
# 遍历 list
foreach(item IN LISTS WORD NAME)
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

在上面的例子中,创建了两个 list 列表,在遍历的时候对它们两个都进行了遍历(可以根据实际需求选择同时遍历多个或者只遍历一个)。输出的日志信息如下:

$ cd build/
$ cmake ..
-- 当前遍历的值为: a
-- 当前遍历的值为: b
-- 当前遍历的值为: c
-- 当前遍历的值为: d
-- 当前遍历的值为: ace
-- 当前遍历的值为: sabo
-- 当前遍历的值为: luffy
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

一共输出了 7 个字符串,说明遍历是没有问题的。接下来看另外一种方式:

cmake_minimum_required(VERSION 3.2)
project(test)

set(WORD a b c "d e f")
set(NAME ace sabo luffy)
foreach(item IN ITEMS ${WORD} ${NAME})
    message(STATUS "当前遍历的值为: ${item}" )
endforeach()

在上面的例子中,遍历过程中将关键字 LISTS 改成了 ITEMS,后边跟的还是一个或者多个列表,只不过此时需要通过 ${} 将列表中的值取出。其输出的信息和上一个例子是一样的:

$ cd build/
$ cmake ..
-- 当前遍历的值为: a
-- 当前遍历的值为: b
-- 当前遍历的值为: c
-- 当前遍历的值为: d e f
-- 当前遍历的值为: ace
-- 当前遍历的值为: sabo
-- 当前遍历的值为: luffy
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build

小细节:在通过 set 组织列表的时候,如果某个字符串中有空格,可以通过双引号将其包裹起来,具体的操作方法可以参考上面的例子。

方法 4

注意事项:这种循环方式要求 CMake 的版本大于等于 3.17。

foreach(<loop_var>... IN ZIP_LISTS <lists>)

通过这种方式,遍历的还是一个或多个列表,可以理解为是方式3 的加强版。因为通过上面的方式遍历多个列表,但是又想把指定列表中的元素取出来使用是做不到的,在这个加强版中就可以轻松实现。

  • loop_var:存储每次循环取出的值,可以根据要遍历的列表的数量指定多个变量,用于存储对应的列表当前取出的那个值。

    • 如果指定了多个变量名,它们的数量应该和列表的数量相等
    • 如果只给出了一个 loop_var,那么它将一系列的 loop_var_N 变量来存储对应列表中的当前项,也就是说 loop_var_0 对应第一个列表,loop_var_1 对应第二个列表,以此类推......
    • 如果遍历的多个列表中一个列表较短,当它遍历完成之后将不会再参与后续的遍历(因为其它列表还没有遍历完)。
  • IN:关键字,表示在 xxx 里边

  • ZIP_LISTS:关键字,对应的是列表 list,通过 set 、list 可以获得

    cmake_minimum_required(VERSION 3.17)
    project(test)
    # 通过list给列表添加数据
    list(APPEND WORD hello world "hello world")
    list(APPEND NAME ace sabo luffy zoro sanji)
    # 遍历列表
    foreach(item1 item2 IN ZIP_LISTS WORD NAME)
        message(STATUS "当前遍历的值为: item1 = ${item1}, item2=${item2}" )
    endforeach()
    
    message("=============================")
    # 遍历列表
    foreach(item  IN ZIP_LISTS WORD NAME)
        message(STATUS "当前遍历的值为: item1 = ${item_0}, item2=${item_1}" )
    endforeach()

    在这个例子中关于列表数据的添加是通过 list 来实现的。在遍历列表的时候一共使用了两种方式,一种提供了多个变量来存储当前列表中的值,另一种只有一个变量,但是实际取值的时候需要通过变量名_0、变量名_1、变量名_N 的方式来操作,注意事项:第一个列表对应的编号是 0,第一个列表对应的编号是 0,第一个列表对应的编号是 0。

    上面的例子输出的结果如下:

    $ cd build/
    $ cmake ..
    -- 当前遍历的值为: item1 = hello, item2=ace
    -- 当前遍历的值为: item1 = world, item2=sabo
    -- 当前遍历的值为: item1 = hello world, item2=luffy
    -- 当前遍历的值为: item1 = , item2=zoro
    -- 当前遍历的值为: item1 = , item2=sanji
    =============================
    -- 当前遍历的值为: item1 = hello, item2=ace
    -- 当前遍历的值为: item1 = world, item2=sabo
    -- 当前遍历的值为: item1 = hello world, item2=luffy
    -- 当前遍历的值为: item1 = , item2=zoro
    -- 当前遍历的值为: item1 = , item2=sanji
    -- Configuring done (0.0s)
    -- Generating done (0.0s)
    -- Build files have been written to: /home/robin/abc/a/build

2.2.2 while

除了使用 foreach 也可以使用 while 进行循环,关于循环结束对应的条件判断的书写格式和 if/elseif 是一样的。while 的语法格式如下:

while(<condition>)
    <commands>
endwhile()

while 循环比较简单,只需要指定出循环结束的条件即可:

cmake_minimum_required(VERSION 3.5)
project(test)
# 创建一个列表 NAME
set(NAME luffy sanji zoro nami robin)
# 得到列表长度
list(LENGTH NAME LEN)
# 循环
while(${LEN} GREATER  0)
    message(STATUS "names = ${NAME}")
    # 弹出列表头部元素
    list(POP_FRONT NAME)
    # 更新列表长度
    list(LENGTH NAME LEN)
endwhile()

输出的结果如下:

$ cd build/
$ cmake ..
-- names = luffy;sanji;zoro;nami;robin
-- names = sanji;zoro;nami;robin
-- names = zoro;nami;robin
-- names = nami;robin
-- names = robin
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/robin/abc/a/build

可以看到当列表中的元素全部被弹出之后,列表的长度变成了 0,此时 while 循环也就退出了。

一、Linux 静态库和动态库

https://subingwen.cn/linux/library/#1-1-%E7%94%9F%E6%88%90%E9%9D%99%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93

不管是 Linux 还是 Windows 中的库文件其本质和工作模式都是相同的,只不过在不同的平台上库对应的文件格式和文件后缀不同。程序中调用的库有两种 静态库和动态库,不管是哪种库文件本质是还是源文件,只不过是二进制格式只有计算机能够识别,作为一个普通人就无能为力了。

在项目中使用库一般有两个目的,一个是为了使程序更加简洁不需要在项目中维护太多的源文件,另一方面是为了源代码保密,毕竟不是所有人都想把自己编写的程序开源出来。

当我们拿到了库文件(动态库、静态库)之后要想使用还必须有这些库中提供的 API 函数的声明,也就是头文件,把这些都添加到项目中,就可以快乐的写代码了。

1 静态库

在 Linux 中静态库由程序 ar 生成,现在静态库已经不像之前那么普遍了,这主要是由于程序都在使用动态库。关于静态库的命名规则如下:

  • 在 Linux 中静态库以 lib 作为前缀,以.a 作为后缀,中间是库的名字自己指定即可,即: libxxx.a

  • 在 Windows 中静态库一般以 lib 作为前缀,以 lib 作为后缀,中间是库的名字需要自己指定,即: libxxx.lib

1.1 生成静态链接库

生成静态库,需要先对源文件进行汇编操作 (使用参数 -c) 得到二进制格式的目标文件 (.o 格式), 然后在通过 ar 工具将目标文件打包就可以得到静态库文件了 (libxxx.a)。

使用 ar 工具创建静态库的时候需要三个参数:

  • 参数c:创建一个库,不管库是否存在,都将创建。

  • 参数s:创建目标文件索引,这在创建较大的库时能加快时间。

  • 参数r:在库中插入模块 (替换)。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。

生成静态链接库的具体步骤如下:

1.需要将源文件进行汇编,得到 .o 文件,需要使用参数 -c

# 执行如下操作, 默认生成二进制的 .o 文件
# -c 参数位置没有要求
$ gcc 源文件(*.c) -c	

2.将得到的 .o 进行打包,得到静态库

$ ar rcs 静态库的名字(libxxx.a) 原材料(*.o)

3.发布静态库

# 发布静态库
	1. 提供头文件 **.h
	2. 提供制作出来的静态库 libxxx.a

1.2 静态库制作举例

1.2.1 准备测试程序

在某个目录中有如下的源文件,用来实现一个简单的计算器:

# 目录结构 add.c div.c mult.c sub.c -> 算法的源文件, 函数声明在头文件 head.h
# main.c中是对接口的测试程序, 制作库的时候不需要将 main.c 算进去
.
├── add.c
├── div.c
├── include
│   └── head.h
├── main.c
├── mult.c
└── sub.c

加法计算源文件 add.c:

#include <stdio.h>
#include "head.h"

int add(int a, int b)
{
    return a+b;
}

减法计算源文件 sub.c:

#include <stdio.h>
#include "head.h"

int subtract(int a, int b)
{
    return a-b;
}

乘法计算源文件 mult.c:

#include <stdio.h>
#include "head.h"

int multiply(int a, int b)
{
    return a*b;
}

减法计算的源文件 div.c

#include <stdio.h>
#include "head.h"

double divide(int a, int b)
{
    return (double)a/b;
}

头文件 head.h

#ifndef _HEAD_H
#define _HEAD_H
// 加法
int add(int a, int b);
// 减法
int subtract(int a, int b);
// 乘法
int multiply(int a, int b);
// 除法
double divide(int a, int b);
#endif

测试文件 main.c

#include <stdio.h>
#include "head.h"

int main()
{
    int a = 20;
    int b = 12;
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", subtract(a, b));
    printf("a * b = %d\n", multiply(a, b));
    printf("a / b = %f\n", divide(a, b));
    return 0;
}

1.2.2 生成静态库

第一步:将源文件 add.c, div.c, mult.c, sub.c 进行汇编,得到二进制目标文件 add.o, div.o, mult.o, sub.o

# 1. 生成.o
$ gcc add.c div.c mult.c sub.c -c
sub.c:2:18: fatal error: head.h: No such file or directory
compilation terminated.

# 提示头文件找不到, 添加参数 -I 重新头文件路径即可
$ gcc add.c div.c mult.c sub.c -c -I ./include/

# 查看目标文件是否已经生成
$ tree
.
├── add.c
├── add.o            # 目标文件
├── div.c
├── div.o            # 目标文件
├── include
│   └── head.h
├── main.c
├── mult.c
├── mult.o           # 目标文件
├── sub.c
└── sub.o            # 目标文件	

第二步:将生成的目标文件通过 ar 工具打包生成静态库

# 2. 将生成的目标文件 .o 打包成静态库
$ ar rcs libcalc.a a.o b.o c.o    # a.o b.o c.o在同一个目录中可以写成 *.o

# 查看目录中的文件
$ tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│   └── `head.h  ===> 和静态库一并发布
├── `libcalc.a   ===> 生成的静态库
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

第三步:将生成的的静态库 libcalc.a 和库对应的头文件 head.h 一并发布给使用者就可以了。

# 3. 发布静态库
	1. head.h    => 函数声明
	2. libcalc.a => 函数定义(二进制格式)

1.3 静态库的使用

当我们得到了一个可用的静态库之后,需要将其放到一个目录中,然后根据得到的头文件编写测试代码,对静态库中的函数进行调用。

# 1. 首先拿到了发布的静态库
	`head.h` 和 `libcalc.a`
	
# 2. 将静态库, 头文件, 测试程序放到一个目录中准备进行测试
.
├── head.h          # 函数声明
├── libcalc.a       # 函数定义(二进制格式)
└── main.c          # 函数测试

编译测试程序,得到可执行文件。

# 3. 编译测试程序 main.c
$ gcc main.c -o app
/tmp/ccR7Fk49.o: In function `main':
main.c:(.text+0x38): undefined reference to `add'
main.c:(.text+0x58): undefined reference to `subtract'
main.c:(.text+0x78): undefined reference to `multiply'
main.c:(.text+0x98): undefined reference to `divide'
collect2: error: ld returned 1 exit status

上述错误分析:

编译的源文件中包含了头文件 head.h, 这个头文件中声明的函数对应的定义(也就是函数体实现)在静态库中,程序在编译的时候没有找到函数实现,因此提示 undefined reference to xxxx。

解决方案:在编译的时将静态库的路径和名字都指定出来

-L: 指定库所在的目录 (相对或者绝对路径) -l: 指定库的名字,需要掐头 (lib) 去尾 (.a) 剩下的才是需要的静态库的名字

# 4. 编译的时候指定库信息
	-L: 指定库所在的目录(相对或者绝对路径)
	-l: 指定库的名字, 掐头(lib)去尾(.a) ==> calc
# -L -l, 参数和参数值之间可以有空格, 也可以没有  -L./ -lcalc
$ gcc main.c -o app -L ./ -l calc

# 查看目录信息, 发现可执行程序已经生成了
$ tree
.
├── app   		# 生成的可执行程序
├── head.h
├── libcalc.a
└── main.c

2 动态库

动态链接库是程序运行时加载的库,当动态链接库正确部署之后,运行的多个程序可以使用同一个加载到内存中的动态库,因此在 Linux 中动态链接库也可称之为共享库。

动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址使用的是相对地址(静态库中使用的是绝对地址),其真实地址是在应用程序加载动态库时形成的。

关于动态库的命名规则如下:

  • 在 Linux 中动态库以 lib 作为前缀,以.so 作为后缀,中间是库的名字自己指定即可,即: libxxx.so

  • 在 Windows 中动态库一般以 lib 作为前缀,以 dll 作为后缀,中间是库的名字需要自己指定,即: libxxx.dll

2.1 生成动态链接库

生成动态链接库是直接使用 gcc 命令并且需要添加 -fPIC(-fpic) 以及 -shared 参数。

  • -fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
  • -shared参数的作用是告诉编译器生成一个动态链接库。

生成动态链接库的具体步骤如下:

1 将源文件进行汇编操作,需要使用参数 -c, 还需要添加额外参数 -fpic /-fPIC

# 得到若干个 .o文件
$ gcc 源文件(*.c) -c -fpic

2 将得到的.o 文件打包成动态库,还是使用 gcc, 使用参数 -shared 指定生成动态库 (位置没有要求)

$ gcc -shared 与位置无关的目标文件(*.o) -o 动态库(libxxx.so)

3 发布动态库和头文件

# 发布
 	1. 提供头文件: xxx.h
 	2. 提供动态库: libxxx.so

2.2 动态库制作举例

在此还是以上面制作静态库使用的实例代码为例来制作动态库,代码目录如下:

# 举例, 示例目录如下:
# 目录结构 add.c div.c mult.c sub.c -> 算法的源文件, 函数声明在头文件 head.h
# main.c中是对接口的测试程序, 制作库的时候不需要将 main.c 算进去
.
├── add.c
├── div.c
├── include
│   └── head.h
├── main.c
├── mult.c
└── sub.c

第一步:使用 gcc 将源文件进行汇编 (参数-c), 生成与位置无关的目标文件,需要使用参数 -fpic或者-fPIC

# 1. 将.c汇编得到.o, 需要额外的参数 -fpic/-fPIC
$ gcc add.c div.c mult.c sub.c -c -fpic -I ./include/

# 查看目录文件信息, 检查是否生成了目标文件
$ tree
.
├── add.c
├── add.o                # 生成的目标文件
├── div.c
├── div.o                # 生成的目标文件
├── include
│   └── head.h
├── main.c
├── mult.c
├── mult.o               # 生成的目标文件
├── sub.c
└── sub.o                # 生成的目标文件

第二步:使用 gcc 将得到的目标文件打包生成动态库,需要使用参数 -shared

# 2. 将得到 .o 打包成动态库, 使用gcc , 参数 -shared
$ gcc -shared add.o div.o mult.o sub.o -o libcalc.so  

# 检查目录中是否生成了动态库
$ tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│   └── `head.h   ===> 和动态库一起发布
├── `libcalc.so   ===> 生成的动态库
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

第三步:发布生成的动态库和相关的头文件

# 3. 发布库文件和头文件
	1. head.h
	2. libcalc.so

2.3 动态库的使用

当我们得到了一个可用的动态库之后,需要将其放到一个目录中,然后根据得到的头文件编写测试代码,对动态库中的函数进行调用。

# 1. 拿到发布的动态库
	`head.h   libcalc.so
# 2. 基于头文件编写测试程序, 测试动态库中提供的接口是否可用
	`main.c`
# 示例目录:
.
├── head.h          ==> 函数声明
├── libcalc.so      ==> 函数定义
└── main.c          ==> 函数测试

编译测试程序

# 3. 编译测试程序
$ gcc main.c -o app
/tmp/ccwlUpVy.o: In function `main':
main.c:(.text+0x38): undefined reference to `add'
main.c:(.text+0x58): undefined reference to `subtract'
main.c:(.text+0x78): undefined reference to `multiply'
main.c:(.text+0x98): undefined reference to `divide'
collect2: error: ld returned 1 exit status

错误原因:

和使用静态库一样,在编译的时候需要指定库相关的信息: 库的路径 -L 和 库的名字 -l

添加库信息相关参数,重新编译测试代码:

# 在编译的时候指定动态库相关的信息: 库的路径 -L, 库的名字 -l
$ gcc main.c -o app -L./ -lcalc

# 查看是否生成了可执行程序
$ tree
.
├── app 			# 生成的可执行程序
├── head.h
├── libcalc.so
└── main.c

# 执行生成的可执行程序, 错误提示 ==> 可执行程序执行的时候找不到动态库
$ ./app 
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

关于整个操作过程的报告:

gcc 通过指定的动态库信息生成了可执行程序,但是可执行程序运行却提示无法加载到动态库。

2.4 解决动态库无法加载问题

2.4.1 库的工作原理

  • 静态库如何被加载

    • 在程序编译的最后一个阶段也就是链接阶段,提供的静态库会被打包到可执行程序中。当可执行程序被执行,静态库中的代码也会一并被加载到内存中,因此不会出现静态库找不到无法被加载的问题。
  • 动态库如何被加载

    • 在程序编译的最后一个阶段也就是链接阶段:
      • 在 gcc 命令中虽然指定了库路径 (使用参数 -L ), 但是这个路径并没有记录到可执行程序中,只是检查了这个路径下的库文件是否存在。
      • 同样对应的动态库文件也没有被打包到可执行程序中,只是在可执行程序中记录了库的名字。
  • 可执行程序被执行起来之后:

    • 程序执行的时候会先检测需要的动态库是否可以被加载,加载不到就会提示上边的错误信息

    • 当动态库中的函数在程序中被调用了, 这个时候动态库才加载到内存,如果不被调用就不加载

    • 动态库的检测和内存加载操作都是由动态连接器来完成的

2.4.2 动态链接器

动态链接器是一个独立于应用程序的进程,属于操作系统,当用户的程序需要加载动态库的时候动态连接器就开始工作了,很显然动态连接器根本就不知道用户通过 gcc 编译程序的时候通过参数 -L 指定的路径。

那么动态链接器是如何搜索某一个动态库的呢,在它内部有一个默认的搜索顺序,按照优先级从高到低的顺序分别是:

  1. 可执行文件内部的 DT_RPATH 段

  2. 系统的环境变量 LD_LIBRARY_PATH

  3. 系统动态库的缓存文件 /etc/ld.so.cache

  4. 存储动态库 / 静态库的系统目录 /lib/, /usr/lib 等

按照以上四个顺序,依次搜索,找到之后结束遍历,最终还是没找到,动态连接器就会提示动态库找不到的错误信息。

2.4.3 解决方案

可执行程序生成之后,根据动态链接器的搜索路径,我们可以提供三种解决方案,我们只需要将动态库的路径放到对应的环境变量或者系统配置文件中,同样也可以将动态库拷贝到系统库目录(或者是将动态库的软链接文件放到这些系统库目录中)。

  • 方案 1: 将库路径添加到环境变量 LD_LIBRARY_PATH 中

    1. 找到相关的配置文件

      • 用户级别: ~/.bashrc —> 设置对当前用户有效

      • 系统级别: /etc/profile —> 设置对所有用户有效

    2. 使用 vim 打开配置文件,在文件最后添加这样一句话

      # 自己把路径写进去就行了
      export LIBRARY_PATH=$LIBRARY_PATH:动态库的绝对路径
    3. 让修改的配置文件生效

      • 修改了用户级别的配置文件,关闭当前终端,打开一个新的终端配置就生效了

      • 修改了系统级别的配置文件,注销或关闭系统,再开机配置就生效了

      • 不想执行上边的操作,可以执行一个命令让配置重新被加载

      # 修改的是哪一个就执行对应的那个命令
      # source 可以简写为一个 . , 作用是让文件内容被重新加载
      $ source ~/.bashrc          (. ~/.bashrc)
      $ source /etc/profile       (. /etc/profile)
  • 方案 2: 更新 /etc/ld.so.cache 文件

    1. 找到动态库所在的绝对路径(不包括库的名字)比如:/home/robin/Library/

    2. 使用 vim 修改 /etc/ld.so.conf 这个文件,将上边的路径添加到文件中 (独自占一行)

      # 1. 打开文件
      $ sudo vim /etc/ld.so.conf
      
      # 2. 添加动态库路径, 并保存退出
    3. 更新 /etc/ld.so.conf 中的数据到 /etc/ld.so.cache 中

      # 必须使用管理员权限执行这个命令
      $ sudo ldconfig   
  • 方案 3: 拷贝动态库文件到系统库目录 /lib/ 或者 /usr/lib 中 (或者将库的软链接文件放进去)

    # 库拷贝
    sudo cp /xxx/xxx/libxxx.so /usr/lib
    
    # 创建软连接
    sudo ln -s /xxx/xxx/libxxx.so /usr/lib/libxxx.so

2.4.4 验证

在启动可执行程序之前,或者在设置了动态库路径之后,我们可以通过一个命令检测程序能不能够通过动态链接器加载到对应的动态库,这个命令叫做 ldd

# 语法:
$ ldd 可执行程序名

# 举例:
$ ldd app
	linux-vdso.so.1 =>  (0x00007ffe8fbd6000)
    libcalc.so => /home/robin/Linux/3Day/calc/test/libcalc.so (0x00007f5d85dd4000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5d85a0a000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f5d85fd6000)  ==> 动态链接器, 操作系统提供

3 优缺点

3.1 静态库

  • 优点:

    • 静态库被打包到应用程序中加载速度快

    • 发布程序无需提供静态库,移植方便

  • 缺点:

    • 相同的库文件数据可能在内存中被加载多份,消耗系统资源,浪费内存
    • 库文件更新需要重新编译项目文件,生成新的可执行程序,浪费时间。

3.2 动态库

  • 优点:

    • 可实现不同进程间的资源共享
    • 动态库升级简单,只需要替换库文件,无需重新编译应用程序
    • 程序猿可以控制何时加载动态库,不调用库函数动态库不会被加载
  • 缺点:

    • 加载速度比静态库慢,以现在计算机的性能可以忽略
    • 发布程序需要提供依赖的动态库

4 视频讲解

以上知识点对应的视频讲解可以关注 B 站 - 爱编程的大丙 视频地址: https://www.bilibili.com/video/BV13U4y1p7kB

cmake和makefile区别和cmake指定编译器(cmake -G)

*一 cmake和makefile区别*

要说明区别,我们先要区分下面三类工具:

1.项目构建生成工具

首先cmake是项目构建生成工具,cmake的代码可以与平台系统和编译器无关。类似cmake的工具还有autotools、qmake、GN,其中qmake已基本由cmake替代。cmake下载地址 cmake.org.cn

也就是说cmake是用来生成makefile和其他项目生成工具配置的。

2.项目构建工具

类似make的工具有 Ninja 、nmake 、devenv(vs)。

makefile 可以理解为是make工具使用的代码,make读取makefile中的配置信息来实现编译、链接和部署。

3.项目编译链接工具

比如有:gcc、 cl.exe 、link 、ar

使用这些工具才能把源码和资源编译成执行程序和库。

下图时我画的各类工具之间的关系:

*二 为什么不直接使用项目编译链接工具?*

那为什么要有这三类工具,首先说下“项目编译链接工具” 只是使用这些工具其实就能够编译出所有的目标,但由于命令过于复杂,编译的流程不好控制。比如我们编译一个执行程序

g++ first_main.cpp -o first_main -I ../include -L../../lib -lpthread

这种方式在涉及到多个多项目、多库、多参数和各种依赖关系是维护难度很大。还有涉及到换编译器时,比如从g++换成clang或者cl.exe,也需要改动很多内容。

那如果换成make:

INCLUDE=-I../include

LIBS=-lpthread

first_main:first_main.o

${CC} -o $@ $< ${INCLUDE} ${LIBS}

依赖关系和所有目标都可以很清晰的管理。

*三 为什么不直接使用make或者Ninja*

其实很多公司和项目就是直接使用make的,但make代码规则严格,语法过于复杂,在做跨平台和跨编译器时的管理更加复杂,移植到不同环境的成本过大。这是就要引入cmake来生成make或者NInja的文件。Android 的NDK开发和鸿蒙native SDK都使用了cmake生成Ninja项目文件。

cmake本身配置就与环境和编译器完全无关了,可以由生成时指定。

cmake编译一个直接程序的配置也就一个函数add_executable的调用:

cmake_minimum_required(VERSION 3.20)

project(xlog)

add_executable(first_main first_main.cpp)

*四 cmake指定编译器(cmake -G)*

可以直接输入,可以看到当前版本支持的编译器

cmake -G

**1.*******CMake支持的编译器列表***

Visual Studio 17 2022 = Generates Visual Studio 2022 project files. Use -A option to specify architecture.

Visual Studio 16 2019 = Generates Visual Studio 2019 project files.

Use -A option to specify architecture.

Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.

Optional [arch] can be "Win64" or "ARM".

Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.

Optional [arch] can be "Win64" or "ARM".

Visual Studio 12 2013 [arch] = Generates Visual Studio 2013 project files.

Optional [arch] can be "Win64" or "ARM".

Visual Studio 11 2012 [arch] = Generates Visual Studio 2012 project files.

Optional [arch] can be "Win64" or "ARM".

Visual Studio 10 2010 [arch] = Deprecated. Generates Visual Studio 2010

project files. Optional [arch] can be

"Win64" or "IA64".

Visual Studio 9 2008 [arch] = Generates Visual Studio 2008 project files.

Optional [arch] can be "Win64" or "IA64".

Borland Makefiles = Generates Borland makefiles.

NMake Makefiles = Generates NMake makefiles.

NMake Makefiles JOM = Generates JOM makefiles.

MSYS Makefiles = Generates MSYS makefiles.

MinGW Makefiles = Generates a make file for use with

mingw32-make.

Green Hills MULTI = Generates Green Hills MULTI files

(experimental, work-in-progress).

Unix Makefiles = Generates standard UNIX makefiles.

Ninja = Generates build.ninja files.

Ninja Multi-Config = Generates build-.ninja files.

Watcom WMake = Generates Watcom WMake makefiles.

CodeBlocks - MinGW Makefiles = Generates CodeBlocks project files.

CodeBlocks - NMake Makefiles = Generates CodeBlocks project files.

CodeBlocks - NMake Makefiles JOM

= Generates CodeBlocks project files.

CodeBlocks - Ninja = Generates CodeBlocks project files.

CodeBlocks - Unix Makefiles = Generates CodeBlocks project files.

CodeLite - MinGW Makefiles = Generates CodeLite project files.

CodeLite - NMake Makefiles = Generates CodeLite project files.

CodeLite - Ninja = Generates CodeLite project files.

CodeLite - Unix Makefiles = Generates CodeLite project files.

Eclipse CDT4 - NMake Makefiles

= Generates Eclipse CDT 4.0 project files.

Eclipse CDT4 - MinGW Makefiles

= Generates Eclipse CDT 4.0 project files.

Eclipse CDT4 - Ninja = Generates Eclipse CDT 4.0 project files.

Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.

Kate - MinGW Makefiles = Generates Kate project files.

Kate - NMake Makefiles = Generates Kate project files.

Kate - Ninja = Generates Kate project files.

Kate - Unix Makefiles = Generates Kate project files.

Sublime Text 2 - MinGW Makefiles

= Generates Sublime Text 2 project files.

Sublime Text 2 - NMake Makefiles

= Generates Sublime Text 2 project files.

Sublime Text 2 - Ninja = Generates Sublime Text 2 project files.

Sublime Text 2 - Unix Makefiles

= Generates Sublime Text 2 project files. **2.*******主要的编译器指定示例***

a. 目录结构

first_cmake.cpp

CMakeList.txt

更换编译器需要删除原来的编译缓存路径build,或者换一个目录生成。

b. Visual Studio 编译器支持(生成项目文件和解决方案)

cmake -S . -B build -G "Visual Studio 17 2022"

cmake -S . -B build -G "Visual Studio 16 2019"

cmake -S . -B build -G "Visual Studio 15 2017"

cmake -S . -B build -G "Visual Studio 10 2010"

c. windows下nmake支持(vs控制台编译)

cmake -S . -B build -G "NMake Makefiles"

d. Ninja (安卓和鸿蒙方案)

cmake -S . -B build -G Ninja

更多编译器资料可以进入cmake.org.cn查看官方手册中的cmake-generators(7)内容


windows的vscode中我的头文件在demo/include下,demo/lib下存放库文件,但是库文件lib文件夹里面没有文件程序也没有引用库文件,demo/src下存放main.cpp和demo/include下的头文件的定义文件cpp,如何编译使得main.cpp输出可执行程序main.exe

以下是可能的解决方案:

在demo/src文件夹中,创建一个名为“Makefile”的文件(注意大小写),并将以下内容粘贴到文件中:

CPPFLAGS := -I../include
LDFLAGS := -L../lib
LDLIBS := -lmylib

main.exe: main.o
    $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@

main.o: main.cpp
    $(CXX) $(CPPFLAGS) -c $< -o $@

其中,CPPFLAGS指定头文件所在的目录,LDFLAGS指定库文件所在的目录,LDLIBS指定需要链接的库文件名(这里假设库文件名为“mylib.lib”)。main.exe是待生成的可执行文件名,main.o是编译好的目标文件名,main.cpp是源代码文件名。 $^表示所有的依赖文件(即main.o),$@表示目标文件(即main.exe),$<表示第一个依赖文件(即main.cpp),-c表示只编译源代码文件,不链接。

打开VS Code终端,在终端中进入到demo/src文件夹,然后输入以下命令:

make

这将自动根据Makefile文件编译源代码文件和库文件,并生成可执行文件main.exe。

在终端中输入以下命令运行可执行文件:

./main.exe

二、Linux下g++编译与使用静态库和动态库

//myAPI.h
int ADD(int a, int b);
int MINUS(int a, int b);

//myAPI.cpp
#include "myAPI.h"
 
int ADD(int a, int b){
    return a + b;
}
 
int MINUS(int a, int b){
    return a - b;
}

//main.cpp
#include "myAPI.h"
#include <iostream>
 
int main(){
    std::cout << "1 + 1 = " << ADD(1, 1) << std::endl;
    std::cout << "1 - 1 = " << MINUS(1, 1) << std::endl;
    return 0;
}
g++ -c myAPI.cpp
或者
g++ -o myAPI.o -c myAPI.cpp

1 生成静态库并使用

linux下用生成静态库的命令 ar 处理 myAPI.o 文件生成静态库文件,生成的库文件应遵循规范,及linux下库文件加“lib”前缀。所以针对本例,通过如下一条命令即可生成 libmyAPI.a 静态库:

ar crv libmyAPI.a myAPI.o

接下来即可在项目编译过程中利用静态库了,此时 myAPI.cpp 这个库函数的定义文件已经不需要了。main.cpp 编译命令如下(注意,依赖的静态库文件要放在被依赖项后面):

g++ main.cpp libmyAPI.a -o output

编译通过后即可运行可执行文件 output , 此时 libmyAPI.a 也已经是不需要的了。执行命令并输出结果如下:

./output

2 生成动态库并使用

linux下编译时通过 -shared 参数可以生成动态库(.so)文件,如下

g++ -shared -fPIC -o libmyAPI.so myAPI.o

生成的动态库在编译时需要声明,运行时需要被依赖。声明如下

g++ main.cpp -L. -lmyAPI -o output 

”-L.”标记告诉G++函数库可能位于当前目录;使用”-lmyAPI”标记来告诉G++ 驱动程序在连接阶段引用共享函数库libmyAPI.so。 使用时如果提示如下错误,则将 libmyAPI.so 移到 /usr/lib 目录下即可:

./output

三、windows下制作和使用lib和dll

lib文件是编译时期连接到应用程序中的,而dll文件才是运行时才会被调用的。 lib文件是用来告诉程序dll中有哪些信息而已

2.1 静态库和动态库的对比

静态库的优点很明显,想想平时使用的标准库的优点就行了,但缺点当然也很明显,静态库是直接嵌入到可执行程序中的,会导致生成的可执行文件体积较大。当初正是为了避免此问题,才开发了动态库技术。

(1)静态库(.lib)

函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE文件)。当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。

(2)动态库(.lib文件和.dll文件)

在使用动态库的时候,编译后往往提供两个文件:一个引入库(.lib)文件(也称“导入库文件”)和一个DLL(.dll)文件。当然到了后面会告诉你如果只提供一个DLL文件,使用显示连接的方式也可以调用,只是稍加麻烦而已。

虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质的区别。对一个DLL文件来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不可复制到可执行文件,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。这时,在发布产品时,除了发布可执行文件以外,同时还需要发布该程序将要调用的动态链接库。

只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。如果DLL不在内存中,系统就将其加载到内存中。当链接Windows程序以产生一个可执行文件时,你必须链接由编程环境提供的专门的 “引入库(import library)”。这些引入库包含了动态链接库名称和所有Windows函数调用的引用信息。链接程序使用该信息在.EXE文件中构造一个表,当加载程序时,Windows使用它将调用转换为Windows函数。

引入库LIb和静态库Lib的区别:

引入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于引入库而言,其实际的执行代码位于动态库中,引入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。但是引入库文件的引入方式和静态库一样,要在链接路径上添加找到这些.lib的路径。

1.3 动态库(Lib和DLL)和静态库Lib的区别:

其实上面已经提到了区别,这里再总结一下: (1)静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了,最终的可执行文件exe会比较大。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。 (2)静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接 库。静态链接库与静态链接库调用规则总体比较如下。

静态链接库运行之前就加载了,而且一直存在直到关闭程序,动态DLL实在运行时再加载,不用一直占内存,dll模块内部更改了,是要替换Dll即可,方便维护。优点明显,但是dll如果丢失或被误删,就无法运行了

DLL即动态链接库(Dynamic-Link Library)的缩写,相当于Linux下的共享对象。Windows系统中大量采用DLL机制,甚至内核的结构很大程度依赖于DLL机制。Windows下的DLL文件和EXE文件实际上是一个概念,都是PE格式的二进制文件。一般的动态库程序有lib文件和dll文件,lib文件是编译时期连接到应用程序中的,而dll文件才是运行时才会被调用的。

为了更好的理解DLL,首先要有导入和导出的概念。 a. 在ELF(Linux下动态库的格式),共享库中所有的全局函数和变量在默认情况下都可以被其它模块使用,即ELF默认导出所有的全局符号。DLL不同,需要显示地“告诉”编译器需要导出某个符号,否则编译器默认所有的符号都不导出。 b. 程序使用DLL的过程其实是引用DLL中导出函数和符号的过程,即导入过程。对于从其他DLL导入的符号,需要使用“_declspec(dllimport)”显示声明某个符号作为导入符号。在ELF中,使用外部符号时,不需要额外声明该符号从其他共享对象导入的。

MSVC编译器提供了一系列C/C++的扩展**来指定符号的导入导出,即*_declspec属性关键字***。 a. _declspec(dllexport)表示该符号是从本DLL导出的符号 b. _declspec(dllimport)表示该符号是从别的DLL中导入的 使用".def"文件来声明导入和导出符号,详细参考http://www.cnblogs.com/enterBeijingThreetimes/archive/2010/08/04/1792099.html

2.2 windows下创建静态库

1、我们先新建一个项目,选择win32项目

img

2、在接下来的应用程序向导中选择静态库并去掉预编译头的勾。

img

3、创建头文件(.h)以及源文件(.cpp),它们的格式如下:

//MyLib.h
#pragma once
#ifndef _STATIC_LIB_H_
#define _STATIC_LIB_H_
 
#include <iostream>
 
namespace STATIC_LIB
{
	void PrintHello();  //测试函数
}
 
#endif


//MyLib.cpp
#include "MyLib.h"
 
void STATIC_LIB::PrintHello()
{
	std::cout << "Hello world!" << std::endl;
}

4、然后,我们选择release,生成解决方案(这么简单的函数应该不用调试了吧)

img

5、随后,我们可以在自定的项目路径下的release文件夹下看到生成的lib文件。

img

2.3 windows下创建动态库

动态库的创建和静态库类似

1、在win32向导中选择dll,并选择空项目创建。

2、建立如图几个文件

img

3、各个文件中的内容如下:

//MyDll.h
#pragma once
#ifndef _DLL_TEST_H_
#define _DLL_TEST_H_
 
#include <iostream>
 
namespace DLL_TEST
{
	void PrintHello();
}
 
#endif // !_DLL_TEST_H_

//MyDll.cpp
#include "MyDll.h"
 
void DLL_TEST::PrintHello()
{
	std::cout << "Hello world!" << std::endl;
}

//MyDll.def
LIBRARY MyDll
 
EXPORTS 
PrintHello

这里对模块定义做一些说明(最后一个文件),LIBRARY后跟的是*项目名,*EXPORTS后是这个dll中需要导出的函数名。

2.4 windows下的vscode中创建静态库和动态库

在 VSCode 中,导出库和动态链接库(DLL)的方法与在 Visual Studio 中略有不同。

导出静态库(.lib):

在工作区中打开要与静态库一起编译的源代码文件和头文件。
在 VSCode 中,使用终端运行以下命令:
cl /c /EHsc source.cpp header.h
lib /OUT:library.lib source.obj header.obj
这些命令将编译源代码文件和头文件,并将它们打包成一个静态库。
-------------------------------------------------------
导出动态链接库(.dll):

在工作区中打开要与 DLL 一起编译的源代码文件和头文件。
在 VSCode 中,使用终端运行以下命令:
cl /c /EHsc /LD source.cpp header.h
link /OUT:dllname.dll /DLL source.obj header.obj
这些命令将编译源代码文件和头文件,并将它们打包成一个动态链接库。
在导出库和 DLL 之前,您需要安装并配置所需的编译环境和构建工具链。
在 VSCode 中,导出静态库和动态库的方法与在 Visual Studio 中略有不同。

----静态库
 a. 在工作区中打开要与静态库一起编译的源代码文件和头文件。
 b. 打开终端并使用以下命令编译源代码文件和头文件:
  cl /EHsc /c source.cpp header.h 
 c. 再使用以下命令将生成的 .obj 文件打包成一个静态库:
  lib /OUT:library.lib source.obj header.obj 
 这将创建一个名为 library.lib 的静态库。
 通过命令行指定生成的库文件路径和名称,可以在两个命令中都使用 /OUT 参数。例如,以下命令将生成一个名为 mylib.lib 的库文件,并将其放置在指定的目录中:
 cl /EHsc /c source.cpp header.h /Fo:\path\to\output\mylib.obj
lib /OUT:\path\to\output\mylib.lib source.obj header.obj

----动态库
 a. 在工作区中打开要与 DLL 一起编译的源代码文件和头文件。
 b. 打开终端并使用以下命令编译源代码文件和头文件:
  cl /EHsc /LD /c source.cpp header.h 
 c. 再使用以下命令将生成的 .obj 文件打包成一个动态链接库:
  link /DLL /OUT:dllname.dll source.obj header.obj 
 这将创建一个名为 dllname.dll 的动态链接库。
 同样,通过命令行指定生成的库文件路径和名称,可以在两个命令中都使用 /OUT 参数。例如,以下命令将生成一个名为 mydll.dll 的 DLL 文件,并将其放置在指定的目录中:
 cl /EHsc /LD /c source.cpp header.h /Fo:\path\to\output\mydll.obj
link /DLL /OUT:\path\to\output\mydll.dll source.obj header.obj
注意:在上述命令中,/OUT 参数和指定的路径应该是文件名和路径的组合。例如,/OUT:mylib.lib 表示将库文件命名为 mylib.lib。如果只指定目录,系统会自动命名文件。

2.5 静态库xxxx.lib的调用

静态库的第一种使用方法

1、我们先创建个普通的win32控制台程序,选择空项目即可。

2、然后我们把上面创建lib时的头文件包含到新创建的控制台程序中:

img

3、然后在源程序中写如下格式的代码调用即可:

#include <iostream>
#include "MyLib.h"
 
#pragma comment(lib,"MyLib.lib") // 它搜索的路径就是在与项目文件名同名的文件夹下,
								 //所以我们可以直接将lib文件复制到该文件夹中,当然我们也可以使用绝对路径
 
int main()
{
	STATIC_LIB::PrintHello();
 
	system("pause");
	return 0;
}

img

(还需注意的是,刚才我们创建的lib是在release环境下创建出来的,此时调用也应在release环境下,不要出现鸡同鸭讲的情况,当然,你也可以都是在debug环境下环境下创建的。)

静态库的第二种使用方法

1、我们创建一个include文件夹和lib文件夹用来存放头文件和lib文件:

img

2、项目名那右击,选择属性,在弹出的属性页窗口中选择VC++目录,然后添加进这两个文件夹

img

3、将先前创建lib时的头文件和lib文件分别复制到这两个文件夹中:

img

img

4、接着,再在属性页面中选择链接器--输入--附加依赖项中添加我们的lib

img

5、准备工作完成,在我们调用的程序中使用如下格式的代码即可调用:

#include <iostream>
#include "MyLib.h"
 
int main()
{
	STATIC_LIB::PrintHello();
 
	system("pause");
	return 0;
}

静态库的第二种使用方法(另一个版本)

静态lib中,一个lib文件实际上是任意个obj文件的集合,obj文件是cpp文件编译生成的。静态库的.lib文件包含了链接库的所有信息(函数代码和接口信息)。所以我们在调用静态库.lib时,只需要包含头文件目录(../include. .h),以及附加库目录即可。因此,静态链接库的使用需要库的开发者提供生成库的.h头文件和.lib文件

在VC中新建一个static library类型的工程TestLib,加入test.cpp文件和test.h文件(头文件内包括函数声明),然后编译,就生成了TestLib.lib文件。

别的工程要使用这个lib方式:

(1)添加lib 方法1):直接用”项目右击”->”添加”–>”现有项”–>选择lib文件–>确定,通过这种方式将.lib加入工程 方法2):工程属性-> 配置属性->链接器->输入->附加依赖项中添加要使用的Lib库的名字;在工程属性-> 配置属性->链接器->输入->附加库目录中输入.lib文件所在路径(相对或绝对路径)

方法3):或者在源代码中加入指令#pragma comment(lib, “TestLib.lib”),也可以指定完整路径(绝对路径或相对路径)#pragma comment(lib, “..\Debug\TestLib.lib”)。可以通过宏#if defined(_DEBUG)来区分用release或debug版本的lib。另外这里如果不指定完整路径,也要像方法2一样添加附加库目录。

如果不在工程属性中添加附加lib库目录,也可以将静态里边库比如TestLib.lib拷贝到工程所在目录,或者拷贝到执行文件生成的目录,或者拷贝到系统Lib目录中。

(2). 添加头文件

加入相应的头文件test.h。#include “test.h”

include file path可以为绝对路径,也可以为相对于工程所在目录的相对路径,如果头文件比较多,可以在project>settings>c/c++>preprocessor的Additional include directories中填入你的头文件所在目录,编译时会自动在此目录查找头文件。

2.6 动态库xxxx.dll的调用

动态链接库的使用需要库的开发者提供生成的.lib文件和.dll文件。或者只提供dll文件。使用时只能使用dll中导出的函数,未导出的函数只能在dll内部使用。Dll的调用有显示连接和隐式连接两种:隐式连接需要三个东西,分别是*.h头文件,lib库(动态的),DLL库;显示连接只需要.dll文件即可。

1. 隐式调用

隐式链接需要三个东西,分别是*.h头文件,lib库(动态的),DLL库,而这里的lib库仅是编译的时候用,运行时候不用,运行时只用Dll

(1). 格式如下:#pragma comment(lib,"DllTest.lib");

在调用程序内必须写上#include"DllTest.h"

(2). 隐式调用时,调用该dll的程序只能是C或C++

采用隐式调用上面生成的dll文件的代码示例如下图所示:

#include <iostream>
#include "Mylib.h"                                // 工程配置里面设置头文件包含路径

//#pragma comment(lib,"../Debug/staticlib.lib")   // 指定路径,当前路径为vcxproj文件所在路径
//#pragma comment(lib,"staticlib.lib")            //工程配置里面可以配置,就可以不填写路径了;(链接器附加库目录中配置)
// 可以在visual studio里面配置
using namespace std;
int main(int argc, char **argv)
{
	STATIC_LIB::PrintHello();
	system("pause");
	return 0;
}

img

添加lib

方法1: 通过设置工程配置来添加lib库.

A、添加工程的头文件目录:工程->属性->配置属性->c/c++->常规->附加包含目录:加上头文件存放目录。添加头文件参考2.2.1.2 B、添加文件引用的lib静态库路径:工程->属性->配置属性->链接器->常规->附加库目录:加上lib文件存放目录。 C 然后添加工程引用的lib文件名:工程->属性->配置属性->链接器->输入->附加依赖项:加上lib文件名。

这种方法比较繁琐,且不直观,而且还可能要争对debug版本和release版本作不同的配置,因为我们生成的两个版本的库可能放在不同的目录中的.

方法2: 使用编译语句:

#ifdef _DEBUG
#pragma comment(lib,"..\\debug\\LedCtrlBoard.lib")
#else
#pragma comment(lib,"..\\release\\LedCtrlBoard.lib")
#endif

这种方法直观,方便,且可以按如上直接区分出Debug版本和Release版本的不同目录.当然,通过宏,还可以区分更多版本.但是在指定目录时,不小心容易出错.

方法3: 直接添加库文件到工程中.

就像你添加.h和.cpp文件一样,把lib文件添加到工程文件列表中去. VC中,切换到”解决方案视图”—>选中要添加lib的工程–>点击右键–>”添加”–>”现有项”–>选择lib文件–>确定. 这个方法适用于在我的工程的debug版本和Release版本中都使用同一个lib库文件时.这样就省去了你1方法配置环境的繁琐,也省去了方法2种语句的可能性错误发生.。

添加头文件

在调用DLL中导出的函数之前要include对应的头文件,可以写绝对路径,也可以拷贝到工程调用源文件的同一目录下,也可以通过VS添加(include)头文件目录,VS中配置方法: (1)VS项目->右击属性->配置属性->VC++目录->包含目录 (2)VS项目->右击属性->配置属性->C/C++->常规->附加包含目录

添加dll

一般将dll拷贝到运行时目录即可,与调用者exe文件在同一目录,当然有其他方法添加环境变量PATH, (1)VS项目->右击属性->配置属性->VC++目录->可执行目录 (2)设定DLL目录的位置,具体方法为:项目右击->属性 -> 配置属性 -> 调试 ->工作目录,在这里设置dll的路径就可以了

注1:release版本和debug版本的区分,每种版本的设置都是独立的,要分别设置 注2:单纯添加lib目录的方法有以下几种方法

(1):把.lib文件放在当前使用它的工程目录下;(如:.exe所在目录,或者工程代码所在的目录) (2):对某一个项目:项目”->“属性”->“配置属性”->“VC++目录”->“常规”->“附加库目录” (3):在vs中,“项目”->“属性”->“配置属性”->“链接器”->“常规”->“附加库目录” (4):放在开发环境IDE的lib库目录下,例如:“C:\Program Files\Microsoft Visual Studio 8\VC\lib”,这是vs2005的vc开发的lib库目录。

注:在VS属性中配置路径时可以用绝对路径,也可以用相对路径,其中./表示当前目录,而../表示上一级目录

上面仅仅对单个项目有效,我们配置过opencv库的都知道,有一种是全局配置lib和include头文件,对所有的项目有效,以Debug版为例,步骤如下: (1) 点击“视图”→“其他窗口”→“属性管理器” (2) 从左侧项目中打开“Debug| Win32”→“Microsoft.Cpp.Win32.user” (3) 双击“Microsoft.Cpp.Win32.user”,在弹出的窗口,点击左侧VC++目录,编辑右侧的可执行文件目录、包含目录与库目录,分别添加对应的路径 (4) 附加依赖项,单击“链接器”→“输入”→“附加依赖项”,填入依赖项.lib后缀的文件名。

2. 显示调用:

隐式链接虽然实现较简单,但除了必须的.dll文件外还需要DLL的.h文件和.lib文件,在那些只提供.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,能更加有效地使用内存,在编写大型应用程序时往往采用此方式。这种方法编程具体实现步骤如下:

①使用Windows API函数Load Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。

②使用GetProcAddress函数得到要调用DLL中的函数的指针。

③不用DLL时,用Free Library函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。

使用LoadLibrary显式链接,那么在函数的参数中可以指定DLL文件的完整路径;如果不指定路径,或者进行隐式链接,Windows将遵循下面的搜索顺序来定位搜索DLL:

包含EXE文件的目录 工程目录 Windows系统目录 Windows目录 列在Path环境变量中的一系列目录

显示调用需要先用Loadlibrary函数加载dll文件,然后用GetProcAddress找到该dll的地址,采用显示调用dll的示例代码如下图所示:

#include <iostream>
#include <windows.h> // 显示调用借助了windows库
 
int main()
{
	// TODO: Add your control notification handler code here
	HINSTANCE hInst;
	hInst = LoadLibrary(L"MyDll.dll");
	typedef void(*Sub)();//函数指针
	Sub PrintHello = (Sub)GetProcAddress(hInst, "PrintHello");//从dll中加载函数出来
 
	PrintHello();//运行函数
 
	FreeLibrary(hInst);       //LoadLibrary后要记得FreeLibrary
 
	system("pause");
	return 0;
}
 

​ 显示调用:不需要使用引入库.lib文件,也不需要include头文件,直接使用函数加载库:

#include <iostream>
//#include "Mylib.h"  // 不用include头文件;
#include <windows.h>
using namespace std;
typedef void(*func)(void);
int main(int argc, char **argv)
{
	HMODULE hDll = LoadLibrary("staticlib.dll");
	if (hDll != NULL)
	{
		func f = (func)GetProcAddress(hDll, "PrintHello");  // 名字要一致,否则返回空指针;
		if (f != NULL)
		{
			f();
		}
	}
	FreeLibrary(hDll);
	system("pause");
	return 0;
}
img

在这里插入图片描述

从以上可知:制作dll有两种方式,调用dll也有两种方式。这样在使用时就有4种组合方式。

以下给出了这四种组合方式使用方法和示例代码。

上面讲到调用时有4种组合方式,下面具体来讲一讲这四种方式的使用方法(末尾给出代码示例链接):

方式一、__declspec(dllexport)制作方式和隐式调用相合

步骤一:首先将dll制作好后的头文件、dll文件、lib文件拷贝到调用该dll的工程中。

步骤二:用教程一中隐式调用的方法将dll加载到该工程内即可。

方式二、__declspec(dllexport)制作方式和显式调用相合

注意:这种方式必须在制作dll的头文件内,在导出函数前添加上extern "C"。因为如果不加的话,C++编译器考虑到函数的重载,会将dll中的函数名进行更改(可以使用dumpbin命令验证添加extern "C"前后是否发生变化)。这导致在使用dll过程中,使用GetProceAddress查找导出函数时必须使用更改后的函数名,很不方便。

步骤一:只需将dll制作好后的dll文件拷贝到调用该dll的工程中。

步骤二:用教程一中的显示调用的方法将dll加载到该工程即可。

方式三、def制作方式和隐式调用结合

步骤一:将制作好的dll文件和lib文件拷贝到调用该dll的工程中。

步骤二:由于def方式没有头文件,所以在调用程序中首先声明导出函数,再按照教程一中的隐式调用方法进行调用即可。

方式四、def制作方式和显式调用结合

注:该方式不会出现方式二中所说的函数名被更改的问题

步骤一:将dll制作好后的dll文件拷贝到调用该dll的工程中。

步骤二:用教程一中的显示调用的方法将dll加载到该工程即可。


cmake如何debug和release编译

你好,这是Bing。根据搜索结果,你可以使用以下方法来进行cmake的debug和release编译:

  • 你可以在cmake命令中使用-DCMAKE_BUILD_TYPE=Debug-DCMAKE_BUILD_TYPE=Release来指定编译类型¹。例如:
    cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Debug
    cmake --build build/
  • 你也可以在CMakeLists.txt文件中使用SET (CMAKE_CXX_FLAGS_DEBUG ...)SET (CMAKE_CXX_FLAGS_RELEASE ...)来设置debug和release模式下的编译选项²。例如:
    SET (CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
    SET (CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
  • 如果你想要查看编译过程中的详细信息,你可以在cmake命令中添加-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON选项,它将永久地启用所有生成的makefile³。例如:
    cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON
    cmake --build build/

希望这些信息对你有帮助。😊

源: 与必应的对话, 2023/6/8 (1) c++ - Debug vs Release in CMake - Stack Overflow. https://stackoverflow.com/questions/7724569/debug-vs-release-in-cmake. (2) makefile[一]:编译选项,debug/release版本 - CSDN博客. https://blog.csdn.net/lqy971966/article/details/105146726. (3) CMake如何添加编译选项(debug和release分别选择对应的库)? - 知乎. https://www.zhihu.com/question/423254770.


windows下cmake如何实现debug和release编译

你好,这是Bing。根据搜索结果,你可以使用以下方法来在Windows下进行cmake的debug和release编译:

  • 你可以在cmake命令中使用-G选项来指定生成器类型,例如-G "Visual Studio 16 2019"-G "Ninja"¹。不同的生成器类型支持不同的配置方式,例如Visual Studio支持多配置,而Ninja支持单配置²。
  • 如果你使用多配置生成器,你可以在cmake命令中使用-A选项来指定平台类型,例如-A x64-A Win32¹。然后你可以在cmake --build命令中使用--config选项来指定编译类型,例如--config Debug--config Release¹。例如:
    cmake -S . -B build/ -G "Visual Studio 16 2019" -A x64
    cmake --build build/ --config Debug
  • 如果你使用单配置生成器,你可以在cmake命令中使用-DCMAKE_BUILD_TYPE=Debug-DCMAKE_BUILD_TYPE=Release来指定编译类型²。然后你可以直接使用cmake --build命令来编译²。例如:
    cmake -S . -B build/ -G "Ninja" -DCMAKE_BUILD_TYPE=Debug
    cmake --build build/
  • 如果你想要查看编译过程中的详细信息,你可以在cmake命令中添加-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON选项,它将永久地启用所有生成的makefile³。例如:
    cmake -S . -B build/ -G "Ninja" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON
    cmake --build build/

希望这些信息对你有帮助。😊

源: 与必应的对话, 2023/6/8 (1) cmake引入第三方库的debug和release版本之Windows版本 - mohist - 博客园. https://www.cnblogs.com/pandamohist/p/14466881.html. (2) c++ - Debug vs Release in CMake - Stack Overflow. https://stackoverflow.com/questions/7724569/debug-vs-release-in-cmake. (3) CMake如何添加编译选项(debug和release分别选择对应的库)? - 知乎. https://www.zhihu.com/question/423254770.


windows下cmake如何通过设置CMakeLists.txt实现debug和release编译

你好,这是Bing。根据搜索结果,你可以使用以下方法来通过设置CMakeLists.txt实现Windows下的debug和release编译:

  • 你可以在CMakeLists.txt文件中使用set(CMAKE_BUILD_TYPE Debug)set(CMAKE_BUILD_TYPE Release)来设置编译类型¹。例如:
    set(CMAKE_BUILD_TYPE Debug)
  • 你也可以在执行cmake命令时使用-DCMAKE_BUILD_TYPE=Debug-DCMAKE_BUILD_TYPE=Release来指定编译类型²。例如:
    cmake -S . -B build/ -DCMAKE_BUILD_TYPE=Debug
  • 如果你使用Visual Studio作为生成器,你可以在CMakeLists.txt文件中使用set(CMAKE_CONFIGURATION_TYPES Debug Release)来指定支持的配置类型³。然后你可以在Visual Studio中选择不同的配置来编译³。例如:
    set(CMAKE_CONFIGURATION_TYPES Debug Release)

希望这些信息对你有帮助。😊

源: 与必应的对话, 2023/6/8 (1) 在 Visual Studio 中自定义 CMake 生成设置 | Microsoft Learn. https://learn.microsoft.com/zh-cn/cpp/build/customize-cmake-settings?view=msvc-170. (2) Visual Studio 中的 CMake 项目 | Microsoft Learn. https://learn.microsoft.com/zh-cn/cpp/build/cmake-projects-in-visual-studio?view=msvc-160. (3) CMake应用:CMakeLists.txt完全指南 - 知乎 - 知乎专栏. https://zhuanlan.zhihu.com/p/371257515.


cmake -G "MinGW Makefiles" .. 和 cmake -S . -B build/ -G "Visual Studio 16 2019" -A x64分别是什么意思,有什么区别,并指明每个参数的含义。

你好,这是Bing。😊

这两个命令都是用来生成Makefile文件的,但是使用了不同的生成器(generator)和选项(option)。生成器是CMake用来指定构建工具的参数,选项是用来指定一些额外的设置的参数。¹²

cmake -G "MinGW Makefiles" .. 的意思是:

  • -G "MinGW Makefiles":指定生成器为MinGW Makefiles,这个生成器适用于在Windows命令提示符下使用mingw32-make作为构建工具的情况。¹
  • ..:指定源代码目录为当前目录的上一级目录。

cmake -S . -B build/ -G "Visual Studio 16 2019" -A x64 的意思是:

  • -S .:指定源代码目录为当前目录。
  • -B build/:指定构建目录为当前目录下的build子目录。
  • -G "Visual Studio 16 2019":指定生成器为Visual Studio 16 2019,这个生成器适用于使用Visual Studio 2019作为构建工具的情况。²
  • -A x64:指定平台名称为x64,这个选项只有在使用Visual Studio生成器时才有效,用来指定目标架构。²

区别在于:

  • 第一个命令使用了MinGW作为构建工具,第二个命令使用了Visual Studio作为构建工具。
  • 第一个命令没有指定构建目录,所以默认在源代码目录下生成Makefile文件,第二个命令指定了构建目录为build子目录,所以在那里生成Makefile文件。
  • 第一个命令没有指定平台名称,所以默认使用当前系统的架构,第二个命令指定了平台名称为x64,所以生成适用于64位系统的Makefile文件。

希望这能帮到你。😊

源: 与必应的对话, 2023/6/8 (1) MinGW Makefiles — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/generator/MinGW%20Makefiles.html. (2) c++ - CMake & MinGW Compilation on Windows, without needing the -G .... https://stackoverflow.com/questions/59095842/cmake-mingw-compilation-on-windows-without-needing-the-g-mingw-makefiles-f. (3) c++ - Compile CMake based project with minGW - Stack Overflow. https://stackoverflow.com/questions/43413717/compile-cmake-based-project-with-mingw.


一般的windows环境下用的编辑器是vscode 在CMakeLists.txt中设置set CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE都是如何设置的。

你好,这是Bing。😊

在CMakeLists.txt中设置set CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE的方法和在其他平台下没有太大区别,只需要在project命令之后,使用set命令来指定相应的编译标志即可。¹ 例如:

project(my_project)
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

这样就可以设置Debug模式和Release模式下的编译标志。²

不过,如果你使用的是Visual Studio作为编译器,那么你可能需要注意一些特殊的选项,比如/MT或者/MD,它们用来指定是否使用静态链接或者动态链接的运行时库。³ 如果你想使用/MT选项,那么你可以在CMakeLists.txt中添加以下代码:

set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")

或者使用string命令来替换/MD为/MT:

string(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
string(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})

希望这能帮到你。😊

源: 与必应的对话, 2023/6/8 (1) How to set cmake CXX variable on windows - Stack Overflow. https://stackoverflow.com/questions/31629966/how-to-set-cmake-cxx-variable-on-windows. (2) Set CFLAGS and CXXFLAGS options using CMake - Stack Overflow. https://stackoverflow.com/questions/10085945/set-cflags-and-cxxflags-options-using-cmake. (3) Compile with /MT instead of /MD using CMake - Stack Overflow. https://stackoverflow.com/questions/14172856/compile-with-mt-instead-of-md-using-cmake.


以下每条指令什么意思,并把指定的参数的含义也说出来。

project(my_project) set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb") set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")

你好,这是Bing。😊

这是你的CMakeLists.txt中的几条指令的意思和参数的含义:

  • project(my_project):指定项目的名称为my_project,这个命令会创建一些变量,比如my_project_BINARY_DIR和my_project_SOURCE_DIR。¹
  • set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb"):设置Debug模式下的C++编译标志为环境变量CXXFLAGS的值加上-O0 -Wall -g -ggdb,其中-O0表示不优化代码,-Wall表示显示所有警告,-g表示生成调试信息,-ggdb表示生成适合gdb调试器的调试信息。²³
  • set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall"):设置Release模式下的C++编译标志为环境变量CXXFLAGS的值加上-O3 -Wall,其中-O3表示开启最高级别的优化,-Wall表示显示所有警告。²³
  • set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT"):在Release模式下的C++编译标志后面追加/MT选项,这个选项表示使用静态链接的运行时库。
  • set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd"):在Debug模式下的C++编译标志后面追加/MTd选项,这个选项表示使用静态链接的调试运行时库。

希望这能帮到你。😊

源: 与必应的对话, 2023/6/8 (1) Changing CMAKE_CXX_FLAGS in project - Stack Overflow. https://stackoverflow.com/questions/15100351/changing-cmake-cxx-flags-in-project. (2) Set CFLAGS and CXXFLAGS options using CMake - Stack Overflow. https://stackoverflow.com/questions/10085945/set-cflags-and-cxxflags-options-using-cmake. (3) How to pass C/CXX flags to an external project in CMake. https://stackoverflow.com/questions/56342306/how-to-pass-c-cxx-flags-to-an-external-project-in-cmake.


执行mingw32-make.exe遇到错误,缺少main函数。

32_a-crt0_c.o):crt0_c.c:(.text.startup+0x2e): undefined reference to `WinMain' collect2.exe: error: ld returned 1 exit status

我没有看到你有指定你的main文件作为源文件,你只是指定了一个目录。你可以使用aux_source_directory命令来自动收集目录下的所有源文件,或者使用set命令来手动列出你的源文件。例如:

aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST) # 自动收集src目录下的所有源文件
# 或者
set(SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp) # 手动指定main.cpp作为源文件

或者

你好,这是Bing。😊

这个错误的原因可能是你的程序缺少入口函数,也就是main函数或者WinMain函数。¹ 你可以检查一下你的源文件中是否有定义这些函数,或者是否有使用正确的编译选项来指定程序的子系统类型,比如-mwindows或者-Wl,-subsystem,windows。²

如果你的程序是一个控制台应用程序,那么你需要定义一个main函数,例如:

int main()
{
    // your code here
    return 0;
}

如果你的程序是一个图形界面应用程序,那么你需要定义一个WinMain函数,例如:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // your code here
    return 0;
}

并且在编译时使用-mwindows或者-Wl,-subsystem,windows选项来告诉链接器你的程序是一个图形界面应用程序。³

希望这能帮到你。😊

源: 与必应的对话, 2023/6/8 (1) Undefined reference to WinMain (C++ MinGW) - Stack Overflow. https://stackoverflow.com/questions/58324230/undefined-reference-to-winmain-c-mingw. (2) c++ - undefined reference to WinMain@16' - Stack Overflow. https://stackoverflow.com/questions/5259714/undefined-reference-to-winmain16. (3) I got a error message "undefined reference to WinMain'". https://stackoverflow.com/questions/67514081/i-got-a-error-message-undefined-reference-to-winmain.

aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST) # 自动收集src目录下的所有源文件 # 或者 set(SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp) # 手动指定main.cpp作为源文件 可以同时指定吗?

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,aux_source_directory命令用于收集指定目录下的所有源文件的文件名,并存储在一个变量中。这个命令主要用于那些使用显式模板实例化的工程,可以避免手动列出所有的实例文件。但是,这个命令也有一些缺点,比如:

  • 它不能感知新的源文件何时被加入,需要手动重新运行CMake来更新构建系统²。
  • 它不能包含头文件,如果头文件中有函数的实现部分,可能会出现问题⁵。
  • 它不能指定源文件的顺序,如果有些源文件之间有依赖关系,可能会导致链接错误⁴。

所以,如果你想同时指定某些源文件和自动收集某个目录下的所有源文件,你可以使用set命令和aux_source_directory命令结合,比如:

set(SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp) # 手动指定main.cpp作为源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST) # 自动收集src目录下的所有源文件

这样,SRC_LIST变量就包含了main.cppsrc目录下的所有源文件。但是,这种方法也不是最佳实践,因为它仍然存在上述的缺点。更好的方法是使用file(GLOB ...)命令来指定源文件的通配符模式,比如:

file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) # 收集src目录下的所有.cpp文件

这样,SRC_LIST变量就只包含了src目录下的所有.cpp文件。这种方法可以更灵活地控制源文件的选择和顺序,而且可以避免一些潜在的问题。当然,它也有一些局限性,比如:

  • 它不能递归地收集子目录下的源文件,需要使用file(GLOB_RECURSE ...)命令⁴。
  • 它不能自动更新构建系统,需要使用CMAKE_CONFIGURE_DEPENDS属性³。

总之,使用哪种方法来指定源文件取决于你的具体需求和偏好。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) aux_source_directory — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/command/aux_source_directory.html. (2) CMAKE(3)—— aux_source_directory包含目录下所有文件以及自动构建系统. https://blog.csdn.net/u012564117/article/details/95085360. (3) cmake的aux_source_directory_物随心转的博客-CSDN博客. https://blog.csdn.net/sinat_31608641/article/details/122373670. (4) cmake学习总结(二) - 知乎 - 知乎专栏. https://zhuanlan.zhihu.com/p/153990002. (5) CMake中aux_source_directory的使用-CSDN博客. https://blog.csdn.net/fengbingchun/article/details/128257838.


cmake 如何通过设置CMakeLists.txt 指定build目录自动生成的目录的位置

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,如果你想让CMake生成所有的输出文件(如Makefile等)到一个指定的构建目录,而不是源代码目录,你可以使用out of source build的方式,即在构建目录下运行CMake,并指定源代码目录作为参数,比如:

mkdir build # 创建构建目录
cd build # 进入构建目录
cmake ../src # 运行CMake并指定源代码目录

这样,CMake就会在build目录下生成所有的输出文件,而不会修改src目录下的任何文件。这种方式有很多优点,比如:

  • 可以保持源代码目录的干净整洁,不会产生临时文件或缓存文件。
  • 可以方便地删除或重建构建目录,而不影响源代码。
  • 可以支持多个构建目录,比如用于不同的编译器或配置。

如果你想在CMake脚本中设置构建目录的位置,你可以使用一些变量来控制输出文件的路径,比如:

  • CMAKE_ARCHIVE_OUTPUT_DIRECTORY:用于设置静态库文件的输出路径。
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY:用于设置动态库文件的输出路径。
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY:用于设置可执行文件的输出路径。

这些变量都可以设置为全局的或者针对某个目标的。例如:

# 设置全局的输出路径
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

# 设置某个目标的输出路径
set_target_properties(mylib PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/mylib)
set_target_properties(myexe PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/myexe)

注意,这些变量都是相对于CMAKE_BINARY_DIR变量的,它表示当前构建目录的路径。你也可以使用CMAKE_CURRENT_BINARY_DIR变量来表示当前正在处理的子目录的路径³。

总之,使用out of source build的方式可以让你更灵活地指定构建目录和输出文件的位置,而不需要在CMake脚本中硬编码。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) CMake how to set the build directory to be different than source .... https://stackoverflow.com/questions/18826789/cmake-how-to-set-the-build-directory-to-be-different-than-source-directory. (2) CMAKE_CURRENT_BINARY_DIR — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/variable/CMAKE_CURRENT_BINARY_DIR.html. (3) How to make cmake output to the "build" directory?. https://stackoverflow.com/questions/52022717/how-to-make-cmake-output-to-the-build-directory.


cmake -B build/ -G "MinGW Makefiles" . 如何将-B build配置在CMakeLists.txt中而不用每次执行cmake都通过-B build/执行

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,如果你想在CMakeLists.txt中指定构建目录的位置,你可以使用一些未公开的CMake选项-H-B来避免离开源代码目录。-H选项用于指定主CMakeLists.txt文件的路径,-B选项用于指定你想要的构建目录的路径。例如:

cmake -H. -Bbuild # 在当前目录下运行CMake,并指定build目录为构建目录

这样,CMake就会在build目录下生成所有的输出文件,而不会修改当前目录下的任何文件。这种方式可以让你更方便地指定构建目录,而不需要每次执行CMake都通过-B选项执行。

但是,你需要注意的是,这些选项是未公开的,也就是说,它们可能在未来的版本中发生变化或被移除。所以,如果你想使用更稳定和标准的方式来指定构建目录,你还是应该使用out of source build的方式,即在构建目录下运行CMake,并指定源代码目录作为参数,比如:

mkdir build # 创建构建目录
cd build # 进入构建目录
cmake .. # 运行CMake并指定上级目录为源代码目录

这样,CMake就会在build目录下生成所有的输出文件,而不会修改上级目录下的任何文件。这种方式有很多优点,比如:

  • 可以保持源代码目录的干净整洁,不会产生临时文件或缓存文件。
  • 可以方便地删除或重建构建目录,而不影响源代码。
  • 可以支持多个构建目录,比如用于不同的编译器或配置。

总之,使用哪种方式来指定构建目录取决于你的具体需求和偏好。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) cmake - Specifying build directory within CMakeLists file - Stack Overflow. https://stackoverflow.com/questions/20834596/specifying-build-directory-within-cmakelists-file. (2) build - Changing CMake files standard location - Stack Overflow. https://stackoverflow.com/questions/13712020/changing-cmake-files-standard-location. (3) Getting Started — Mastering CMake. https://cmake.org/cmake/help/book/mastering-cmake/chapter/Getting%20Started.html.


SET(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/install_path)是什么意思

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,CMAKE_INSTALL_PREFIX变量用于设置安装目录的前缀。当你执行make install或者构建INSTALL目标时,这个变量的值会被添加到所有的安装目录的前面。这个变量的默认值是/usr/local在UNIX系统上,或者c:/Program Files/${PROJECT_NAME}在Windows系统上。你可以在配置构建树时定义这个变量来设置它的值,或者在使用cmake命令行工具的--install模式时,使用--prefix选项来指定一个不同的值,比如:

cmake --install . --prefix /my/install/prefix # 在当前目录下安装,并指定安装目录的前缀为/my/install/prefix

这个变量也会被添加到CMAKE_SYSTEM_PREFIX_PATH变量中,以便让find_package()find_program()find_library()find_path()find_file()等命令能够在这个前缀下搜索其他软件。如果你想禁用这个行为,你可以在第一个project()调用之前设置CMAKE_FIND_NO_INSTALL_PREFIX变量为TRUE。

注意,使用GNUInstallDirs模块可以提供GNU风格的选项来控制安装目录的布局。

总之,使用CMAKE_INSTALL_PREFIX变量可以让你更灵活地指定安装目录的位置和结构。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) CMAKE_INSTALL_PREFIX — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html. (2) CMake and Setting CMAKE_INSTALL_PREFIX | Baeldung on Linux. https://www.baeldung.com/linux/cmake-cmake_install_prefix. (3) Installing Files — Mastering CMake. https://cmake.org/cmake/help/book/mastering-cmake/chapter/Install.html.


CMakeLists.txt中的 ${CMAKE_CURRENT_SOURCE_DIR}是什么含义

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,${CMAKE_CURRENT_SOURCE_DIR}变量表示当前处理的CMakeLists.txt文件所在的路径。这是当前被CMake处理的源代码目录的完整路径。如果你在源代码目录下运行CMake,这个变量的值就和${CMAKE_BINARY_DIR}变量一样。如果你在脚本模式下运行CMake,这个变量的值就是当前工作目录。

例如,假设你有一个项目的目录结构如下:

project/
  CMakeLists.txt
  A/
    CMakeLists.txt
    B/
      CMakeLists.txt

如果你在project目录下运行CMake,并且在project/CMakeLists.txt文件中使用add_subdirectory(A)命令来添加子目录A,那么在project/A/CMakeLists.txt文件中,${CMAKE_CURRENT_SOURCE_DIR}变量的值就是project/A

如果你在project/A/CMakeLists.txt文件中使用include(B/CMakeLists.txt)命令来包含子目录B中的文件,那么在project/A/B/CMakeLists.txt文件中,${CMAKE_CURRENT_SOURCE_DIR}变量的值仍然是project/A,因为include()命令不会创建一个新的作用域,而最近的有自己作用域的CMakeLists.txt文件是在目录A中。

总之,${CMAKE_CURRENT_SOURCE_DIR}变量可以让你获取当前处理的CMakeLists.txt文件所在的路径,这对于指定相对路径或者引用其他文件很有用。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) CMAKE_CURRENT_SOURCE_DIR. https://cmake.org/cmake/help/latest/variable/CMAKE_CURRENT_SOURCE_DIR.html. (2) CMAKE_SOURCE_DIR — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/variable/CMAKE_SOURCE_DIR.html. (3) cmake常用变量和常用环境变量 - 掘金. https://juejin.cn/post/6998055558741753893. (4) cmake - Difference between CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT .... https://stackoverflow.com/questions/15662497/difference-between-cmake-current-source-dir-and-cmake-current-list-dir.

好的,我可以帮你生成一个简单的CMakeLists.txt示例。😊

假设你的项目是一个C++程序,它有一个可执行文件xe和一个静态库文件xlib,并且它们都在project/A/B目录下。那么你可以使用以下的CMakeLists.txt文件来构建你的项目:

# project/CMakeLists.txt
cmake_minimum_required(VERSION 3.12...3.25)
project(xCMakeProject VERSION 1.0 LANGUAGES CXX)
add_subdirectory(A) # 添加子目录A

# project/A/CMakeLists.txt
include(B/CMakeLists.txt) # 包含子目录B中的文件

# project/A/B/CMakeLists.txt
add_library(xlib STATIC xlib.c xlib.h) # 创建静态库目标xlib
add_executable(xe xe.cpp) # 创建可执行文件目标xe
target_link_libraries(xe PRIVATE xlib) # 链接静态库xlib到可执行文件xe
install(TARGETS xe xlib DESTINATION bin) # 安装目标xe和xlib到bin目录下

这样,你就可以在project目录下运行CMake,并指定一个构建目录,比如:

mkdir build # 创建构建目录
cd build # 进入构建目录
cmake .. # 运行CMake并指定上级目录为源代码目录

然后,你就可以在build目录下运行make来编译你的项目,并运行make install来安装你的项目到指定的位置。

这只是一个非常简单的示例,你可以根据你的具体需求和偏好来修改或扩展它。我希望这能对你有所帮助。


执行make install 会发生什么?如何设置执行make install的路径。

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,执行make install会根据你在CMakeLists.txt文件中定义的install()命令来安装相关的文件或目标到指定的位置。这些install()命令会生成一个文件cmake_install.cmake在构建目录中,它是由生成的install目标和CPack内部使用的。你也可以手动调用这个脚本,比如:

cmake -P cmake_install.cmake # 执行安装脚本

如果你想设置执行make install的路径,你可以使用一些选项来修改安装目录的前缀或后缀,比如:

  • CMAKE_INSTALL_PREFIX:用于设置安装目录的前缀。你可以在配置构建树时定义这个变量,或者在使用cmake命令行工具的--install模式时,使用--prefix选项来指定一个不同的值。
  • DESTDIR:用于设置安装目录的顶层路径。你可以在执行make install之前设置这个环境变量,或者在使用cmake命令行工具的--install模式时,使用--destdir选项来指定一个不同的值。
  • DESTINATION:用于设置安装目录的底层路径。你可以在每个install()命令中使用这个参数来指定一个相对或绝对路径。

例如,假设你有一个项目,它有一个可执行文件目标xe和一个静态库目标xlib,并且你想把它们安装到不同的位置,你可以使用以下的CMakeLists.txt文件来定义你的安装规则:

# project/CMakeLists.txt
cmake_minimum_required(VERSION 3.12...3.25)
project(xCMakeProject VERSION 1.0 LANGUAGES CXX)
add_library(xlib STATIC xlib.c xlib.h) # 创建静态库目标xlib
add_executable(xe xe.cpp) # 创建可执行文件目标xe
target_link_libraries(xe PRIVATE xlib) # 链接静态库xlib到可执行文件xe
set(CMAKE_INSTALL_PREFIX /usr/local) # 设置安装目录的前缀为/usr/local
install(TARGETS xe DESTINATION bin) # 安装目标xe到/usr/local/bin目录下
install(TARGETS xlib DESTINATION lib) # 安装目标xlib到/usr/local/lib目录下

然后,你可以在构建目录下运行CMake,并指定一个构建目录,比如:

mkdir build # 创建构建目录
cd build # 进入构建目录
cmake .. # 运行CMake并指定上级目录为源代码目录

然后,你就可以在构建目录下运行make install来安装你的项目到指定的位置。如果你想修改安装位置,你可以使用以下的方法:

  • 使用环境变量或选项来修改顶层路径,比如:
export DESTDIR=/tmp # 设置顶层路径为/tmp
make install # 安装到/tmp/usr/local/bin和/tmp/usr/local/lib

# 或者

cmake --install . --destdir /tmp # 安装到/tmp/usr/local/bin和/tmp/usr/local/lib
  • 使用选项来修改前缀路径,比如:
cmake --install . --prefix /opt # 安装到/opt/bin和/opt/lib
  • 使用参数来修改底层路径,比如:
# project/CMakeLists.txt
...
install(TARGETS xe DESTINATION mybin) # 安装目标xe到/usr/local/mybin目录下
install(TARGETS xlib DESTINATION mylib) # 安装目标xlib到/usr/local/mylib目录下

总之,使用make installinstall()命令可以让你更灵活地安装你的项目到不同的位置,而不需要手动复制或移动文件。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) How to Install and Run CMake on Windows - Earthly Blog. https://earthly.dev/blog/installandrun-cmake-on-windows/. (2) Installing | CMake. https://cmake.org/install/. (3) install — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/command/install.html. (4) CMake之install方法的使用 - 知乎 - 知乎专栏. https://zhuanlan.zhihu.com/p/102955723.

cmake --build build/是什么意思

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,执行make install会根据你在CMakeLists.txt文件中定义的install()命令来安装相关的文件或目标到指定的位置。这些install()命令会生成一个文件cmake_install.cmake在构建目录中,它是由生成的install目标和CPack内部使用的。你也可以手动调用这个脚本,比如:

cmake -P cmake_install.cmake # 执行安装脚本

如果你想设置执行make install的路径,你可以使用一些选项来修改安装目录的前缀或后缀,比如:

  • CMAKE_INSTALL_PREFIX:用于设置安装目录的前缀。你可以在配置构建树时定义这个变量,或者在使用cmake命令行工具的--install模式时,使用--prefix选项来指定一个不同的值。
  • DESTDIR:用于设置安装目录的顶层路径。你可以在执行make install之前设置这个环境变量,或者在使用cmake命令行工具的--install模式时,使用--destdir选项来指定一个不同的值。
  • DESTINATION:用于设置安装目录的底层路径。你可以在每个install()命令中使用这个参数来指定一个相对或绝对路径。

例如,假设你有一个项目,它有一个可执行文件目标xe和一个静态库目标xlib,并且你想把它们安装到不同的位置,你可以使用以下的CMakeLists.txt文件来定义你的安装规则:

# project/CMakeLists.txt
cmake_minimum_required(VERSION 3.12...3.25)
project(xCMakeProject VERSION 1.0 LANGUAGES CXX)
add_library(xlib STATIC xlib.c xlib.h) # 创建静态库目标xlib
add_executable(xe xe.cpp) # 创建可执行文件目标xe
target_link_libraries(xe PRIVATE xlib) # 链接静态库xlib到可执行文件xe
set(CMAKE_INSTALL_PREFIX /usr/local) # 设置安装目录的前缀为/usr/local
install(TARGETS xe DESTINATION bin) # 安装目标xe到/usr/local/bin目录下
install(TARGETS xlib DESTINATION lib) # 安装目标xlib到/usr/local/lib目录下

然后,你可以在构建目录下运行CMake,并指定一个构建目录,比如:

mkdir build # 创建构建目录
cd build # 进入构建目录
cmake .. # 运行CMake并指定上级目录为源代码目录

然后,你就可以在构建目录下运行make install来安装你的项目到指定的位置。如果你想修改安装位置,你可以使用以下的方法:

  • 使用环境变量或选项来修改顶层路径,比如:
export DESTDIR=/tmp # 设置顶层路径为/tmp
make install # 安装到/tmp/usr/local/bin和/tmp/usr/local/lib

# 或者

cmake --install . --destdir /tmp # 安装到/tmp/usr/local/bin和/tmp/usr/local/lib
  • 使用选项来修改前缀路径,比如:
cmake --install . --prefix /opt # 安装到/opt/bin和/opt/lib
  • 使用参数来修改底层路径,比如:
# project/CMakeLists.txt
...
install(TARGETS xe DESTINATION mybin) # 安装目标xe到/usr/local/mybin目录下
install(TARGETS xlib DESTINATION mylib) # 安装目标xlib到/usr/local/mylib目录下

总之,使用make installinstall()命令可以让你更灵活地安装你的项目到不同的位置,而不需要手动复制或移动文件。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) How to Install and Run CMake on Windows - Earthly Blog. https://earthly.dev/blog/installandrun-cmake-on-windows/. (2) Installing | CMake. https://cmake.org/install/. (3) install — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/command/install.html. (4) CMake之install方法的使用 - 知乎 - 知乎专栏. https://zhuanlan.zhihu.com/p/102955723.

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,cmake --build build/是一个命令行工具,它用于构建一个由CMake生成的项目。它的含义是:

  • cmake:表示调用cmake可执行文件,它是CMake的命令行接口。
  • --build:表示执行构建操作,它是cmake的一个选项。
  • build/:表示构建目录的路径,它是--build选项的一个参数。这个目录应该包含一个由CMake生成的构建系统,比如Makefile或者Visual Studio工程文件。

这个命令相当于在build/目录下运行相应的构建工具,比如make或者msbuild,来编译项目的源代码并生成可执行文件或库文件。这个命令的优点是,它可以自动识别不同的构建系统,并且可以使用一些通用的选项来控制构建过程,比如:

  • --config <config>:表示指定构建配置,比如Debug或者Release。这个选项只对多配置的构建系统有效,比如Visual Studio或者Xcode。
  • --target <target>:表示指定构建目标,比如一个可执行文件或库文件的名称。这个选项可以重复多次来指定多个目标。
  • --parallel <parallel>:表示指定并行构建的级别,比如使用多少个线程或进程来同时编译源文件。这个选项需要CMake 3.12或更高版本。

例如,如果你想在build/目录下构建一个名为xe的可执行文件目标,并且使用Release配置和4个线程,你可以使用以下的命令:

cmake --build build/ --target xe --config Release --parallel 4

总之,使用cmake --build build/命令可以让你更方便地构建一个由CMake生成的项目,而不需要知道具体的构建工具或命令。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) cmake(1) — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/manual/cmake.1.html. (2) build_command — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/command/build_command.html. (3) cmake / cmake --build 如何理解_make.build_Ruo_Xiao的博客-CSDN博客. https://blog.csdn.net/itworld123/article/details/123862402. (4) cmake --build with specifying Debug or Release configuration. https://discourse.cmake.org/t/cmake-build-with-specifying-debug-or-release-configuration/5183.

set(CMAKE_BUILD_TYPE Debug) # 设置编译类型为debug 不生效

你好,这是Bing。我可以帮你解答。😊

根据搜索结果¹,set(CMAKE_BUILD_TYPE Debug)是一个CMake命令,它用于设置构建类型为Debug。这个命令的含义是:

  • set:表示设置一个变量的值。
  • CMAKE_BUILD_TYPE:表示构建类型的变量名,它可以是Debug,Release,RelWithDebInfo或者MinSizeRel之一。
  • Debug:表示构建类型的变量值,它表示使用调试信息和不优化的编译标志。

这个命令的作用是,它会影响一些内置的变量,比如:

  • CMAKE_C_FLAGS_DEBUG:表示C语言的编译标志,在Debug模式下使用。
  • CMAKE_CXX_FLAGS_DEBUG:表示C++语言的编译标志,在Debug模式下使用。
  • CMAKE_EXE_LINKER_FLAGS_DEBUG:表示可执行文件的链接标志,在Debug模式下使用。
  • CMAKE_SHARED_LINKER_FLAGS_DEBUG:表示共享库的链接标志,在Debug模式下使用。

这些变量的默认值取决于不同的平台和编译器,你可以在CMakeCache.txt文件中查看或修改它们。

如果你想让这个命令生效,你需要注意以下几点:

  • 这个命令只对单配置的构建系统有效,比如Makefile或者Ninja。对于多配置的构建系统,比如Visual Studio或者Xcode,这个命令会被忽略,因为它们可以在内部切换不同的配置。
  • 这个命令应该在第一个project()调用之前执行,否则它可能会被覆盖或无效。如果你想在第一个project()调用之后修改这个变量,你需要使用FORCE选项来强制设置它,比如:
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
  • 这个命令应该在生成构建系统之前执行,否则它可能会被忽略或无效。如果你想在生成构建系统之后修改这个变量,你需要重新运行CMake来更新构建系统,或者使用一些选项来指定不同的配置,比如:
cmake --build build/ --config Debug # 使用Debug配置来构建项目

总之,使用set(CMAKE_BUILD_TYPE Debug)命令可以让你设置构建类型为Debug,但你需要注意一些条件和限制。我希望这能对你有所帮助。

源: 与必应的对话, 2023/6/8 (1) CMAKE_BUILD_TYPE is not being used in CMakeLists.txt. https://stackoverflow.com/questions/24460486/cmake-build-type-is-not-being-used-in-cmakelists-txt. (2) c - cmake ignores -D CMAKE_BUILD_TYPE=Debug - Stack Overflow. https://stackoverflow.com/questions/23907679/cmake-ignores-d-cmake-build-type-debug. (3) CMake & CTest : make test doesn't build tests - Stack Overflow. https://stackoverflow.com/questions/733475/cmake-ctest-make-test-doesnt-build-tests.

可是编译出来的app.exe不带_debug后缀

根据搜索结果¹²,你可以使用CMAKE_DEBUG_POSTFIX变量来设置debug版本的可执行文件名的后缀。你需要在运行cmake之前设置这个变量,例如:

cmake -DCMAKE_DEBUG_POSTFIX=_debug -B build/ -G "MinGW Makefiles" .

这样,当你编译debug版本的时候,你的可执行文件名会自动加上_debug后缀。你也可以在CMakeLists.txt文件中设置这个变量,例如:

set(CMAKE_DEBUG_POSTFIX _debug)

希望这能帮助你。👍

源: 与必应的对话, 2023/6/8 (1) cmake - How to get debug postfix in executable name - Stack Overflow. https://stackoverflow.com/questions/28893450/how-to-get-debug-postfix-in-executable-name. (2) c++ - Debug vs Release in CMake - Stack Overflow. https://stackoverflow.com/questions/7724569/debug-vs-release-in-cmake. (3) CMAKE_DEBUG_POSTFIX — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/variable/CMAKE_DEBUG_POSTFIX.html.

根据搜索结果¹²,你可以使用DEBUG_POSTFIX目标属性或CMAKE_DEBUG_POSTFIX变量来设置debug版本的可执行文件名的后缀。你需要在add_executable()命令之后使用set_target_properties()命令来设置这个属性,例如:

add_executable(app ${SRC_LIST})
set_target_properties(app PROPERTIES DEBUG_POSTFIX _debug)

或者,你可以在CMakeLists.txt文件中设置CMAKE_DEBUG_POSTFIX变量,例如:

set(CMAKE_DEBUG_POSTFIX _debug)

这样,当你编译debug版本的时候,你的可执行文件名会自动加上_debug后缀。你也可以使用CMAKE_EXECUTABLE_SUFFIX变量来设置所有可执行文件名的后缀,例如:

set(CMAKE_EXECUTABLE_SUFFIX _64.exe)

希望这能帮助你。👍

源: 与必应的对话, 2023/6/8 (1) cmake - How to get debug postfix in executable name - Stack Overflow. https://stackoverflow.com/questions/28893450/how-to-get-debug-postfix-in-executable-name. (2) cmake: add "d" suffix for debug build of static library. https://stackoverflow.com/questions/49672264/cmake-add-d-suffix-for-debug-build-of-static-library. (3) DEBUG_POSTFIX — CMake 3.26.4 Documentation. https://cmake.org/cmake/help/latest/prop_tgt/DEBUG_POSTFIX.html.

windows10+vscode+mingw+cmake环境已经搭建好的情况下搭建c++项目时的CMakeLists.txt中的set(CMAKE_GENERATOR "MinGW Makefiles") 有什么用?不在CMakeLists.txt中设置可以吗?

在Windows 10 + VS Code + MinGW + CMake环境中,CMakeLists.txt中的set(CMAKE_GENERATOR "MinGW Makefiles")设置的作用是指定使用MinGW Makefiles生成器来生成构建系统。

生成器是CMake中的一个重要概念,它决定了CMake如何生成项目的构建系统。不同的生成器有不同的构建系统,例如Makefiles,Ninja,Visual Studio等。

如果你在CMakeLists.txt中没有设置CMAKE_GENERATOR,CMake会使用默认的生成器。在Windows上,默认的生成器是Visual Studio生成器。

对于像你这样使用MinGW和Makefile作为构建系统的情况,设置CMAKE_GENERATOR为"MinGW Makefiles"可以确保CMake使用MinGW Makefiles生成器来生成构建系统,从而正确配置项目的构建过程。

如果你没有在CMakeLists.txt中设置CMAKE_GENERATOR,而是在命令行中手动指定了生成器(比如使用cmake -G "MinGW Makefiles" ..),那么CMake会根据命令行参数来选择生成器,并忽略CMakeLists.txt中的设置。

因此,如果你已经在命令行中手动指定了生成器,那么在CMakeLists.txt中设置CMAKE_GENERATOR是可选的,不设置也不会影响构建过程。但是,为了项目的一致性和可维护性,建议在CMakeLists.txt中设置CMAKE_GENERATOR,以确保项目在不同环境中能够正确构建。

windows10+vscode+mingw+cmake环境已经搭建好的情况下搭建c++项目时使用CMakeLists.txt,当我使用默认的vscode的生成器时,在项目根目录的build目录下执行cmake .. 会出现-- Building for: NMake MakefilesCMake Error at CMakeLists.txt:27 (project): Running 'nmake' '-?' failed with: 系统找不到指定的文件。-- Configuring incomplete, errors occurred!是什么原因造成的。现在我不得不在终端build目录下执行cmake .. 时加上-G "MinGW Makefiles"才能正常运行

这个错误通常是由于没有正确设置编译器路径或未将编译器路径添加到系统环境变量中引起的。请确保在你的系统环境变量中添加了MinGW的安装路径。另外,你还可以在CMakeLists.txt中指定编译器路径,例如:

set(CMAKE_C_COMPILER "C:/MinGW/bin/gcc.exe")
set(CMAKE_CXX_COMPILER "C:/MinGW/bin/g++.exe")

请根据你的MinGW安装路径进行相应的设置。

另外,你还可以尝试在执行cmake ..命令之前先将VS Code的终端设置为使用MinGW编译器。在VS Code中,按下Ctrl + Shift + P打开命令面板,然后搜索“Select Default Shell”,选择“MinGW”的终端。然后,在终端中执行cmake ..命令,看看是否能够解决问题。

如果上述方法仍然无法解决问题,你可能需要检查一下你的MinGW安装是否正确,并且对应的编译器是否在相应的路径下可用。

c++项目中如何让多个cpp文件同时含有main函数,且运行都不会报错

在C++项目中,每个可执行文件只能有一个main函数,它是程序的入口点。如果您尝试在同一个项目中编译多个包含main函数的源文件,编译器会报错,提示main函数已经定义。

如果您希望在同一个项目中编写多个独立的程序,每个程序都有自己的main函数,您可以将它们组织为多个目标。例如,在使用CMake构建系统的项目中,您可以使用add_executable命令为每个程序创建一个可执行目标。每个目标都可以包含一个包含main函数的源文件。

另一种方法是使用条件编译来控制哪个main函数被编译。例如,您可以在每个包含main函数的源文件中使用预处理器指令来检查一个宏是否已定义,如果已定义,则包含该文件中的main函数。然后,在编译时指定要定义哪个宏,以选择要编译哪个main函数。

希望这些信息对您有所帮助。

你能用后者给出代码举例说明吗?

当然可以。下面是一个使用条件编译来控制哪个main函数被编译的示例。

假设您有两个源文件,main1.cppmain2.cpp,它们都包含一个main函数。

// main1.cpp
#include <iostream>

#ifdef USE_MAIN1
int main() {
    std::cout << "This is main 1" << std::endl;
    return 0;
}
#endif
// main2.cpp
#include <iostream>

#ifdef USE_MAIN2
int main() {
    std::cout << "This is main 2" << std::endl;
    return 0;
}
#endif

在这两个文件中,main函数都被包含在一个条件编译块中。只有当定义了相应的宏(USE_MAIN1USE_MAIN2)时,相应的main函数才会被编译。

然后,在编译时,您可以指定要定义哪个宏。例如,如果您使用g++编译器,可以使用-D选项来定义宏:

g++ -DUSE_MAIN1 main1.cpp main2.cpp -o program

在这个例子中,我们定义了宏USE_MAIN1,因此只有main1.cpp中的main函数会被编译。如果运行生成的程序,它将输出“This is main 1”。

希望这个例子能够帮助您理解如何使用条件编译来控制哪个main函数被编译。

tasks.json中的type command和args如何执行cd build 和cmake ..和minw32-make.exe

tasks.json 文件用于在 Visual Studio Code 中配置任务。要在 tasks.json 文件中执行 cd buildcmake ..mingw32-make.exe 命令,您可以创建一个 shell 类型的任务,并在 args 属性中指定要执行的命令。

下面是一个示例 tasks.json 文件,它定义了一个名为 build 的任务,用于执行上述命令:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "type": "shell",
            "command": "",
            "args": [
                "cd build && cmake .. && mingw32-make.exe"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

在这个示例中,我们创建了一个类型为 shell 的任务,并在 args 属性中指定了要执行的命令。这些命令将按顺序执行:首先执行 cd build 命令,然后执行 cmake .. 命令,最后执行 mingw32-make.exe 命令。

您可以根据需要修改这个示例,以满足您的需求。

command指定为powershell是什么意思?

如果您在 tasks.json 文件中的任务中将 command 属性指定为 powershell,则意味着该任务将使用 PowerShell 来执行命令。

PowerShell 是 Windows 操作系统中的一个命令行 shell 和脚本语言。它提供了许多功能强大的命令和脚本功能,可以用于管理和自动化系统任务。

下面是一个示例 tasks.json 文件,它定义了一个名为 build 的任务,该任务使用 PowerShell 来执行命令:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "type": "shell",
            "command": "powershell",
            "args": [
                "-Command",
                "cd build; cmake ..; mingw32-make.exe"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

在这个示例中,我们创建了一个类型为 shell 的任务,并将 command 属性指定为 powershell。这意味着该任务将使用 PowerShell 来执行命令。在 args 属性中,我们指定了要执行的 PowerShell 命令。

您可以根据需要修改这个示例,以满足您的需求。

cmake 引入子项目

project/
├─ CMakeLists.txt (主项目的 CMakeLists.txt)
├─ subproject1/
│   ├─ CMakeLists.txt (子项目1的 CMakeLists.txt)
│   ├─ ...
└─ subproject2/
    ├─ CMakeLists.txt (子项目2的 CMakeLists.txt)
    ├─ ...

主项目的 CMakeLists.txt

添加子项目1

add_subdirectory(subproject1)

添加子项目2

add_subdirectory(subproject2)

这样,当你构建主项目时,CMake 会进入每个子项目的目录,并执行子项目的构建规则。每个子项目都可以有自己的 CMakeLists.txt 文件来定义构建规则和目标。

config.h.cmakeconfig.h.in 文件

config.h.cmakeconfig.h.in 文件在 CMake 中具有相似的作用,但在使用方式和生成过程中有一些差异。

config.h.cmake 文件是一个模板文件,其中包含了配置选项的宏定义。通常,它不直接用于生成最终的配置文件,而是作为中间文件,在生成过程中通过 CMake 脚本填充特定的值,并生成 config.h 文件。

config.h.in 文件也是一个模板文件,用于生成最终的配置文件 config.h。它包含了预定义的变量占位符,例如 @VAR_NAME@,用于在生成阶段通过 CMake 脚本替换为具体的配置值。

CMake 在生成过程中遵循以下步骤来生成最终的配置文件 config.h

  1. CMake 在配置构建过程中读取 config.h.in 文件,并根据其中的占位符生成 config.h.cmake 文件。
  2. CMake 脚本根据用户在 CMakeLists.txt 中的配置选项,设置变量的值,并将这些值填充到 config.h.cmake 文件中。
  3. CMake 使用生成的 config.h.cmake 文件,通过对占位符进行替换,生成最终的配置文件 config.h

因此,config.h.cmakeconfig.h.in 文件都用于生成最终的配置文件 config.h,但 config.h.cmake 是在用户配置之后生成的中间文件,而 config.h.in 是最初的模板文件,用于创建 config.h.cmake 文件。

1
https://gitee.com/ShiGuang0829/c-practice.git
git@gitee.com:ShiGuang0829/c-practice.git
ShiGuang0829
c-practice
C-Practice
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891