Awes0meE / 66CCFF LabXJTLU Undergraduate西交利物浦大学本科生
Back to notes返回笔记

2025-08-18

CMake And Build Logic Learning NotesCMake 与编译底层逻辑学习笔记

Two Notion-exported source notes rendered directly as the CMake/build-logic note: one CMake primer and one Qt-oriented compiler/toolchain map.把两份 Notion 导出的原始笔记直接渲染成 CMake / 编译底层逻辑笔记:一份 CMake 入门,一份 Qt 学习导向的编译器与工具链地图。

CMakeCompilerNinjaQt
Related Project相关项目Nanjing Turing Qt, Build, and Packaging Learning Log南京图灵 Qt、编译与打包学习记录

English Translation

Source

This page now renders the original Notion Markdown exports directly, not a PDF text extraction. The English section below is a substantial section-by-section translation of the two source notes: first the CMake primer, then the Qt-oriented build/toolchain map. The original Chinese Markdown body, diagrams, links, and code listings remain below as source evidence.

Notion Source 1: CMake Primer

The CMake note was written as a systematic study record for understanding low-level project building and deployment. The study material came from a Bilibili CMake course and its companion blog posts. The structure starts from a local Linux development environment, then moves through basic CMake commands, source-file discovery, header include paths, static and dynamic libraries, logging, string operations, and common project organization patterns.

For the local environment, I used WSL on Windows 11 with Ubuntu and VS Code Remote - WSL. The setup path was: enable WSL from an administrator PowerShell, install Ubuntu from Microsoft Store, create the Linux username/password, update packages with sudo apt update && sudo apt upgrade -y, and install the base development tools with sudo apt install -y build-essential gcc g++ cmake make git.

The environment was considered ready after gcc --version and cmake --version returned valid versions. VS Code was then connected into WSL through the Remote - WSL extension, and the C/C++ extension was installed inside the WSL environment.

CMake itself was described as a cross-platform project build tool. Its core input is CMakeLists.txt; CMake reads the commands in that file and generates the appropriate underlying build files for the current platform and toolchain. The conceptual build flow is preprocessing, compilation, assembly, and linking. A tiny program can be compiled directly with a command such as g++ *.cpp -o app, but once a project grows to many source files, headers, dependencies, and libraries, manual command-line compilation stops being practical.

The first small CMake example used five .cpp files plus one header. The initial CMakeLists.txt declared the minimum CMake version with cmake_minimum_required(VERSION 3.25), named the project with project(test), and created the executable target with add_executable(app add.c sub.cpp mult.cpp div.cpp main.cpp).

The key points were: cmake_minimum_required declares the minimum CMake version; project declares the project name and optional metadata; add_executable declares the executable target and must receive source files, not headers. Running cmake in the directory containing CMakeLists.txt generated build files, then make produced the executable.

The set command was introduced as CMake's way to define variables. In this learning note, I understood a variable mainly as a named collection of source files or a reusable configuration value, such as set(SRC_LIST add.c div.c main.c mult.c sub.c) followed by add_executable(app ${SRC_LIST}).

Variables are referenced with ${...}. The same command can set the C++ standard, such as CMAKE_CXX_STANDARD 17, and output paths such as EXECUTABLE_OUTPUT_PATH.

For file discovery, the note compares aux_source_directory and file(GLOB ...). The goal is to avoid manually listing every source file once a project has a larger src tree. The note also records the common tradeoff: automatic globbing is convenient for learning and small projects, while explicit source lists are often clearer in serious projects because build systems can track changes more predictably.

Header paths were handled with include_directories, and library creation/linking was split into static and dynamic library examples. Static libraries package code into .a or .lib files and are linked into the final executable; dynamic libraries are loaded at runtime as .so, .dll, or .dylib files. The learning record emphasizes that linking is not only "finding a file"; the compiler, architecture, runtime library, and exported symbols all need to match.

The later CMake sections cover practical commands: printing diagnostic messages with message, manipulating strings, organizing subdirectories, using add_subdirectory, producing libraries from submodules, and linking targets. The code blocks in the original Chinese note remain neutral and are intentionally kept as source listings rather than translated prose.

Notion Source 2: Build Logic For Qt Study

The second source note maps the lower-level build concepts needed when working with Qt projects. Its central idea is that a Qt build is not just "press Build" in an IDE. The result depends on a whole toolchain: compiler version, architecture, generator, build type, Qt binary package, deployment tool, and runtime dependencies.

The note distinguishes several layers:

  • the compiler, such as MSVC, GCC, or Clang, which turns source code into object files;
  • the build system/generator, such as CMake, qmake, Ninja, Makefiles, or Visual Studio solutions;
  • the linker, which combines object files and libraries into final binaries;
  • the Qt binary package, whose directory name often encodes the required compiler and architecture, such as msvc2019_64;
  • deployment tools, such as windeployqt and macdeployqt, which collect runtime dependencies.

For Qt, binary compatibility is a hard rule. A Qt library built for MSVC2019 x64 should be used by a project compiled with the matching MSVC2019 x64 toolchain. If the compiler version, CPU architecture, or runtime library does not match, typical errors include LNK1112 for machine type conflicts, LNK2038 for runtime/toolset mismatches, and runtime failures from missing or incompatible DLLs.

The Qt installation layout was read as a map of this compatibility information. For example, C:\Qt\6.5.3\msvc2019_64 means Qt 6.5.3 built for the MSVC 2019 64-bit toolchain. Its bin directory contains runtime DLLs and tools, include contains headers, lib contains link-time libraries, and lib/cmake/Qt6 contains CMake package configuration files such as Qt6Config.cmake and Qt6CoreConfig.cmake. Those CMake files define imported targets such as Qt6::Core, which carry include paths, link libraries, and binary locations.

Qt modules were described as separately linkable functional packages: Qt6::Core for core types and the meta-object system, Qt6::Gui for graphics/windowing foundations, Qt6::Widgets for desktop UI controls, Qt6::Network for network APIs, and Qt6::Sql for database access. A project must request and link the modules it uses, for example with find_package(Qt6 REQUIRED COMPONENTS Network) and target_link_libraries(my_app PRIVATE Qt6::Network).

The note also explains Qt's automatic tools. moc processes the meta-object system, especially Q_OBJECT, signals, slots, and dynamic properties. uic converts .ui files from Qt Designer into generated C++ headers. rcc compiles .qrc resource collections into generated C++ code so resources can be accessed through paths such as :/images/logo.png. In qmake or CMake projects these tools are usually invoked automatically during the build.

Runtime deployment was summarized through windeployqt. A Qt executable normally does not contain all Qt code inside the .exe; it loads Qt DLLs, platform plugins, image plugins, translations, and sometimes QML or third-party libraries at runtime. windeployqt scans the application and copies the required files into a deploy folder. The key deployment checks are that Qt DLLs exist, the platforms/qwindows.dll plugin is available, resource paths are correct, and any non-Qt third-party DLLs are copied manually if windeployqt does not know about them.

The final checklist is a troubleshooting map:

  • verify the active compiler with cl.exe or the relevant compiler command;
  • check available CMake generators with cmake --help;
  • confirm the Qt version and toolchain with qmake -v;
  • match architecture (x64 versus x86);
  • match the MSVC toolset (v142 for VS2019, v143 for VS2022);
  • use the right generator, such as Ninja for command-line builds or Visual Studio for IDE solutions;
  • remember that single-config generators use -DCMAKE_BUILD_TYPE=Release, while multi-config generators use --config Release.

The original diagrams and detailed code/listing sections are left below as the source version. They are technical artifacts rather than prose-only paragraphs, so they remain language-neutral where appropriate.

原文来源

这里不再使用 PDF 文本抽取。下面直接放从 Notion 导出的两份 Markdown 原文:先是 CMake 入门学习记录,再是面向 Qt 的编译底层逻辑学习记录。图片也使用 Notion 导出文件夹里的原图。

第一份 Notion 原文:CMake 入门学习记录

打开原始 Markdown

CMake 入门学习记录

这里是 CMake 学习的系统性记录,用于提高个人对代码底层构建的理解和部署。

所学课程来自 B 站 up主 爱编程的大丙 所录制的课程,共 21 集,总时长约 3.5 小时: > 🔗 CMake 保姆级教程【C/C++】 本课程是有配套博客资料的,由 up 主本人提供,共分为上、下两部分,链接如下: > 🔗 CMake 保姆级教程(上) CMake 保姆级教程(下)(未整理)

大纲


Linux 本地开发环境准备

使用 WSL 子系统在 Windows 11 本地部署 Ubuntu 22.04,使用 VS code 的 Remote - WSL 功能开发。

步骤 1:启用 WSL 功能

  1. 以管理员身份打开 PowerShell
  2. - 按 Win + X,选择 Windows Terminal (Admin) 或 PowerShell (Admin)
  3. 启用 WSL

运行以下命令:

``powershell wsl --install ``

这会自动安装 WSL 和默认的 Ubuntu 发行版(如果未安装,会提示下载)。

安装成功后会提示:

``powershell 正在安装: 虚拟机平台 已安装 虚拟机平台。 正在安装: 适用于 Linux 的 Windows 子系统 已安装 适用于 Linux 的 Windows 子系统。 正在安装: Ubuntu 已安装 Ubuntu。 请求的操作成功。直到重新启动系统前更改将不会生效。 ``

  1. 重启电脑

