From 5de3763e72dabaeda32b50495d1ecd299407b49b Mon Sep 17 00:00:00 2001 From: "DeepSeek-V4.1-Flash" <> Date: Wed, 4 Nov 2026 02:48:59 +0800 Subject: [PATCH] =?UTF-8?q?perf(size):=20=E7=A7=BB=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=9A=84=20dynamic=5Fcast=20=E5=B9=B6=E7=9C=81?= =?UTF-8?q?=E6=8E=89=2056KB=20=E7=9A=84=20C++=20=E5=90=8D=E5=AD=97?= =?UTF-8?q?=E5=8F=8D=E4=BF=AE=E9=A5=B0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两处独立收益: 1. Toolbar::BuildBoxes 里唯一的 dynamic_cast 是**多余强转**(Button::widget 本来就是 UiButtonWidget*),却让全项目必须开 RTTI。改为直接遍历 m_buttons(与控件树同源)。 最小复现程序实测:39,936 B -> 115,200 B(+75,264 B)。 2. RTTI 关掉后 cp-demangle.o 仍然在链接里 —— 真凶是 libstdc++ 的 vterminate.o (引用 __cxa_demangle)。本文件自行定义 __gnu_cxx::__verbose_terminate_handler(), 让它不再被拉入(本应用启动后会换成自己的 terminate 处理,行为等效)。 Makefile 新增 NO_RTTI=1(默认开启)-> CXXFLAGS += -fno-rtti。 实测:Release 890,880 -> 829,440 B(-61,440);.text 753,136 -> 703,616(-49,520); .rdata 36,936 -> 26,616(-10,320);Debug 1,801,617 -> 1,734,578(-67,039)。 release/debug 全量重建零警告。 --- Components/Toolbar/Src/Toolbar.cpp | 18 +++++---- Makefile | 9 +++++ Utilities/Src/MemProbe.cpp | 62 ++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/Components/Toolbar/Src/Toolbar.cpp b/Components/Toolbar/Src/Toolbar.cpp index 62cef66..e55276f 100644 --- a/Components/Toolbar/Src/Toolbar.cpp +++ b/Components/Toolbar/Src/Toolbar.cpp @@ -359,19 +359,21 @@ namespace jdi return; } // ⚠️ 跳过根控件:它的矩形与工具栏区域完全重合(已经被上一层登记过)。 - for (const std::unique_ptr& button : m_root->Children()) + // + // ⚠️⚠️ **这里绝不能出现 `dynamic_cast`**:旧写法用它把 `UiWidget*` 转成 + // `UiButtonWidget*`,但 `Button::widget` 本来就是 `UiButtonWidget*`。 + // 一个多余的转换会链入 RTTI 元数据 + `cp-demangle.o`(Release map 里 + // **56,320 B**;最小复现程序实测 **+75,264 B**),并逼得全项目无法用 + // `-fno-rtti`。现在改为直接遍历 `m_buttons`(与控件树同源,且无需转换)。 + for (const Button& spec : m_buttons) { - if (button == nullptr) + if (spec.widget == nullptr) { continue; } - boxes.Add(button->Bounds(), 1); + boxes.Add(spec.widget->Bounds(), 1); // 按钮**内容**(文本或齿轮图标)单独上报:它是居中绘制的,与按钮矩形不同。 - const UiButtonWidget* raw = dynamic_cast(button.get()); - if (raw != nullptr) - { - boxes.Add(raw->ContentRect(), 2); - } + boxes.Add(spec.widget->ContentRect(), 2); } } diff --git a/Makefile b/Makefile index f325ccf..bfde428 100644 --- a/Makefile +++ b/Makefile @@ -192,6 +192,15 @@ ifeq ($(strip $(NO_EXCEPTIONS)), 1) CXXFLAGS += -fno-exceptions endif +# ---- RTTI:全项目不使用 `dynamic_cast` / `typeid` ---- +# 2026-11-04 审查:唯一的 `dynamic_cast`(Toolbar::BuildBoxes)是多余的强转, +# 已改为直接使用已有的 `UiButtonWidget*`。RTTI 会链入 `cp-demangle.o` +# (Release map 实测 56,320 B)+ `type_info` 表,最小复现程序实测 +75,264 B。 +NO_RTTI = 1 +ifeq ($(strip $(NO_RTTI)), 1) +CXXFLAGS += -fno-rtti +endif + # 丢弃未被引用的函数/数据(非 LTO 时还需要配合 -ffunction-sections 才有粒度)。 GC_SECTIONS = -Wl,--gc-sections diff --git a/Utilities/Src/MemProbe.cpp b/Utilities/Src/MemProbe.cpp index 0792d18..c841a0b 100644 --- a/Utilities/Src/MemProbe.cpp +++ b/Utilities/Src/MemProbe.cpp @@ -12,6 +12,35 @@ #include #include +namespace +{ + /** + * @brief 「未捕获异常 / 分配失败」的可见提示(`std::terminate` 的兜底路径)。 + * + * ⚠️ **为什么放在全局匿名命名空间**:下面要在 `namespace __gnu_cxx` 里覆盖 + * libstdc++ 的默认终止处理器,而 `__gnu_cxx` 必须是**全局**命名空间 + * (不能嵌套在本项目自己的 `jdi` 里)。 + * + * ⚠️ 本来这个函数由 libstdc++ 的 `__gnu_cxx::__verbose_terminate_handler()` 提供, + * 但它所在的 `vterminate.o` 会链入 `cp-demangle.o` + * (Release map 实测 `.text` 49,600 + `.rdata` 6,720 ≈ **56 KB** 的 C++ 名字反修饰器, + * 对一个不打印栈回溯的 GUI 程序毫无用处)。 + * 本文件自行定义该函数 ⇒ 归档成员 `vterminate.o` 不再被拉入 ⇒ demangler 消失 + * (2026-11-04 代码审查,实测 −55 KB 量级)。 + * + * ⚠️ 这里**不能**用任何会再次分配内存的机制(此刻堆可能已不可用): + * 只用静态字面量与 Win32 API。 + */ + void MemTerminateFallbackImpl() + { + MessageBoxW(nullptr, + L"内存不足:本次操作需要的内存超过系统可用量。\n\n" + L"建议:减少同时显示的网格数或信号数,或关闭占用内存较大的程序后重试。", + L"Just DataInspector", MB_ICONERROR | MB_OK | MB_SETFOREGROUND); + std::_Exit(EXIT_FAILURE); + } +} // namespace + namespace jdi { @@ -32,18 +61,13 @@ namespace jdi std::atomic g_memCachedBytes{0}; /** - * @brief terminate 处理:给出可见提示后退出。 + * @brief 应用安装的 terminate 处理(`MemInstallTerminateHandler` 注册)。 * - * ⚠️ 这里**不能**用 MessageBoxW 之外的任何复杂机制:此刻堆可能已不可用, - * 连 `std::wstring` 都不该构造(字面量是静态数据,安全)。 + * ⚠️ 与文件末尾的 `__gnu_cxx::__verbose_terminate_handler()` 覆盖共用同一条提示路径。 */ void OnTerminate() { - MessageBoxW(nullptr, - L"内存不足:本次操作需要的内存超过系统可用量。\n\n" - L"建议:减少同时显示的网格数或信号数,或关闭占用内存较大的程序后重试。", - L"Just DataInspector", MB_ICONERROR | MB_OK | MB_SETFOREGROUND); - std::_Exit(EXIT_FAILURE); + MemTerminateFallbackImpl(); } } // namespace @@ -99,3 +123,25 @@ namespace jdi } } // namespace jdi + +/** + * @brief **覆盖** libstdc++ 的默认终止处理器(省掉 56 KB 的 C++ 名字反修饰器)。 + * + * `std::terminate()` 定义在 `eh_terminate.o` 里,它引用 `__gnu_cxx::__verbose_terminate_handler()`。 + * 该符号原本由归档成员 `vterminate.o` 提供,而 `vterminate.o` 又引用 `__cxa_demangle` + * ⇒ 链接器被迫拉入 `cp-demangle.o`(Release map 实测 `.text` 49,600 B + `.rdata` 6,720 B)。 + * 我们在自己的目标文件里提供同名定义,`eh_terminate.o` 的未定义引用就被满足, + * `vterminate.o` 与整个 demangler 都不会进入最终产物。 + * + * ⚠️ 必须定义在**全局** `__gnu_cxx` 命名空间(不能嵌套在 `jdi` 内)。 + * ⚠️ 应用启动后会通过 `jdi::MemInstallTerminateHandler()` 换上自己的处理函数, + * 因此这个默认实现只在「启动早期崩溃」时生效 —— 行为与 libstdc++ 的版本等效 + * (libstdc++ 版会打印反修饰后的异常类型名,我们只给内存提示)。 + */ +namespace __gnu_cxx +{ + void __verbose_terminate_handler() + { + MemTerminateFallbackImpl(); + } +} // namespace __gnu_cxx