news 2026/9/19 1:39:15

Qt 5.14.2静态交叉编译aarch64嵌入式Linux完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt 5.14.2静态交叉编译aarch64嵌入式Linux完整指南

干过嵌入式Linux的人应该都体会过这种尴尬:在一台x86电脑上写好的Qt程序,拿到arm板子上要么缺库、要么版本对不上,折腾半天最后发现目标板上连个libQt5Widgets.so都没有。这个时候,静态交叉编译几乎是唯一体面的解法。

这篇东西我整理自一次完整项目实践:主机Ubuntu 20.04 x86_64,目标平台aarch64,Qt版本5.14.2,全程静态链接。文章会从编译工具链准备、sysroot制作、configure参数逐项拆解、静态插件集成,到最后的部署验证和踩坑记录,全部给你理清楚。适合刚接触交叉编译的Qt开发者,也适合那些被动态库依赖折磨过、想一次性搞明白静态编译思路的人。

1. 为什么非要折腾静态交叉编译

1.1 静态 vs 动态,一张表看懂差别

很多人在刚开始接触交叉编译的时候,脑子里默认就选了动态编译。原因很简单:开发机上装好Qt,交叉工具链一连,生成一个动态链接的arm版可执行文件,丢到板子上跑。听起来挺顺,但落地时麻烦一件接一件。

对比维度静态编译动态编译
可执行文件体积大,通常几十MB起很小,几MB常见
部署方式单个文件拷过去即跑需要把一堆.so一起拷过去
对环境依赖只依赖内核ABI依赖目标板所有动态库版本
调试便利性改动后需全量重编只编译业务代码,快
目标板适配性只要架构对基本能跑板子库不全会直接启动失败
体积优化空间strip后能压缩一部分天然小,共享库不占程序本身

实际项目的痛点往往是:产品板子已经量产,rootfs定死了,不能随便加qt库;或者客户现场没有权限动/usr/lib;又或者就是单纯嫌动态库部署麻烦,拷少了报“无法加载共享库”,拷多了又怕覆盖掉别的组件。

静态编译最直接的价值就是把上面这些问题一锅端。可执行文件里已经包含Qt核心库、UI插件、字体、图片格式支持,拷贝过去,chmod +x,直接运行。内核是aarch64的就行,不关心板子上装了什么发行版、有没有Qt。

1.2 交叉编译的核心思路,一句话讲透

交叉编译本质上是:在一种架构(x86_64)的主机上,构建出另一种架构(aarch64)上运行的二进制。实现这个目标需要三个东西协同工作:

  • 交叉工具链:编译器和链接器,比如 aarch64-linux-gnu-gcc/g++。它生成的机器码是ARM 64位的。
  • 目标系统的头文件和库文件:也就是sysroot。交叉编译器在编译和链接时,需要找到目标板上那些库的头文件和.a/.so文件,但绝不能去找主机上的/usr/include或/usr/lib,否则就会混入x86的东西。
  • 构建系统的交叉配置:Qt用的是qmake + configure,你得明确告诉它“目标平台是啥、工具链前缀是啥、系统根目录在哪”。

特别是第三点,Qt自己有一套mkspecs机制来适配不同平台。Qt 5.14.2的源码里已经自带了 linux-aarch64-gnu-g++ 这个mkspec,里面写明了用 aarch64-linux-gnu-gcc 来编译,这给我们的工作省了不少力气。

2. 环境与工具链准备

2.1 宿主机要求与基础依赖

我先说下我用的这台机器配置:Ubuntu 20.04.4 LTS,x86_64,内存16GB,磁盘剩了至少50GB可用空间。Qt源码编译比较吃磁盘和CPU,完整编译(带Qt WebEngine)能轻松超过30GB,但我们后面会skip掉webengine这类大模块,普通配置下10~15GB足够。

宿主机要装的基础依赖:

sudo apt update sudo apt install -y build-essential perl python3 git rsync \ libxcb1-dev libx11-dev libxkbcommon-dev \ libgl1-mesa-dev libfontconfig1-dev \ gperf bison flex gdb