确保 WSL 生效。


步骤 2:安装 Ubuntu 发行版

  1. 打开 Microsoft Store
  2. - 搜索 Ubuntu(推荐选择最新的 LTS 版本,如 Ubuntu 22.04 LTS)。
  3. 点击安装
  4. - 安装完成后,从开始菜单打开 Ubuntu,会提示设置用户名和密码(Linux 环境专用,无需与 Windows 相同)。

在设置完用户名和密码后,Ubuntu 的命令提示符中会显示以下内容:

bash
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.6.87.2-microsoft-standard-WSL2 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Tue Aug 19 11:21:39 CST 2025

  System load:  0.53                Processes:             62
  Usage of /:   0.1% of 1006.85GB   Users logged in:       0
  Memory usage: 3%                  IPv4 address for eth0: 172.20.77.158
  Swap usage:   0%

This message is shown once a day. To disable it please create the
/home/alvin-li/.hushlogin file.
alvin-li@66CCFF:~$

这样 Ubuntu 系统本身就已经安装完成了,我这里装的是 24.04.1 版本。


步骤 3:配置 WSL 和 Ubuntu

  1. 更新软件包列表

在 Ubuntu 终端中运行:

``bash sudo apt update && sudo apt upgrade -y ``

  1. 安装基础开发工具

``bash sudo apt install -y build-essential gcc g++ cmake make git ``

  • build-essential:包含 GCC、Make 等基础工具。
  • - cmake:CMake 构建工具。

输入完这两行之后,Ubuntu 会自动安装一堆东西,静候即可,直到再次出现 alvin-li@66CCFF:~$


步骤 4:验证环境

  1. 检查 GCC 和 CMake

``bash gcc --version # 查看 GCC 版本 cmake --version # 查看 CMake 版本 ``

分别返回 gcc 和 CMake 的版本号即为安装成功。


步骤 5:安装 VS Code 和必要扩展

  1. 下载并安装 VS Code
  2. - 从官网下载安装:Visual Studio Code
  3. 安装 Remote - WSL 扩展
  4. - 打开 VS Code,点击左侧活动栏的 扩展图标(或按 Ctrl+Shift+X)。
  5. - 搜索 Remote - WSL(由 Microsoft 官方提供),点击安装。

步骤 6:连接 WSL 中的 Ubuntu

  1. 启动 WSL 终端
  2. - 在 Windows 开始菜单中打开已安装的 Ubuntu(或通过 wsl 命令启动)。
  3. 从 WSL 内启动 VS Code
  4. - 在 Ubuntu 终端中,导航到你的项目目录(例如 cd ~/projects),然后运行:

``bash code . ``

  • 这会自动在 Windows 的 VS Code 中打开当前 WSL 目录,并建立远程连接。

步骤 7:配置开发环境

安装 C/C++ 扩展

  • 在 VS Code 的扩展市场中搜索 C/C++(Microsoft 官方扩展),安装时会提示 "Install in WSL: Ubuntu",选择在 WSL 中安装。

至此,一个简单的 Linux 本地开发环境就配置完成了。


CMake 概述

一款项目构建工具,可以实现大型项目的自动化管理。主体为 CMakeLists.txt 脚本,在里面部署一些指令,CMake 构建时会按文件中的命令调用对应的工具链部分,从而自己完成整个项目的编译。

关键词跨平台、自动建立 toolchain 编译规则、多开发环境支持(VS、Qt、Clion)

构建流程:预编译(预处理)→ 编译 → 汇编 → 链接

若源代码比较少可以通过命令行去编译,但是一旦文件多了就不好处理,此时可以用 CMake 解决。CMake 不依赖于平台,比 Makefile 高级,可以对应不同平台创建对应的 Makefile。

学习 CMake 的成本较 Makefile 更低,效率更高。本次课程会学习一些常用 CMakeLists.txt 中的命令,可以解决 60% - 70% 的构建问题。

课程基于 Linux 开发环境讲解,所以在一切开始之前,需要先在本地部署带 CMake 和完整匹配的工具链的 Linux 虚拟机进行学习。(没用 Linux 云服务器是因为看了看各家都有点小贵,鉴于 CMake 的学习周期较短,直接干本地就完事了)


编写一个简单的 CMakeLists.txt 文件

首先准备了 5 个 .cpp 源文件和一个 .h 头文件,用于后面 CMake 构建:

cpp
// 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
cpp
// add.cpp

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

int add(int a, int b)
{
    return a + b;
}
cpp
// sub.cpp

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

int subtract(int a, int b)
{
    return a-b;
}
cpp
// mult.cpp

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

int multiply(int a, int b)
{
    return a*b;
}
cpp
// div.cpp

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

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

#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;
}

这五个文件是在同一个目录下的,这里我放在了 ~/CMake/demo 中。

用命令行方式编译上面的小程序其实也是可以实现的,只需要在命令行中输入:

bash
g++ *.cpp -o app

即可在相同路径下编译出一个 app 可执行文件,这时候再输入:

bash
# 运行可执行文件
./app

可以看到终端中成功运行了上述程序。

这样虽然可以正常执行编译流程,但是毕竟这只是一个一共只有 6 个文件的小程序,如果源文件和头文件的数量级来到数百上千,又或者在编译的时候需要引入额外的依赖文件或库,命令行的方式就不再实用了,而要用 CMake 构建。在这里为了演示原理,接下来用 CMake 处理上面的程序:

先在目录中创建一个文件,名为 CMakeLists.txt (一个字都不能错,包括大小写),并写入:

cpp
cmake_minimum_required(VERSION 3.25)
project(test)
add_executable(app add.c sub.cpp mult.cpp div.cpp main.cpp)
  • cmake_minimum_required:指定使用的 CMake 的最低版本,可选
  • project:定义工程名称和其他参数

``cpp # 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:定义工程会生成一个可执行程序,指定 .exe 的名字

``cpp add_executable(可执行程序名 源文件名称) ``

add_executable() 中后面的参数一定是源文件(.cpp),不能有头文件(.h)。

CMakeLists.txt 准备好之后,在同目录的终端里运行 cmake 命令即可开始构建。

构建完成后发现同目录下多了一堆文件,其中存在一个 Makefile 文件,然后在终端中输入 make 命令,即可获得可执行程序 app,输入 ./app 运行该程序,发现程序成功运行。

:执行 cmake 命令时,命令后面跟的路径一定是 CMakeLists.txt 所在的路径


CMake 中 set 的使用

CMake 中可以使用 set 命令设置变量,变量类型为字符串,变量定义时名字不能重复。

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

VAR:变量名 / VALUE:变量值

个人理解:所谓的变量其实是多个源代码文件的一个集合,在有些项目中,某些源代码文件集合(好多个源代码文件)可能在编译的时候需要多次调用,每次都直接写在 executable 后面的括号中太繁琐了,故用 set 命令定义一个变量,变量中包含了某固定批量的源代码文件,这样在需要这批源文件时,直接将定义的变量名放进 executable 即可。

部分使用方法

  • 定义文件集合,即可将一堆文件集合组成的变量放入 add_executable() 方便调用

``cpp # 方式1: 各个源文件之间使用空格间隔 et(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}) ``

:变量名一定要放入 ${ } 中才能成功调用。

  • 设置 C++ 标准

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

:也可另选在执行 cmake 命令的时候指定标准。

  • 使用 EXECUTABLE_OUTPUT_PATH 宏指定最终生成的可执行文件生成文件

``cpp set(HOME /home/robin/Linux/Sort) # 定义一个变量用于存储一个绝对路径 set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin) # 将拼好的路径值设给EXECUTABLE_OUTPUT_PATH # 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建 ``

:变量存储路径时,建议使用绝对路径


搜索文件

使用 set 命令声明变量后虽然 add_executable 中的内容变得简洁了,但其实还没有解决本质问题,那就是需要手动输入每个需要参与编译的文件。这时就需要一种命令,可以帮我们搜集所有需要的的文件,在 CMake 中,可以使用 aux_source_directory 命令或者 file 命令实现。

  • aux_source_directory 命令可以查找某个路径下的所有源文件(.cpp),命令格式为:

``cpp # 指令语法: aux_source_directory(< dir > < variable >) ``

dir:要搜索的目录 / variable:将从 dir 目录下搜索到的源文件列表存储到该变量中

用例:搜索当前目录的 src 目录下所有的源文件(.cpp),并存储到变量中。

``cpp cmake_minimum_required(VERSION 3.0) project(CALC) # 搜索 src 目录下的源文件 aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST) add_executable(app ${SRC_LIST}) ``

  • file 命令也可以做到上面的事情(当然,除了搜索以外还可以做其他事情),命令格式为:

``cpp # 指令语法: file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型) ``

GLOB: 将指定目录下 搜索到的 满足条件的 所有文件名 生成一个列表,并将其存储到变量中 GLOB_RECURSE:功能同上,但是会递归搜索指定目录(目录中的所有子文件夹也搜)

用例:搜索当前目录的 src 目录下所有的源文件(.cpp),并存储到变量中。

``bash 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 文件所在的路径。