这些包是Qt在配置和编译时可能用到的。即使做静态交叉编译,主机上也需要一些基础开发工具,比如perl(Qt的configure脚本依赖它跑一些配置逻辑)、python3(部分Qt模块的构建工具)和make等。

提示:不用在宿主上装Qt开发包。交叉编译时qtbase源码会自动用内部的同步qt模块机制处理头文件和库的生成,宿主的Qt版本反而可能干扰环境,尤其是qxcb插件路径,容易出版本不一致的妖蛾子。

2.2 交叉编译工具链选型

aarch64的工具链选择比较丰富,Linaro GCC、ARM GNU Toolchain、各芯片厂商的SDK编译器都行。我这次选用的是Ubuntu官方源里的gcc-aarch64-linux-gnu,版本是9.4.0(Ubuntu 20.04仓库默认),目标平台glibc版本也比较通用,跟常见板子兼容性好。

sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu # 验证 aarch64-linux-gnu-gcc --version

安装完工具链后,务必检查默认前缀是否正确。Ubuntu源里的工具前缀是aarch64-linux-gnu-(gcc、g++、ld、ar、strip这些工具都有),这跟Qt内置的 linux-aarch64-gnu-g++ 这个mkspec的默认预期是一致的。如果你的工具链前缀不同,比如arm-linux-gnueabihf-或者厂商工具链,就需要额外改mkspec了。

检查是否所有配套工具都在:

which aarch64-linux-gnu-gcc aarch64-linux-gnu-g++ aarch64-linux-gnu-ld \ aarch64-linux-gnu-ar aarch64-linux-gnu-strip aarch64-linux-gnu-make

如果返回的不是全路径,说明某个工具没装全,需要处理,否则编译到链接阶段会报无法找到某个binutils工具的错误。

2.3 准备目标板sysroot,这是很多人忽略的坑

sysroot就是目标板的根文件系统“精简版”,它包含目标板上所有的动态库和头文件。Qt在静态编译时,虽然最终可执行文件不再动态依赖Qt库,但libc、libstdc++、libm等系统库的头文件和符号还是要有的,链接阶段必须能查到。

sysroot的来源我推荐两种方式:

方式一:直接从开发板拷贝(最可靠)

如果你的板子已经跑起来,而且系统文件齐全,直接打包一份/下面需要的目录:

# 在板子上执行 mkdir -p /tmp/rootfs cd / tar --exclude=/proc --exclude=/sys --exclude=/dev --exclude=/tmp --exclude=/run \ -czf /tmp/rootfs.tar.gz . # 拷贝到宿主机解压

这种方法的好处是:目标板和编译环境完全一致性,libc版本、库文件齐全,不会出现那个经典问题——“编出来的程序在板子上突然报GLIBC版本过高”。

方式二:用厂商SDK提供的sysroot

大部分嵌入式厂商(NXP、Rockchip、Allwinner等)的SDK包都会自带独立sysroot,路径通常在 SDK/out/sysroot 或类似目录。这种sysroot往往是裁剪过的,已经集成了目标平台的库和开发头文件,用起来最省心。

我这次是基于实际板卡从板上提取的sysroot,路径统一放在/opt/rootfs。在这个目录下应该至少看到libusr/includeusr/lib这些标准结构。

# 确认sysroot目录结构 ls /opt/rootfs # bin dev etc lib proc sbin sys usr var ls /opt/rootfs/usr/include # 应该能看到 stdio.h 等C标准库头文件

如果sysroot里连basic头文件都没有,可以先用debootstrap做一套纯净的aarch64 rootfs来应急,但要注意跟目标板glibc版本的一致性。实践中真正能和板子匹配上的,还是板子上原封不动带出来的sysroot。

注意:sysroot里一定要有基础的.a静态库吗?不一定。静态编译Qt时,Qt自身的库会被编译为.a,但libc和libstdc++通常不会被全静态链接进来,除非你显式用 -static-libgcc -static-libstdc++。实际上,纯静态链接整个系统库在嵌入式上并不常见,也不推荐,很多板卡的libc和libstdc++动态库版本都比较稳定,动态链系统库、静态链Qt库反而最实用。

3. Qt源码编译全流程

3.1 源码获取与版本取舍