指定头文件路径

当项目体积增大,文件变的多而繁杂的时候,一般都会将源文件和头文件放进不同的路径中管理。这就会出现某些头文件的路径不与 CMakeLists.txt 所在路径一致。CMakeLists.txt 中没有描述这些头文件的声明,此时 CMake 就会自然而然的以为构建需要用到的头文件(.h)都处在和 CMakeLists.txt 相同的路径中,从而在后面执行 make 命令的时候导致 make 找不到需要的头文件从而报错。这时候就需要有一个 CMake 命令去指定头文件在哪里。在 CMake 中,我们用 include_directories() 命令来定义头文件的路径,从而帮助项目正确构建。

  • include_directories() 命令可以显示声明头文件所在的路径,命令格式为:

``cpp # 指令语法: include_directories(< headpath >) ``

headpath:头文件所在路径(一定是路径!不能是头文件名或其集合变量!)

用例 1:若头文件都在 CMakeLists.txt 所在路径中的一个叫 include 的文件夹内

cpp
# 指定头文件的位置
include_directories(${PROJECT_SOURCE_DIR}/include)

用例 2:和 set 命令搭配使用显示指定头文件路径

cpp
# 定义包含目录的变量 INCLUDE_DIRS
set(INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/include)

# 使用变量
include_directories(${INCLUDE_DIRS})

通过 CMake 制作库文件

项目代码写完,一般会将项目 release 到公共平台上。有时我们并不想让别人知道我代码中的实现逻辑是什么,也不想让源码公开,而项目运行却偏偏需要这些源码。这时为了让别人仅使用功能、不获取源码,可以将源码打包成库的方式发放给别人。这些库的本质上其实还是机器码、符号表等在主程序执行时会调用的内容,但跟 .cpp 不同的是它是以二进制的形式保存的,只有机器能看懂。

库的命名方式一般为 lib + 库名 + 后缀 的形式,分为两种类型,一种是静态库,一种是动态库。静态库在 Windows 中以 .lib 结尾,在 Linux 中以 .a 结尾,且其不可执行;动态库在 Windows 中以 .dll 结尾,在 Linux 中以 .so 结尾,且在机器的视角中,动态库为可执行文件(不完整,无程序入口点,只有部分可执行代码)。在 CMake 中,可以使用 add_library() 命令生成库文件。

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

``cpp # 制作静态库 add_library(库名称 STATIC 源文件1 源文件2 ...) ``

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

``cpp # 制作动态库 add_library(库名称 SHARED 源文件1 源文件2 ...) ``

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

用例 1:和 file 命令搭配生成不同的库

cpp
# 收集生成库用到的文件
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")

# 生成静态库
add_library(calc_st SHARED ${SRC_LIST})

# 生成动态库
add_library(calc_sh SHARED ${SRC_LIST})

用例 2:搭配 set 命令和 LIBRARY_OUTPUT_PATH 宏指定库的输出路径

cpp
# 收集生成库用到的文件
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")

# 用 set 命令指定静态库、动态库文件输出路径
set(LIBRARY_OUTPUT_PATH /home/alvin-li/CMake/demo_v1)

# 生成静态库到上述路径中
add_library(calc_st SHARED ${SRC_LIST})

# 生成动态库到上述路径中
add_library(calc_sh SHARED ${SRC_LIST})

在程序中链接静态库

获得的库文件是为了将来在可执行程序中调用的。先来说一下静态库的调用方式,若一个可执行程序在运行时需要调用静态库时,静态库就会在可执行程序开始运行的时候被加载到内存中。在 CMake 中,用如下命令可以调用静态库:

cpp
# 调用静态库,括号中依次写出调用库的名字即可
link_libraries(<static lib> <static lib>...)

用例 1:假设选择我们有静态库文件 libcalc.a

cpp
# 在构建时链接静态库
link_libraries(calc)

:括号内的库名可以是掐头去尾的 calc,也可以是完整名称 libcalc.a

用例 2:若需要调用的库不为系统自带的库(自己制作或第三方提供的库),则需要显示声明这些库 的路径在哪,在 CMake 中,使用 link_directories() 命令即可:

cpp
# 声明静态库路径,假设该库在 CMakeLists.txt 所在路径中的 lib 文件夹内
link_directories(${PROJECT_SOURCE_DIR}/lib)

# 链接静态库
link_libraries(calc)

link_directories() 命令一定要写在 add_executable() 命令之前;如有多个库待链接,也可以使用 file 命令定义库集合变量再链接。


在程序中链接动态库

动态库和静态库最大的调用区别就是链接到可执行程序文件的时机,静态库是在可执行程序编译时就被链接到可执行程序中的,在程序启动时就会被同步映射到虚拟内存当中;而动态库只有当可执行程序运行时调用,才会被链接、加载到虚拟内存空间。而且动态库可以同时被多个进程共享调用,静态库则会被复制多份在不同进程中各自工作。在 CMake 中,用以下方式链接动态库:

cpp
# 链接动态库,这个命令要放在目标创建之后(add_executable() 之后)调用
target_link_libraries(
    <target>
    <PRIVATE|PUBLIC|INTERFACE> <item>...
    <PRIVATE|PUBLIC|INTERFACE> <item>...)

target:动态库要链接到的文件的名字: 该文件可能是一个源文件(.cpp), 该文件可能是一个动态库(.so / .dll), 该文件可能是一个可执行文件(.exe

PRIVATE | PUBLIC | INTERFACE:动态库的访问权限,默认(不写)为 PUBLIC

访问权限详解

动态库的链接具有传递性,如果动态库 A 链接了动态库 B、C,动态库 D 链接了动态库 A,此时动态库 D 相当于也链接了动态库 B、C,并可以使用动态库 B、C 中定义的方法。若动态库之间没有依赖关系,无需做任何设置,使用默认的 PUBLIC 即可。

如果对保密性有要求,不希望库中的函数被多次传递或定位,可以在链接动态库时使用访问权限关键字限制传递权限,具体解释如下:

  • PRIVATE (保密性最高)

在 private 后面的库仅被链接到前面的 target 中,并且止步于此,后续无法传递给其他依赖 target 的文件,后续文件无法使用该库中的内容。

  • PUBLIC (无保密性)

在 public 后面的库会被链接到前面的 target 中,并且里面的内容也会被传递给所有依赖者。

  • INTERFACE (保密性中等)

在 interface 后面的库不会被链接到 target 中,但后续传递者需要链接该库,一般为头文件库。

用例 1:将动态库 libpthread.so 链接到可执行程序 app 上:

cpp
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC})

# 指定可执行程序要链接的动态库名字,这里默认权限为 PUBLIC
target_link_libraries(app pthread)

:库名和静态库一样,可以是掐头去尾的 pthread,也可以是完整名称 libpthread.so

用例 2:将第三方动态库 libcalc.so 链接到可执行程序 app 上,需要先显示声明库路径

cpp
# 指定要链接的动态库的路径,路径声明要放在 add_executable() 之前
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib_sh)

# 添加并生成一个可执行程序
add_executable(app ${SRC})

# 指定要链接的第三方动态库
target_link_libraries(app calc)

在 CMake 中打印日志信息

与其他的编程语言一样,在 CMake 中也有运行时往控制台输出消息内容的命令。在这里,运行时是指执行 cmake 命令后,CMake 开始构建时所在控制台输出的消息。CMake 输出消息的命令如下:

cpp
# CMake 中构建时在调试台打印消息
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 区域显示所有消息。

疑问stdoutstderr 的区别和关系是什么? > 💡 1. 标准输出 (stdout) - 目的:用于输出程序的常规、预期的结果。 - 内容:程序正常运行产生的数据、请求的信息、最终的计算结果等。 2. 标准错误 (stderr) - 目的:用于输出错误消息、警告、诊断信息、状态日志。 - 内容:错误信息(如“文件未找到”)、警告、调试信息、进度更新等。

用例

cpp
# 普通消息打印到控制台
message("xxxxxxxxxxxxxxxxxxx")

# 状态消息打印到控制台
message(STATUS "11111xxxxxxxxxxxxxxxxxxx")

# 致命错误消息打印到控制台
message(FATAL_ERROR "22222xxxxxxxxxxxxxxxxxxx")

# 状态消息打印到控制台,不执行
message(STATUS "33333xxxxxxxxxxxxxxxxxxx")

以上部分输出内容:

bash
# 普通消息
xxxxxxxxxxxxxxxxxxx

# 状态消息 STATUS
-- 11111xxxxxxxxxxxxxxxxxxx

# 致命错误消息 FATAL_ERROR
CMake Error at CMakeLists.txt:61 (message):
  22222xxxxxxxxxxxxxxxxxxx

# 后续构建不执行,看不到 33333xxx...

字符串操作


第二份 Notion 原文:关于编译的底层逻辑(Qt 学习导向)

打开原始 Markdown

关于编译的底层逻辑(Qt 学习导向)

核心关联线(从 A-E 依次关联)


大纲

💡