Qt 5.14.2是2020年发布的版本,但到现在依然有大量嵌入式项目在使用。相比后来的5.15和6.x,5.14.2的编译配置逻辑和模块依赖更简单,而且很多板卡厂商的BSP里默认就是这个版本。如果项目里已经依赖了5.14.2的功能特性,不建议升级到5.15再折腾一遍,除非你愿意处理版本更迭引入的接口变化。

源码包要到Qt官网下载,或者用清华源等镜像站加速。完整源码包是 qt-everywhere-src-5.14.2.tar.xz,面面俱到地包含了qtbase、qtdeclarative、qtserialport、qtcharts等常用模块。

wget https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.14/5.14.2/qt-everywhere-src-5.14.2.tar.xz # 或者本地已有安装包就直接解压 tar -xf qt-everywhere-src-5.14.2.tar.xz cd qt-everywhere-src-5.14.2

一个大前提:把源码放到一个不含中文和空格的路径下,编译Qt对路径很敏感,这个是老生常谈了。我用的是 /home/user/work/qt-everywhere-src-5.14.2。

3.2 configure核心参数逐项拆解

静态编译最关键的一步就是configure。参数错了,整个编译链都是白搭。下面是这次使用的完整configure配置,我来逐项解释为什么这么写:

mkdir -p /opt/qt5.14.2-aarch64-static cd /home/user/work/qt-everywhere-src-5.14.2 ./configure \ -static \ -release \ -opensource \ -confirm-license \ -prefix /opt/qt5.14.2-aarch64-static \ -xplatform linux-aarch64-gnu-g++ \ -sysroot /opt/rootfs \ -no-opengl \ -linuxfb \ -no-xcb \ -nomake examples \ -nomake tests \ -skip qtwebengine \ -skip qtwebview \ -skip qtandroidextras \ -no-feature-sql \ -no-feature-xml \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -no-gif \ -no-icu \ -no-dbus

参数对应含义:

参数含义坑点说明
-static静态编译Qt库必须放在显眼位置,忘记这个等于白干
-release去掉调试信息交叉编译别用debug,链接体积和速度都失控
-opensource -confirm-license用开源版许可商业闭源项目需要自己评估license
-prefix安装目录编译完的Qt会放到这个目录,后面编译业务程序时要用到
-xplatform交叉目标平台linux-aarch64-gnu-g++ 是Qt内置的aarch64设备mkspec
-sysroot目标根文件系统交叉编译器找头文件和库的根路径
-no-opengl禁用OpenGL如果板子没有GPU,Opengl一堆依赖麻烦事,先禁用最稳
-linuxfb启用Linux Framebuffer插件没有X11的情况下,Qt通过/dev/fb0直接画屏幕
-nomake examples/tests不编译示例和测试省时间和磁盘,纯跑生产环境没必要编
-skip qtwebengine跳过webengine模块WebEngine体积巨大且依赖系统库复杂,大部分嵌入式用不上
-no-feature-sql去掉数据库模块不用数据库就去掉,静态库体积能小不少
-no-icu去掉ICUICU对中文本地化有一定作用,但体积非常大,嵌入式我默认关掉
-qt-zlib -qt-libpng -qt-libjpeg用Qt自带的三方库避免sysroot里缺静态库导致链接失败
-no-dbus去掉D-Bus很多嵌入式板子没DBus,有需要的话可以保留

这里特别讲一下-no-xcb。Qt应用要运行在X11协议下就需要xcb插件,但它依赖X11的一组动态库。如果你目标板上没有运行X服务器(嵌入式大多都是直接FrameBuffer或EGLFS),就别编译xcb了,否则静态链接时会因为找不到libxcb静态库而报一大堆错。

还要强调-no-opengl的取舍。如果目标板有GPU并且Qt程序需要绘制动画、图表等GPU加速能力,你得拿到厂商的OpenGL ES库和头文件,在configure里打开-opengl es2并配置好EGL库路径。但这是另一套复杂度,第一次做交叉编译建议先用linuxfb + no-opengl跑通全流程,再考虑GPU支持。

提示:如果是常规aarch64 Linux板子,还可以考虑用-eglfs替代linuxfb,它接管EGL显示。但eglfs依赖具体的GPU厂商驱动,没有厂商库的时候宁可不用。

3.3 编译、安装与验证

configure配置完成后就开始编译,我用了8个并发任务跑:

make -j8

整个编译时间取决于机器性能,我第一次跑大概花了30分钟左右。遇到某个模块编译报错时,千万不要直接重跑整个make,先解决报错再make。常用做法是先看具体模块的错误信息:

make 2>&1 | tee build.log # 看到错误后定位具体模块 grep -r "error:" build.log

编译完成后安装到指定前缀:

make install

安装完成后,检查 /opt/qt5.14.2-aarch64-static 目录:

ls /opt/qt5.14.2-aarch64-static/lib # 应该能看到 libQt5Core.a libQt5Gui.a libQt5Widgets.a 等静态库

同时检查平台插件静态库:

ls /opt/qt5.14.2-aarch64-static/plugins/platforms/ # 至少要有 libqlinuxfb.a

如果看到这些.a静态库文件,说明你的Qt静态库已经成功构建。接下来就是验证它能不能真正给工程用。

4. 静态链接的程序怎么跑起来

4.1 QPA插件与静态插件机制

Qt有自己的一套插件机制,尤其是UI平台插件(QPA,Qt Platform Abstraction),它在Qt启动时负责创建窗口和渲染输出。动态编译时,Qt会从 plugins/platforms/ 目录里动态加载libqxcb.so或libqeglfs.so等。但静态编译时,动态加载路径不存在了,编译器必须在编译阶段就把需要的QPA插件链接进可执行文件。

Qt的解决办法是:

  • configure时,根据你启用的平台(linuxfb、eglfs等),生成对应的插件静态库,比如 libqlinuxfb.a
  • 链接时,需要在.pro文件里指定QTPLUGIN += qlinuxfb,qmake会把这个插件静态库链接进程序
  • 在main函数或某个源文件里,还要通过Q_IMPORT_PLUGIN宏把自己需要的插件导入,这样Qt启动时才能在内部插件链中枚举到它

实际操作中,如果漏掉了这两步,最常见报错是:

qt.qpa.plugin: Could not find the Qt platform plugin "linuxfb" in ""

即使你的静态可执行文件已经包含libqlinuxfb.a,但没做Q_IMPORT_PLUGIN,照样找不到插件。所以静态Qt工程的main函数长这样:

#include <QApplication> #include <QtPlugin> // 如果需要用linuxfb平台插件,导入这个宏 Q_IMPORT_PLUGIN(QLinuxFbIntegrationPlugin) int main(int argc, char *argv[]) { QApplication app(argc, argv); // ... return app.exec(); }

4.2 工程文件与链接参数配置

工程文件(.pro)需要把插件和额外链接库都声明好。下面是一个可用的最小工程配置:

QT += core gui widgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets # 静态链接时必须手动声明需要集成的插件 QTPLUGIN += qlinuxfb # 如果没有在main中写Q_IMPORT_PLUGIN,也可以考虑用这种方法导入 # 但qmake在静态编译时,下面的写法相对可靠 CONFIG += static TARGET = hello_qt TEMPLATE = app SOURCES += main.cpp

编译命令:

/opt/qt5.14.2-aarch64-static/bin/qmake hello_qt.pro make

make结束后,查看生成的可执行文件:

file hello_qt # hello_qt: ELF 64-bit LSB executable, ARM aarch64, dynamically linked (uses shared libs)

注意这里显示 dynamically linked,是因为glibc和libstdc++这些系统库还是动态链接的(我们前面特意没做全静态),但你已经看不到libQt5Core.so.something这种Qt库的依赖了。如果你确实想链接全部库(包括libc、libstdc++),可以给make加参数:

make LFLAGS="-static-libgcc -static-libstdc++"

或者在.pro里加:

QMAKE_LFLAGS += -static-libgcc -static-libstdc++

不要强行加-static链接系统所有库,嵌入式板卡的libc库往往跟宿主不同,而且glibc对静态链接有多次警告,出core问题排查会很痛苦。

4.3 字体、资源与体积控制

静态编译可执行文件虽然不依赖Qt动态库了,但目标板上的字体文件它不会自动读。因为Qt在编译时,是在宿主系统里扫描字体的,生成的是“宿主”字体路径。到了目标板上,那些路径大概率不存在,界面就会出现中文乱码或方块字。