与 A 的关系:必须先有 A(环境变量/SDK)才能找到头文件和库

  • [C. 构建系统](#c-构建系统)(自动化流程编排)
  • - **CMake(规则生成器)**:读 CMakeLists.txt → 生成“构建规则图”( build.ninja
  • - **Ninja(执行器)**:读取 build.ninja → 高并发、按依赖图编译/链接
  • - 构建三阶段Configure(找工具/库)→ Generate(产出规则)→ Build(执行规则)

💡

与 B 的关系:CMake/Ninja 并不编译代码本身,它们“指挥” cl / link 去干活

💡

与 C 的关系:D 的输出(发现的库、包)直接为 C 的 target_link_libraries 输入

  • [E. Qt 专属知识](#e-qt-专属知识)(为什么要“告诉” CMake Qt 在哪)
  • - Qt 常用模块Qt6::Core / Qt6::Gui / Qt6::Widgets …(CMake 导入目标)
  • - **自动工具**moc / uic / rcc(CMake 有 AUTOMOC/AUTOUIC/AUTORCC 自动处理)
  • - 二进制兼容:编译器版本/架构必须匹配(例如 msvc2019_64 就要用 MSVC 2019 的 x64)
  • - **运行时部署**:编译出的 .exe 运行需要对应的 Qt .dll(后面会用 windeployqt 解决)

💡

与 D 的关系:Qt 模块、工具链依赖 D 的结果,确保 CMake 正确调 moc/uic 与各种库


A. 平台与架构

x86 为 32 位架构,最早在 1978 年从 Intel 8086 开始,Windows 长期主导。

x64 为 64 位架构,2000 年左右 AMD64 扩展的 64 位架构后成为主流。

不同架构的区别

寄存器的宽度:寄存器 32 位宽与 64 位宽的区别。

内存寻址空间:4GB 与 16EB 的区别。

🔖

内存寻址空间泛指 CPU 可以直接访问的内存空间大小,32 位系统最多可以访问 4GB 的内存,在性能发挥上受到了一定限制,而 64 位系统可访问内存的空间大小理论值为 16EB(1EB = 10^18字节),大大提高了系统的性能上限。

注:

  • 内存寻址空间本质是由 CPU 的地址总线宽度决定的。
  • 因操作系统或硬件保留部分地址空间(如 BIOS 等),32位系统中,用户可用内存一般小于 4GB;64 位系统的实际寻址空间一般也小于理论值(如 x86-64 架构通常只实现 48 位或 52 位物理地址,即 256TB 或 4PB),但远超 32 位的 4GB。
  • 更大的寻址空间支持更多内存,能避免频繁的磁盘交换,让更多数据在内存中处理。

额外补充:

  • 现代操作系统通过分页(Paging)管理内存,虚拟地址空间可以大于物理内存。

调用约定的区别:32 位主要用栈传参;64 位前 4-6 个用寄存器,其余用栈传参。

🔖

调用约定定义了函数调用的时候如何传递参数,如何分配寄存器,如何管理栈。它确保了不同模块(编译器生成的代码、汇编代码、系统库)之间的正确交互。

注:

  • 32 位系统常见的调用约定有:
  • - cdecl(C Declaration、C 语言默认)
  • - stdcall(Windows API 常用)
  • - fastcall(部分参数通过寄存器传递)
  • - thiscall(C++ 成员函数调用)
  • 64 位系统常见的调用约定有:
  • - System V AMD64 ABI(Linux/macOS)
  • - Microsoft x64(Windows)
  • 为什么 64 位系统改用寄存器传参?
  • - 性能优化:寄存器访问比栈快,减少内存读写
  • - 减少栈操作:传统 32 位方式频繁 push/pop,64 位减少栈依赖
  • - 统一规范:System V 和 Microsoft 都采用寄存器优先,提高兼容性

额外补充:32 位 vs 64 位调用约定的关键区别表

特性32 位64 位
主要调用约定cdeclstdcallfastcallSystem V AMD64Microsoft x64
参数传递主要用栈(fastcall 用部分寄存器)前 4-6 个参数用寄存器,其余用栈
栈清理责任cdecl(调用者),stdcall(被调用者)调用者清理(64 位统一)
this 指针thiscallecx 或栈)rcx(Windows),rdi(System V)
浮点参数栈传递xmm0-xmm7(寄存器传递)
影子存储区Windows x64 要求 32 字节预留
其他补充区别: - 32 位 CPU 无法原生运行64位系统,但 64 位 CPU 通常兼容 32 位模式 - 64 位系统拥有大文件(文件单体超过 4GB)的处理能力 - 64 位系统安全性更强(如 x86-64 引入 NX bit 防止代码注入攻击) - 32 位系统可以运行所有 Windows 版本,64 位系统需要从 WinXP x64 开始才兼容

操作系统 & 文件系统

环境变量作用原理:环境变量是以键值对(Key-Value)形式存储在操作系统内核中的全局数据,所有进程都可以继承或修改。进程的继承机制如下图所示:

deepseek_mermaid_20250814_8efe1c.png
deepseek_mermaid_20250814_8efe1c.png

环境变量存储在进程环境块(PEB)中,每个进程都有独立副本;在内存中实际存储形式是连续字符串,以\0分隔,最后以双\0结束,示例:

PATH=C:\Windows\system32\0TEMP=C:\Temp\0USERNAME=Alice\0\0
↑                        ↑ ↑                     ↑
|________________________| |_____________________|
       第一个变量               第二个变量

在 Windows PowerShell 中输入如下代码,可以查看当前系统的环境变量配置路径:

powershell
# 查看当前PATH(分号分隔)
echo $env:PATH
  • $env:INCLUDE:头文件搜索路径(影响#include <windows.h>
  • $env:LIB:静态库搜索路径(影响link.exe查找.lib文件)

本人电脑中的环境变量返回显示路径如下:

powershell
C:\Program Files\Common Files\Oracle\Java\javapath;  # Oracle Java 默认路径(可能指向系统默认的 Java 版本)
D:\Develop\JDK\bin;                                  # 自定义安装的 JDK 二进制目录(用于开发,如 javac/java)
C:\Program Files (x86)\Common Files\Oracle\Java\javapath;  # 32 位 Oracle Java 路径(兼容旧版应用)
D:\Software\qq\Bin;                                  # QQ 客户端程序目录(可能包含 QQ 相关命令行工具)
C:\Program Files (x86)\Common Files\Intel\Shared Libraries\redist\intel64\compiler;  # Intel 编译器运行时库(支持 Intel 优化代码)
C:\Windows\system32;C:\Windows;                      # 系统核心目录(包含基础命令如 cmd.exe)
C:\Windows\System32\Wbem;                            # WMI (Windows Management Instrumentation) 工具目录
C:\Windows\System32\WindowsPowerShell\v1.0\;         # PowerShell 1.0 基础命令(兼容旧脚本)
C:\Windows\System32\OpenSSH\;                        # OpenSSH 客户端/服务端工具(如 ssh/scp)
C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;  # NVIDIA PhysX 物理引擎运行时文件
C:\Program Files\dotnet\;                            # .NET Core/Runtime 命令行工具(如 dotnet 命令)
C:\WINDOWS\system32;C:\WINDOWS;                      # 重复的系统目录(部分应用可能依赖)
C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;  # 重复的 WMI 和 PowerShell 路径
C:\WINDOWS\System32\OpenSSH\;                        # 重复的 OpenSSH 路径
C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;  # SQL Server ODBC 工具(如 sqlcmd)
C:\Program Files (x86)\Microsoft SQL Server\150\Tools\Binn\;  # SQL Server 2019 32 位工具
C:\Program Files\Microsoft SQL Server\150\Tools\Binn\;  # SQL Server 2019 64 位工具
C:\Program Files\Microsoft SQL Server\150\DTS\Binn\;  # SQL Server 数据转换服务 (ETL 工具)
C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;  # Windows 性能分析工具(如 xperf)
D:\Program Files\MATLAB\R2021a\runtime\win64;        # MATLAB 运行时环境(支持独立程序)
D:\Program Files\MATLAB\R2021a\bin;                  # MATLAB 主程序目录(如 matlab.exe)
C:\Program Files\NVIDIA Corporation\NVIDIA app\NvDLISR;  # NVIDIA 图形驱动相关组件(DLSS/图像缩放)
C:\Program Files (x86)\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility;  # STM32 开发板烧录工具
C:\Program Files\Git\cmd;                            # Git 命令行工具(如 git.exe)
C:\Program Files\CMake\bin;                          # CMake 构建工具(如 cmake.exe)
C:\Users\123\AppData\Local\Microsoft\WindowsApps;    # 用户级 Windows 应用商店程序(如 Python3/pip3)
D:\Develop\IDEA\IntelliJ IDEA Community Edition 2023.1.3\bin;  # IntelliJ IDEA 启动脚本
D:\Develop\CLion 2023.3.2\bin;                       # CLion (C/C++ IDE) 启动脚本
C:\Users\123\AppData\Local\Programs\Microsoft VS Code\bin  # VS Code 命令行工具(如 code.cmd)

Windows SDK

SDK(Software Development Kit)是微软提供的开发 Windows 应用程序的工具包,包含:

  • 头文件(.h):声明API函数和数据结构(如 windows.h
  • 库文件(.lib/.dll):API的实现(如 kernel32.lib + kernel32.dll
  • 工具集(运行时组件):编译器、调试器、分析工具等
  • 文档和示例:API使用说明和代码范例

SDK 与 Visual Studio 的关系如下图所示:

deepseek_mermaid_20250814_72624b.png
deepseek_mermaid_20250814_72624b.png

Windows SDK 核心组件详解

SDK 中,首先包括头文件,头文件目录结构如下:

C:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\
├── um/          # 传统Win32 API头文件
│   ├── windows.h      # 核心头文件
│   └── winuser.h      # 用户界面相关
└── shared/      # 通用头文件
    ├── guiddef.h      # GUID定义
    └── minwindef.h    # 基础类型定义

关键头文件

  • windows.h:几乎所有Win32程序都需要包含
  • fileapi.h:文件操作API声明
  • processthreadsapi.h:进程/线程管理API

然后是库文件,库文件目录结构如下:

C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\
├── um/
│   ├── x86/     # 32位库
│   │   └── kernel32.lib
│   └── x64/     # 64位库
│       └── kernel32.lib
└── ucrt/        # 通用C运行时库

库类型

类型作用示例
导入库(.lib)提供DLL函数的符号链接kernel32.lib
静态库(.lib)直接包含代码到可执行文件libcmt.lib
UCRT库标准C函数实现ucrtbase.lib

最后是运行时组件(Runtime),包括以下两种组件:

  • 系统DLL:C:\Windows\System32\ 下的核心组件,例如:

`` kernel32.dll # 基础系统服务 user32.dll # 用户界面功能 gdi32.dll # 图形设备接口 ``

  • 可再发行包:通过 **vcredist_*.exe** 分发,不过多讨论

组件依赖关系(重点)

  • 程序编译期间,依赖头文件(.h)
  • 程序链接期间,依赖库文件(.lib)
  • 程序运行期间,依赖运行时组件(.dll)

B. 编译器工具链

cl.exe(编译器)

核心作用:像翻译官,把 C++ 代码"翻译"成 CPU 能理解的二进制指令

输入.cpp/.h 源代码文件

输出.obj 目标文件(包含机器码但未最终组装)

典型命令与参数

powershell
cl /c /EHsc /Iinclude /O2 /Zi main.cpp helper.cpp
参数作用等效GCC参数
/c只编译不链接-c
/EHsc启用C++异常处理-fexceptions
/Iinclude添加头文件搜索路径-Iinclude
/O2启用优化-O2
/Zi生成调试信息-g
疑问:编译器优化是什么意思? > 🔖 编译器优化是在保持程序逻辑不变的前提下,对代码进行等价变形,使其: 1. 运行速度更快(减少CPU指令) 2. 内存占用更小(精简数据布局) 3. 功耗更低(减少不必要的计算) :当使用 /02 优化时,编译器会执行以下动作: | 优化技术 | 作用示例 | 原始代码 | 优化后代码 | | --- | --- | --- | --- | | 常量传播 | 替换为已知常量 | **int x=5; y=x*2; | y=10; | | 循环展开 | 减少循环开销 | for(i=0;i<3;i++) sum+=i; | sum+=0; sum+=1; sum+=2; | | 删死代码 | 删不可达代码 | if(false) {...} | (完全删除) | 不同优化级别对比表(MSVC) | 选项 | 别名 | 优化强度 | 适用场景 | | --- | --- | --- | --- | | /Od | 无优化 | 0 | 调试开发阶段 | | /O1 | 精简 | 中等 | 需要平衡大小和速度 | | /O2 | 速度 | 高 | 发布版本(默认推荐) | | /Ox** | 最大 | 极高 | 性能关键代码 |

lib.exe(静态库打包器)

核心作用:用于创建、管理和提取 .lib 格式的静态库文件,便于代码复用和项目管理

输入:多个.obj文件

输出:单个.lib静态库文件

使用示例

powershell
# 示例1:创建静态库
lib.exe /out:utils.lib utils.obj math.obj /NOLOGO

# 示例2:向库中添加新模块
lib.exe utils.lib /out:utils_v2.lib new.obj

# 示例3:删除库中的模块
lib.exe utils.lib /remove:old.obj

# 示例4:查看库内容
lib.exe /list utils.lib

基本命令

命令格式说明
lib.exe /out:<库名>.lib <文件1.obj> <文件2.obj> ...将多个 .obj 文件打包成静态库。
lib.exe <旧库名>.lib /out:<新库名>.lib <新增文件.obj>向现有库添加或替换文件。
lib.exe <库名>.lib /remove:<文件名.obj>从库中删除指定模块。
lib.exe <库名>.lib /extract:<文件名.obj> /out:<输出路径.obj>提取库中的特定模块为 .obj 文件。
lib.exe /list <库名>.lib列出库中所有模块(符号表)。

常用选项

选项作用
/out:<文件名>.lib指定输出库文件名(默认取第一个 .obj 文件名)。
/NOLOGO隐藏版权和版本信息(静默模式)。
/VERBOSE显示详细处理过程。
**`/MACHINE:x86x64
**`/LINKERMACHINE:x86x64`**
**`/SUBSYSTEM:CONSOLEWINDOWS`**

link.exe(链接器)

核心作用:负责将 目标文件(.obj)、静态库(.lib)、动态库(.dll) 等链接成最终的可执行文件(.exe.dll 等)。它是程序构建的最后一步,直接影响程序的运行行为、内存布局和依赖关系。

输入和输出

输入文件输出文件
.obj(目标文件);.lib(静态库).exe(可执行文件);.dll(动态链接库)
.res(资源文件).sys(驱动程序)
.def(模块定义文件).pdb(调试符号文件)
疑问:把 obj 文件打包转换成 lib 文件不是 lib.exe 的事么?为什么这里将多个 obj 文件合成可执行文件或动态库是 link.exe 的活?还是说 lib.exe 只管打包成静态库 lib,而 link.exe 会打包成动态库和 exe?如果是这样的话,编译流程中如何判断一个 obj 是要打包成静态库还是动态库 / exe? > 🔖 理解基本正确。lib.exe 负责将多个 .obj 文件打包成静态库(.lib),生成的是可复用的代码集合,供后续链接使用;而 link.exe 则是将 .obj.lib 文件链接成最终的可执行文件.exe)或动态库.dll),负责解析符号依赖、分配内存布局并指定程序入口。静态库和动态库/可执行文件的生成由构建流程(如CMake)决定:若目标是代码复用(如工具库),用 lib.exe 打包成 .lib;若目标是运行程序,则用 link.exe 生成 .exe/DLL 选项生成 .dll.obj 文件本身是中性中间文件,其最终用途取决于调用的是 lib.exe 还是 link.exe

基本语法

powershell
link.exe [选项] [输入文件.obj/.lib] [/out:输出文件.exe]

常用选项

选项说明
/OUT:<文件名>指定输出文件名(默认取第一个 .obj 文件名)
/LIBPATH:<路径>指定库文件的搜索路径(类似 -L in GCC)
/DEFAULTLIB:<库名>指定默认链接的库(如 kernel32.lib
**`/SUBSYSTEM:CONSOLEWINDOWS`**
/ENTRY:<函数名>指定程序入口(默认为 main 或 WinMain
/DEBUG生成调试信息(.pdb 文件)
/DLL生成动态链接库(.dll
/NOLOGO隐藏版权信息(静默模式)
/VERBOSE显示详细链接过程
**`/MACHINE:x86x64

用法示例

powershell
# 基础控制台程序
link.exe main.obj utils.obj /OUT:myapp.exe /SUBSYSTEM:CONSOLE

# 链接静态库(指定库目录)
link.exe app.obj /LIBPATH:libs mylib.lib /OUT:app.exe

# 基础DLL
link.exe /DLL dllmain.obj helper.obj /OUT:mylib.dll
# 生成DLL+导入库(自动生成mylib.lib)

# 指定入口函数(GUI程序)
link.exe winmain.obj /SUBSYSTEM:WINDOWS /ENTRY:WinMainCRTStartup

# 调试模式(生成PDB)
link.exe debug.obj /DEBUG /OUT:debug_app.exe

产物与中间件

总的来说,在开发过程中,编译流程会生成中间产物和最终产物,它们的关系如下表所示:

阶段工具输入输出作用
编译 clcl.exe.cpp/.h.obj将源码转为机器码(未链接)
静态库打包 liblib.exe.obj.lib合并多个.obj为可复用的库
链接 linklink.exe.obj/.lib.exe/.dll解析依赖,生成可执行文件
调试 debug-.obj/.exe.pdb存储调试符号(需/DEBUG选项)

各个流程中生成的文件也有各自的角色和特定的功能,它们的类型如下表所示:

扩展名类型说明
.obj中间文件编译器生成的二进制代码(未链接)
.lib静态库一组.obj的集合,供其他程序链接
.dll动态库运行时加载的共享库(需配套.lib导入库)
.exe可执行文件最终程序
.pdb调试符号存储变量/函数名等调试信息
疑问:.pdb 调试符号文件具体是干什么的,有什么用? > 🔖 存储程序二进制代码( .exe 或 .dll)与源代码(.cpp)之间的映射关系。可以将机器码(0b 地址)映射回源代码的变量名、函数名、行号等,使调试器显示人类可读的信息。 :崩溃日志中的 0x00401000 → main.cpp 第 15 行的 calculate() 函数

编译全流程依赖关系(重点)

.cpp → (编译) → .obj → (链接) → .exe/.dll ←—-

| |

原生.obj-- → (打包) → .lib → (被链接) —→

  • 若项目仅需生成可执行文件,可直接用 cl.exe 编译并链接(省略.obj中间步骤)。
  • 若需代码复用,先编译为.obj,再通过 lib.exe 或 link.exe 处理。

C. 构建系统

CMake(规则生成器)

核心作用:跨平台的"构建蓝图设计师":"Write once, build everywhere"

输入:CMakeLists.txt(声明式构建规则)

输出:生成适配不同平台的构建系统文件(如Ninja/MS Build/Makefile)

典型 CMakeLists.txt 结构

cpp
cmake_minimum_required(VERSION 3.20)
project(MyApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)  # 好比选择施工标准(C++17规范)

add_executable(my_app       # 声明要建造的"大楼"(可执行文件)
    src/main.cpp           # 指定"建筑材料"(源文件)
    src/utility.cpp
)

target_link_libraries(my_app
    PRIVATE Qt6::Core      # 指定"外部供应商"(链接库)
)

# 模块化设计
add_library(math STATIC math.cpp)          # 定义"建筑模块"(静态库)
target_include_directories(math PUBLIC include)  # 暴露"接口"

# 条件化构建
option(USE_CUDA "Enable GPU acceleration" OFF)
if(USE_CUDA)
    find_package(CUDA REQUIRED)           # 按需加载"特种设备"
endif()

CMake 阶段流程图

deepseek_mermaid_20250815_a56fb3.png
deepseek_mermaid_20250815_a56fb3.png

🔖

系统性 CMake 学习:

CMake 入门学习记录


Ninja(执行器)

Ninja 是一个专注于极致速度的构建系统(build system),专为管理大型项目的编译流程而设计。

本质上是一个用来构建系统的任务调度器(执行引擎),就像一个工厂流水线上的控制系统,只负责通过接收到的生产指令(build.ninja文件)调度工人(CPU)组装零件(.obj 文件)。

核心设计理念

理念具体表现对比传统Makefile
极简主义仅5000行C++代码通常数万行
单职责原则只做任务执行,不生成规则既生成规则又执行
确定性优先依赖检查基于哈希+时间戳通常仅用时间戳

核心工作流程图

deepseek_mermaid_20250815_2541cb.png
deepseek_mermaid_20250815_2541cb.png

与其他相关概念的联系

概念与 ninja 对比 / 依赖关系类比
CMake生成Ninja的"施工图纸"建筑设计师
cl.exe/gccNinja调用的"施工工人"砌墙的工匠
Makefile功能相似但效率较低的替代品老式流水线控制系统

构建三阶段

源文件(.cpp)构建全流程分为三个阶段:

Configure(找工具/库)→ Generate(产出规则)→ Build(执行规则)

疑问:这三个阶段都是谁做的?关系是什么? > 🔖 Configure + Generate 阶段 → CMake 全权负责 - 读 CMakeLists.txt - 检测电脑环境(找编译器、库的位置) - 生成构建系统能理解的“施工图纸”(如 build.ninja 或 Makefile) 其中“施工图纸”的类型: - build.ninja(Ninja用) - Makefile(GNU Make用) - MyProject.sln(Visual Studio用) Build 阶段 → 由构建工具执行 - 如果是 Ninja: - 读取 build.ninja(CMake生成的图纸) - 调用编译器(cl/gcc)和链接器(link/ld)干活 - 如果是 Visual Studio: - 打开 .sln 文件点击“生成”按钮 - 内部调用 MSBuild 执行编译

Configure 阶段的核心任务和关键技术

  • 工具链探测

``cpp # CMake内部执行的检测逻辑(简化版) if(MSVC) find_program(CL_EXE cl.exe) if(NOT CL_EXE) message(FATAL_ERROR "MSVC compiler not found!") endif() endif() ``

  • 依赖库定位

`` A[find_package(Qt6)] --> B[搜索路径] B --> C[Qt6Config.cmake] C --> D[提取Qt6_INCLUDE_DIRS] C --> E[提取Qt6_LIBRARIES] ``

  • 交叉编译支持

``cpp # 指定交叉编译工具链 set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) set(CMAKE_SYSROOT /opt/rpi/sysroot) ``

  • 缓存变量机制:首次检测结果存入CMakeCache.txt,后续构建直接复用

Generate 阶段的核心任务和关键技术

根据规则生成所对应的构建命令。把在 CMakeLists.txt 里写的“需求清单”(比如要编译哪些文件、需要什么库),翻译成具体的“构建说明书”(比如 build.ninja 或 Makefile)。

输入

  • CMakeLists.txt
  • Configure 阶段探测到的环境信息(比如编译器路径、库的位置)

输出

  • build.ninja(若为 Ninja)
  • Makefile(若为 GNU Make)
  • MyProject.sln(若为 Visual Studio)

处理流程

deepseek_mermaid_20250815_7db53e.png
deepseek_mermaid_20250815_7db53e.png

生成结果示例

cpp
# 定义“怎么编译一个.cpp文件”(规则模板)
rule compile_cpp
  command = g++ -c $in -o $out  # 编译命令
  description = 正在编译 $out    # 进度显示的文字

# 定义“具体要编译哪个文件”(任务清单)
build main.o: compile_cpp main.cpp  # 目标: 规则 输入文件
build utils.o: compile_cpp utils.cpp

# 定义“怎么链接成最终程序”
rule link
  command = g++ $in -o $out
build my_app: link main.o utils.o

CMake 优势:跨平台适配

同一份 CMakeLists.txt 可以生成:

  • Windows 的 build.ninja(用 cl.exe 编译)
  • Linux 的 Makefile(用 g++ 编译)

Build 阶段的核心任务

Build 阶段会调用工具链中的编译器(如 g++/cl.exe)、链接器(如 ld/link.exe)等实际工具,将源代码转换为最终的可执行文件或库。这一阶段由构建工具(如 Ninja、Make 或 MSBuild)直接执行,且会遵循明确的规则和依赖关系。

Build 阶段的特点

  • 依赖驱动:只重新构建已变更的文件及其依赖项(通过时间戳或内容哈希判断)

*例:修改了 main.cpp,只会重新编译 main.obj 和依赖它的目标,不会重编译未变动的文件。*

  • 并行最大化:利用多核 CPU 并发执行独立任务(如同时编译多个 .cpp 文件)
  • 确定性:相同的输入和环境下,每次构建结果完全一致
  • 最小化工作:跳过未变更的任务(通过缓存机制),仅执行必要的操作

*例:未修改的头文件不会触发重新编译。*

🔖

Build 阶段的额外优化机制

  • 资源池控制:限制高内存任务的并发数(如链接器通常单线程运行)
  • 失败恢复:部分构建工具支持断点续建

Build 阶段流程图(以 ninja 位例)

deepseek_mermaid_20250815_5ff10c.png
deepseek_mermaid_20250815_5ff10c.png

D. 包与库发现机制

find_package (Qt6 …)

find_package 是 CMake 提供的一个智能搜索命令,它的核心功能为:

  • 定位第三方软件包(如 Qt、Boost、OpenCV)
  • 加载该包的配置信息(头文件路径、库文件路径、依赖项等)
  • 生成便于使用的 CMake 目标(如 Qt6::Core

find_package 的三大组成部分

  • 搜索机制:查找 <PackageName>Config.cmake 或 Find<PackageName>.cmake 文件
  • 包配置文件:一般为 Config.cmake(一般由库作者提供)或者 Find<Package>.cmake(CMake 或社区编写,兼容旧版本),文件中会定义包的版本信息、导入目标、编译链接参数等
  • 输出结果:缓存变量(如 Qt6_FOUNDQt6_VERSION、导入目标(如包含头文件路径、链接库的 Qt6::Core)、使用接口(通过 target_link_libraries 直接调用)

find_package 的工作流程

deepseek_mermaid_20250818_585daa.png
deepseek_mermaid_20250818_585daa.png
额外补充内容 与其他概念的关系 | 相关概念 | 与 find_package 的关系 | 类比 | | --- | --- | --- | | CMake模块 | Find<Package>.cmake 是传统模块 | 手写说明书 vs 官方说明书 | | 导入目标 | find_package 的输出结果 | 检索系统生成的借书卡 | | CMAKE_PREFIX_PATH | 影响 find_package 的搜索路径 | 图书馆的优先检索区域 | find_package 机制带来的便利 1. 跨平台兼容:不同系统中库的安装路径不同(如 Windows 的 C:\Qt 和 Linux 的 /usr/lib) 2. 版本管理:可指定查找特定版本(如 find_package(Qt6 6.5.3)) 3. 依赖传递:自动处理库的依赖项(如 Qt6 需要 ZLib)

搜索路径优先级

搜索路径优先级是 CMake 查找第三方库时的路径检查顺序规则,一共分为 3 级路径搜索。

第一优先级:CMAKE_PREFIX_PATH

  • 定义:跨项目的通用库搜索路径(相当于送快递时的「常用收货地址列表」)
  • 典型场景:当多个项目共用同一套 Qt 安装时
  • 设置方法:

``cpp # 在CMakeLists.txt中设置(分号分隔多个路径) list(APPEND CMAKE_PREFIX_PATH "C:/Qt/6.5.3/msvc2019_64" "/opt/qt/6.5.3" ) ``

``bash # 或通过命令行传递 cmake -B build -DCMAKE_PREFIX_PATH = "C:/Qt/6.5.3/msvc2019_64" ``

第二优先级:<Package>_DIR(如 Qt6_DIR

  • 定义:精准指定某个包的配置目录(相当于送快递时的「精确到某快递柜编号」)

🔖

注:不能为模糊路径,如 C:/Qt/6.5.3",必须指向包含 Qt6Config.cmake 的具体目录

  • 典型场景:需要强制使用特定版本的 Qt
  • 设置方法:

``cpp # 必须指向包含Config.cmake的目录 set(Qt6_DIR "C:/Qt/6.5.3/msvc2019_64/lib/cmake/Qt6" CACHE PATH "") ``

``bash # 命令行方式 cmake -B build -DQt6_DIR = "C:/Qt/6.5.3/msvc2019_64/lib/cmake/Qt6" ``

第三优先级:系统默认路径

  • 定义:操作系统或环境变量预设的路径(相当于送快递时的「小区默认快递站」)
  • 常见路径
平台典型路径对应环境变量
Windows**C:/Program Files/*/lib/cmake**PATH
Linux/usr/lib/cmake /usr/local/libLD_LIBRARY_PATH
macOS/opt/homebrew/lib/cmakeDYLD_LIBRARY_PATH

路径优先搜索流程图

deepseek_mermaid_20250818_95d4a0.png
deepseek_mermaid_20250818_95d4a0.png

优先级规则总结表

优先级路径类型设置方法适用场景
1CMAKE_PREFIX_PATHlist(APPEND CMAKE_PREFIX_PATH)项目组共享同一套库
2Qt6_DIRset(Qt6_DIR ... CACHE)精确控制特定版本
3系统默认路径自动搜索简单项目或系统级安装

观察到的目录

Qt 的安装目录,以 C:\Qt\6.5.3\msvc2019_64 为例,这个路径是Qt官方定义的标准安装布局,相当于Qt的"身体构造"。目录展开如下:

msvc2019_64/
├── bin/           # 运行时必需品(如DLL、exe工具)
├── include/       # 开发必需品(头文件)
├── lib/           # 链接必需品(.lib/.a文件)
└── lib/cmake/     # CMake集成核心(配置脚本)
    └── Qt6/       # ↓
        ├── Qt6Config.cmake          # 总入口
        ├── Qt6CoreConfig.cmake      # Core模块配置
        └── ...                      # 其他模块配置

子目录的详细解析

bin 目录 - Qt 的"心脏"

  • 核心文件:
  • - Qt6Core.dll:核心功能动态库
  • - qmake.exe:项目生成工具
  • - moc.exe:元对象编译器
  • 使用实例:

``bash # 运行程序时需要这些DLL windeployqt myapp.exe # 自动拷贝依赖的DLL ``

include 目录 - Qt 的"大脑"

  • 关键头文件:

``bash include/ └── QtCore/ # 核心模块头文件 ├── qobject.h # 元对象系统核心 └── ... └── QtGui/ # GUI模块头文件 ``

  • 使用实例:

``cpp #include <QtCore/QObject> // 包含头文件 ``

lib 目录 - Qt 的"骨骼"

文件格式作用示例
.lib(Windows)静态链接库/导入库Qt6Core.lib
.so(Linux)动态库符号链接libQt6Core.so
.prlqmake的库依赖描述文件Qt6Core.prl

lib/CMake/Qt6 - Qt 的"身份证"

  • CMake 集成核心:

``bash Qt6Config.cmake # 声明Qt6的全局配置 Qt6CoreConfig.cmake # 定义Qt6::Core目标 Qt6WidgetsConfig.cmake # 定义Qt6::Widgets目标 ``

  • CMake 内部示例(简化版):

``cpp # Qt6CoreConfig.cmake片段 add_library(Qt6::Core SHARED IMPORTED) set_target_properties(Qt6::Core PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include/QtCore" IMPORTED_LOCATION "${_IMPORT_PREFIX}/bin/Qt6Core.dll" ) ``

目录梳理图

deepseek_mermaid_20250818_1b8e6c.png
deepseek_mermaid_20250818_1b8e6c.png

E. Qt 专属知识

这里是基于 AI 提供的学习大纲所学习的 Qt 部分内容。此外,这段时间还对 Qt 进行了系统化的入门学习,并将学习记录保存了下来。欲了解更多关于 Qt 的系统性内容,还请详细阅读: Qt 入门系统性学习记录

Qt 常用模块

Qt 将不同功能封装成独立的模块,每个模块都是一个即插即用的功能包,通过 CMake 的导入目标机制提供。常用的模块有以下几种:

  • 核心模块(Qt6::Core):提供基础结构(如信号槽、字符串处理)
  • 装饰模块(Qt6::Gui/Qt6::Widgets):添加图形界面能力
  • 扩展模块(Qt6::Network/Qt6::Sql):追加网络/数据库等功能

Qt6::Core - 地基模块

  • 元对象系统(Q_OBJECT宏、信号槽)
  • 基础数据类型(QStringQList
  • 文件/目录操作(QFileQDir
cpp
// 用例:非GUI程序也需要的基础功能
#include <QCoreApplication>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
    qDebug() << "Current path:" << QDir::currentPath();
    return app.exec();
}

Qt6::Gui - 图形基石(更偏向于绘图、图像处理)

  • 窗口系统集成(QWindow
  • 2D绘图(QPainter
  • 图像处理(QPixmap
cpp
// 用例:
#include <QGuiApplication>
#include <QWindow>

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    QWindow window;
    window.setTitle("Pure GUI Window");
    window.show();
    return app.exec();
}

Qt6::Widgets - 桌面UI(更偏向于互动、布局)

  • 控件系统(QPushButtonQLineEdit
  • 布局管理(QVBoxLayout
  • 样式定制(QStyle
cpp
// 用例:
#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QPushButton button("Click Me");
    button.show();
    return app.exec();
}

Qt6::Network - 网络模块

  • TCP协议通信(QTcpSocket
  • UDP协议通信(QUdpSocket
  • HTTP请求处理(QNetworkAccessManager
cpp
// 用例:HTTP客户端
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    QNetworkAccessManager manager;
    QObject::connect(&manager, &QNetworkAccessManager::finished,
        [](QNetworkReply *reply) {
            qDebug() << "Response:" << reply->readAll();
            reply->deleteLater();
            QCoreApplication::quit();
        });

    manager.get(QNetworkRequest(QUrl("https://api.example.com/data")));

    return app.exec();
}

Qt6::Sql - 数据库模块

  • 数据库连接管理(QSqlDatabase
  • SQL语句执行(QSqlQuery
  • 表格数据模型(QSqlTableModel
cpp
// 用例:SQLite操作
#include <QCoreApplication>
#include <QtSql>
#include <QDebug>

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    // 1. 创建数据库连接
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(":memory:");  // 内存数据库

    if (!db.open()) {
        qDebug() << "Database error:" << db.lastError().text();
        return 1;
    }

    // 2. 执行SQL
    QSqlQuery query;
    query.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
    query.exec("INSERT INTO users VALUES(1, 'Alice')");

    // 3. 查询数据
    query.exec("SELECT * FROM users");
    while (query.next()) {
        qDebug() << query.value("id") << query.value("name");
    }

    return 0;
}

其他常用扩展模块

模块名功能领域典型类是否需要额外依赖
Qt6::Multimedia音视频QMediaPlayer可能需要编解码器
Qt6::Charts数据可视化QChartView不需要
Qt6::Bluetooth蓝牙通信QBluetoothDeviceInfo需要系统蓝牙支持
额外补充内容: 1. 扩展模块需要在 CMake 配置中显式声明,例如若需调用 Network 库: ``cpp // CMakeList 中需声明: find_package(Qt6 REQUIRED COMPONENTS Network) target_link_libraries(my_app PRIVATE Qt6::Network) `` 2. 模块之间可以自由组合(如Network+Sql实现数据同步) 3. 某些模块需要额外系统依赖(如数据库驱动)

自动工具

自动工具主要指 Qt 提供的用于自动化构建、测试和部署的工具集,其中包括了构建工具(qmakeCMake)、代码生成工具(mocuicrcc)、测试工具(Qt Test)、部署工具(Qt Installer Framework)与第三方工具(如 GitHub Actions)。

这里延续上面的思路,分析代码生成工具mocuicrcc)的作用。当使用 qmake 或 CMake 构建时,这些工具会在编译过程中自动触发,无需手动运行。

moc(Meta-Object Compiler,元对象编译器)

处理 Qt 的元对象系统(如 Q_OBJECT 宏、信号槽、动态属性等),生成额外的 C++ 代码(**moc_*.cpp** 文件)。

疑问:元对象系统是什么? > 🔖 - 元对象系统是 Qt 的核心,它让 C++ 在运行时能动态获取类的信息(比如类名、信号槽),实现普通 C++ 做不到的功能。 - 信号槽靠它驱动,比如 emit signal() 和 connect 能自动触发对应函数,全靠元对象系统在背后悄悄传递消息。 - Q_OBJECT 是开关:类声明中包含 Q_OBJECT 宏时,Qt 就会自动生成额外代码(通过 moc),让这个类支持信号槽、动态属性等高级功能。

测试用例

cpp
// MyClass.h
#include <QObject>

class MyClass : public QObject {
    Q_OBJECT  // 必须添加此宏才能使用信号槽
public slots:
    void mySlot();
signals:
    void mySignal();
};

// 结果:**moc** 会生成 **moc_MyClass.cpp**,包含信号槽的实现和元对象信息。

uic(User Interface Compiler,用户界面编译器)

用于将 Qt Designer 设计的 .ui 文件(XML 格式的界面描述)转换为对应的 C++ 头文件(**ui_*.h**),包含界面控件的布局、对象名、信号槽连接等代码。

测试用例

bash
# 命令行调用 uic
uic mainwindow.ui -o ui_mainwindow.h
cpp
// 在代码中通过 **Ui::MainWindow** 类加载界面:
#include "ui_mainwindow.h"

class MainWindow : public QMainWindow {
    Q_OBJECT
private:
    Ui::MainWindow *ui;  // 指向生成的界面类
};

rcc(Resource Compiler,资源编译器)

用于将 .qrc 文件(Qt 资源集合,如图片、QML 文件等)编译为二进制格式(**qrc_*.cpp**),并嵌入到最终的可执行文件中。

测试用例

xml
<!-- resources.qrc 资源文件 -->
<RCC>
		<qresource prefix="/images">
				<file>icon.png</file>
		</qresource>
</RCC>
bash
# 命令行调用编译
rcc resources.qrc -o qrc_resources.cpp

# 编译后在代码中通过 **:/images/icon.png** 路径访问资源

二进制兼容

二进制兼容(Binary Compatibility)指不同编译产物(.obj/.lib/.dll)能够正确链接运行的硬性规则。而 Qt 的二进制兼容规则就像瑞士钟表——必须所有齿轮严丝合缝才能准确走时。

例如

MSVC2019 x64编译的Qt库 ↔ MSVC2019 x64编译的程序 匹配

MSVC2022 x64编译的程序 ↔ MSVC2019 x64的Qt库 不匹配

若想要程序成功编译,需要整条工具链上的所有要素均需匹配才行,工具链则指以下三要素:

要素定义示例匹配值不匹配后果
编译器版本生成二进制文件的工具版本MSVC 2019(v142)LNK2038运行时库冲突
架构CPU指令集宽度x86_64(64位)LNK1112模块机器类型冲突
运行时库动态/静态链接运行时MD(动态链接DLL)LNK4098运行时库不匹配

Qt 安装路径中的编码信息

C:\Qt\6.5.3\
└── msvc2019_64      # 编译器版本+架构: Visual Studio版本必须用VS2019(v142工具链);
		|									                  CMAKE_GENERATOR_PLATFORM必须用x64架构
		|
    └── bin/Qt6Core.dll  # 实际二进制文件

兼容验证工具与方法

powershell
# Windows查看Qt库的DLL编译信息
dumpbin /HEADERS C:\Qt\6.5.3\msvc2019_64\bin\Qt6Core.dll | findstr "machine"

# 输出示例:
8664 machine (x64)             # 架构标识
19.00 linker version           # VS2019的链接器版本
cpp
// 查看项目的编译设置
message(STATUS "MSVC工具集: ${CMAKE_VS_PLATFORM_TOOLSET}")
message(STATUS "目标架构: ${CMAKE_SYSTEM_PROCESSOR}")
cpp
// 运行时诊断:在代码中检查运行时库
#ifdef _MTqDebug() << "使用静态运行时(MT)";
#elseqDebug() << "使用动态运行时(MD)";
#endif

运行时部署

运行时部署(Runtime Deployment)是指将程序运行所需的依赖文件与可执行文件一起打包分发的过程。Qt 默认采用动态链接方式,编译生成的 .exe 文件本身不包含 Qt 功能代码,而是运行时从程序的依赖文件(动态链接库(DLL)、平台插件、资源系统)中加载对应功能。

windeployqt 是 Qt 官方提供的 Windows 平台部署工具,专门用于自动收集 Qt 程序运行所需的依赖文件。它解决了手动收集依赖的痛点,故对于 Qt 程序来说,部署的关键简化为要确保:

  • 所有必需的 Qt 动态库(.dll)存在
  • 平台插件(如platformsqwindows.dll)可访问
  • 资源文件(如图片/QSS)路径正确

关键词说明

术语定义示例路径(Windows)
动态链接库(DLL)Qt功能的实现文件,运行时加载C:\Qt\6.5.3\msvc2019_64\bin\Qt6Core.dll
平台插件处理不同操作系统的底层交互plugins\platforms\qwindows.dll
资源系统将图片/翻译文件等编译进程序的机制:/images/logo.png
windeployqtQt官方提供的部署工具,自动收集依赖windeployqt.exe --dir deploy myapp.exe

windeployqt 工作原理图解

deepseek_mermaid_20250818_3ce4ce.png
deepseek_mermaid_20250818_3ce4ce.png

自动部署示例

powershell
# 基本部署(需在Qt命令行中执行)
windeployqt --dir ./deploy ./build/myapp.exe

# 包含QML文件的部署
windeployqt --qmldir ./qml --dir ./deploy myapp.exe

命令行部署参数详解

参数作用示例
--dir <目录>指定输出目录--dir ./deploy
--qmldir <路径>指定QML文件所在目录(用于QML程序)--qmldir ./src/qml
--release部署Release版本的依赖默认自动检测
--no-compiler-runtime不拷贝编译器运行时(如MSVCRT)需单独安装VC Redistributable

项目构建 & 部署关系图解

deepseek_mermaid_20250818_161f13.png
deepseek_mermaid_20250818_161f13.png

重点注意事项 - 匹配原则

架构匹配

  • x64(64位):CPU支持64位指令集,内存寻址超过4GB
  • x86(32位):传统32位模式,最大支持4GB内存

匹配原则

Qt安装版本编译器命令行典型错误表现
msvc2019_64x64 Native ToolsLNK1112机器类型冲突
mingw73_32MinGW 32-bit内存访问越界

检查方法

powershell
# 查看exe的架构
dumpbin /HEADERS myapp.exe | findstr "machine"

# 输出示例:
8664 machine (x64)  # 64位程序
014C machine (x86)  # 32位程序

典型错误(LNK1112):用 32 位编译器链接 64 位 Qt 库

error LNK1112: 模块计算机类型“x64”与目标计算机类型“x86”冲突

编译器版本匹配

MSVC版本对照表

Qt目录名VS版本工具集版本_MSC_VER
msvc2017_64VS 2017v1411910-1916
msvc2019_64VS 2019v1421920-1929
msvc2022_64VS 2022v1431930+

必须匹配的项目

  • Qt二进制包名(如msvc2019_64
  • Visual Studio安装版本
  • CMake生成器工具集

强制检查方法

cpp
# 在CMakeLists.txt中添加验证
if(NOT "${CMAKE_VS_PLATFORM_TOOLSET}" STREQUAL "v142")
    message(FATAL_ERROR "必须使用VS2019(v142)工具链")
endif()

典型错误(LNK2038):Qt 用 VS2019 编译,项目用 VS2022 编译

error LNK2038: 检测到“_MSC_VER”不匹配: 值“1929”不匹配值“1930”

生成器匹配

生成器类型对比

生成器特点适用场景
Ninja极速、低开销命令行开发
Visual Studio集成IDE解决方案Windows图形化调试
Unix Makefiles传统Unix风格Linux服务器环境

正确配对示例

powershell
# Ninja生成器(需提前安装ninja.exe)
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..

# VS生成器(自动创建.sln)
cmake -G "Visual Studio 17 2022" -A x64 ..

典型错误(CMake Error):未安装 ninja 却指定 -G Ninja

CMake Error: Could not create named generator Ninja

构建类型

核心区别:单配置 vs 多配置

类型生成器示例特点设置方式
单配置Ninja/Makefiles一次构建只能一种类型-DCMAKE_BUILD_TYPE=Release
多配置Visual Studio/XcodeIDE 中可切换 Debug/Release--config Release

典型错误(编译失败):Ninja下未指定构建类型

cpp
# 错误:Ninja下未指定构建类型
cmake -G Ninja ..  # 生成未优化的调试版
# 正确:
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..

额外补充内容 - Check List

  1. 新建项目时验证

``powershell # 查看当前工具链 cl.exe # 看是否识别编译器 cmake --help | findstr "Generator" # 查看可用生成器 # 检查Qt版本匹配 qmake -v ``

  1. CMake预设推荐

``json // CMakePresets.json { "configurePresets": [ { "name": "msvc2019-x64", "generator": "Ninja", "architecture": "x64", "toolset": "v142", "variables": { "CMAKE_BUILD_TYPE": "Release", "Qt6_DIR": "C:/Qt/6.5.3/msvc2019_64/lib/cmake/Qt6" } } ] } ``

  1. 问题诊断流程图
deepseek_mermaid_20250818_221410.png
deepseek_mermaid_20250818_221410.png