我常用的解决方式:

  • 把用到的字体文件(比如 NotoSansCJK-Regular.ttc)加到资源文件.qrc里
  • 程序启动时,用QFontDatabase::addApplicationFont加载字体
  • 然后全局设置这个字体族

示例代码:

#include <QFontDatabase> #include <QFont> int fontId = QFontDatabase::addApplicationFont(":/fonts/NotoSansCJK-Regular.ttc"); QString family = QFontDatabase::applicationFontFamilies(fontId).at(0); QFont defaultFont(family, 12); QApplication::setFont(defaultFont);

除了字体,图片格式插件也要注意。如果程序里加载PNG/JPG等图片,静态编译时这些格式插件(如libqjpeg.a、libqgif.a)也需要通过Q_IMPORT_PLUGIN或QTPLUGIN导入。否则运行时QImageReader::supportedImageFormats()返回空,图片全都加载不出来。

体积控制方面,编译完的静态Qt可执行文件通常在20~40MB之间(如果加了字体资源还要往上走)。有几个常规手段可以减肥:

# 用交叉strip去符号表,能砍掉一半 aarch64-linux-gnu-strip hello_qt

还可以在.pro里加上编译优化参数:

QMAKE_CXXFLAGS_RELEASE += -Os QMAKE_CFLAGS_RELEASE += -Os

不过静态库体积基本由Qt模块决定,业务代码瘦身空间有限。如果产品对体积特别敏感,可以考虑用-no-feature-*精简模块特性,比如去掉DOM、动画、QML等,但这需要从configure阶段就开始规划。

5. 实战排错与经验沉淀

5.1 高频报错与修复方案

实际操作中难免踩坑,我把自己见过的最典型的报错整理出来,基本都是静态交叉编译环境下特有的问题。

报错信息原因分析修复方案
Could not find the Qt platform plugin "linuxfb"静态插件没导入在main中加Q_IMPORT_PLUGIN,并在.pro里加QTPLUGIN
cannot find -lGLOpenGL库缺失configure加 -no-opengl,或链接GL库
undefined reference to 'qt_plugin_query_metadata'静态插件链接顺序错误确保QTPLUGIN和Q_IMPORT_PLUGIN同时满足,且qmake重新生成Makefile
unknown module(s) in qt: serialportserialport模块没有编译在configure时不跳过qtserialport,并且make install确保模块装上了
cannot mix incompatible Qt library (5.15.3) with this library (5.15.2)环境里有多个Qt版本,qmake或LD_LIBRARY_PATH指向错了编译时用 /opt/qt5.14.2-aarch64-static/bin/qmake 绝对路径,清空LD_LIBRARY_PATH
GLIBC_2.xx not found宿主机工具链的glibc版本高于目标板换用目标板sysroot内的libc,或用更老的工具链
cannot find -lcsysroot里没有libc库检查sysroot完整性,尤其检查usr/lib/aarch64-linux-gnulib/aarch64-linux-gnu目录
error: unknown module(s) in qt: serialport(Windows下Qt Creator交叉编译配置问题)Windows宿主上的交叉配置不一致建议用Linux作为构建宿主机,Windows上的Qt交叉编译会引入更多环境变量混乱
error while building/deploying project (kit: desktop qt ...)Qt Creator的kit配置里用了宿主工具链或错误的编译器重新建kit,编译器指向aarch64工具链,qmake指向交叉编译的qmake

第一个报错是全静态编译最容易中招的。有个排查技巧:用ldd看之后,再使用strings hello_qt | grep linuxfb,如果没有任何输出,说明插件根本没链进二进制里,一定是Q_IMPORT_PLUGIN/QTPLUGIN配置缺失。

5.2 交叉编译环境的控制与整洁性

工程做到后期,项目一多就会遇到环境变量混乱。Linux下最常见的坑是PATH、LD_LIBRARY_PATH、PKG_CONFIG_PATH同时影响编译过程。

基于我的经验,交叉编译Qt时建议这样做:

  • /opt/qt5.14.2-aarch64-static/bin加到PATH中时,只对当前终端生效,不要写进~/.bashrc全局生效,避免影响宿主机其他Qt项目。
  • 编译业务程序时,优先使用qmake的绝对路径:/opt/qt5.14.2-aarch64-static/bin/qmake,避免系统自带qmake或另一个交叉版本qmake抢占。
  • 编译前unset LD_LIBRARY_PATH。静态编译程序运行不需要这个变量,但编译过程如果带着宿主Qt的LD_LIBRARY_PATH,qmake会扫描到不该有的库路径。

第二个建议特别重要,我遇到过两次项目之间串环境导致的怪问题,“cannot mix incompatible Qt library”这个报错就是典型的qmake指错版本带来的。qt.qpa.plugin报错也有可能是LD_LIBRARY_PATH里混入了host的Qt库,运行时动态加载出问题。

每次编译前,我习惯写一个环境准备脚本,比如 env-arm.sh:

#!/bin/bash export QT_ROOT=/opt/qt5.14.2-aarch64-static export PATH=$QT_ROOT/bin:$PATH export QMAKE=$QT_ROOT/bin/qmake export QTDIR=$QT_ROOT unset LD_LIBRARY_PATH

这样切换到aarch64项目时,source一下即可,结束时unset QT_ROOT QMAKE QTDIR就行。这种管理方式后来帮我省了大量排查环境问题的时间。

注意:不要把sysroot的/opt/rootfs/usr/bin加进PATH。sysroot里是目标板的二进制工具,架构是aarch64的,在x86宿主上直接运行会报Exec format error。交叉编译的工具链目录是宿主机上的,跟sysroot是两码事。

5.3 关于部署测试的一些建议

交叉编译的最终目标是目标板能跑,所以部署验证的环节不能省。我通常的流程是:

# 拷贝到板子 scp hello_qt root@板子IP:/root/ # 登录板子执行 ssh root@板子IP chmod +x /root/hello_qt /root/hello_qt -platform linuxfb

-platform linuxfb显式指定平台插件,可以避免Qt自动枚举平台时找不到合适插件的问题。如果你的板子有液晶屏,且Qt在启动时要显示窗口,那环境变量QT_QPA_FB_DRM=1或者QT_QPA_FB_DEVICE=/dev/fb0都要按需配置。之前我用linuxfb时,遇到过程序启动后黑屏,排查发现是fb0设备还没初始化好。让程序等待一下framebuffer设备初始化完成,或者确认/dev/fb0访问权限(一般加chmod 666 /dev/fb0即可)。

还有个小细节:程序里如果用了固定的界面尺寸,但板子的分辨率跟开发时不一样,界面显示就会错位。建议代码里读取QScreen::geometry()做自适应布局,或者设置QT_QPA_FB_DRM=0时用环境变量指定尺寸。

最后,静态交叉编译的Qt程序不一定非要拷贝到root目录跑,普通用户权限也可以运行,因为没有任何库需要写权限或额外安装。这算是一个额外好处:方便产品出厂环境的权限隔离。

我在实际做了两三次完整流程后,对静态交叉编译的体会是:它更像一次性的“配置成本”工程。只要你把工具链、sysroot、Qt编译配置固化下来,后续业务代码的编译、迭代、部署会非常省心。尤其是做嵌入式产品需要批量出机器的时候,拷一个可执行文件比往rootfs里补一套Qt运行时库,可靠性和效率都高得多。如果你手头正好有aarch64的板子,建议就照这个流程完整跑一遍,踩过的坑以后基本不会再踩第二次。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 1:34:43

ERP沙盘模拟实训报告怎么写?从数据分析到经营复盘全攻略

简介&#xff1a;一份ERP沙盘模拟实训报告&#xff0c;源自某高校商学院市场营销专业团队的真实企业运营对抗实战&#xff0c;完整记录了《企业运营沙盘模拟》课程的实验目的、经营过程、结果分析与个人角色总结&#xff0c;可帮助经管类专业学生、ERP沙盘参赛团队及实训报告写…

作者头像 李华
网站建设 2026/9/19 1:32:39

用Excel搭建数据字典:字段设计、公式配置与维护实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 1:32:12

电荷灵敏前置放大器噪声优化实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华