Wiki
1655 words
8 minutes
虚幻中的调试
Updated 2025-04-17

30秒概要#

  • ensure:开发阶段调试工具,条件失败时记录日志,允许程序继续运行
  • check:运行时断言工具,条件失败时立即崩溃,用于关键逻辑保护
  • checkf:带格式化信息的 check,提供更详细的错误信息
  • 选择依据:错误严重程度、构建类型和错误信息需求

1. 核心概念解析#

【重点】ensure 宏#

  • 定义:开发/测试构建专用的调试工具
  • 作用:验证”不太可能发生但仍需检查”的错误情况
  • 特点
    • 仅在开发/测试构建中生效
    • Shipping 构建中自动移除
    • 条件失败时记录日志,可选择触发断点
    • 同一条件仅记录一次日志(防日志泛滥)
  • 【注意】使用限制
    • 不能用于关键系统验证
    • 不适用于需要立即终止的情况
    • 避免在性能关键路径上过度使用

【重点】check 宏#

  • 定义:全构建类型生效的断言工具
  • 作用:确保代码逻辑的绝对正确性
  • 特点
    • 所有构建类型(包括 Shipping)都生效
    • 条件失败时立即崩溃
    • 每次失败都会记录并终止程序
  • 【注意】使用限制
    • 仅用于真正关键的错误检查
    • 避免在频繁调用的函数中使用
    • 注意性能影响

【重点】checkf 宏#

  • 定义:带格式化字符串的 check 变体
  • 作用:在关键逻辑保护的同时提供详细的错误信息
  • 特点
    • 与 check 相同的严格性
    • 支持格式化字符串输出
    • 提供更丰富的调试信息
    • 所有构建类型都生效
  • 【注意】使用限制
    • 格式化字符串有性能开销
    • 避免在循环中使用
    • 注意字符串长度限制

2. 对比分析#

特性ensurecheckcheckf
生效范围仅开发/测试构建所有构建所有构建
失败行为记录日志,可继续运行立即崩溃立即崩溃
日志频率每个条件仅一次每次失败都记录每次失败都记录
使用场景非致命错误检测关键逻辑保护需要详细错误信息的关键逻辑保护
性能影响Shipping 无开销所有构建都有开销所有构建都有开销
严格程度较宽松非常严格非常严格
信息丰富度基础信息基础信息可自定义详细信息
适用阶段开发/测试全生命周期全生命周期

3. 【技巧】最佳实践指南#

ensure 适用场景#

  • 检查指针是否为 nullptr
  • 验证函数参数有效性
  • 检测逻辑上不应发生的状态
  • 开发阶段的错误追踪
  • 资源加载状态检查
  • 游戏逻辑验证

check 适用场景#

  • 核心系统初始化验证
  • 关键资源存在性检查
  • 防止致命错误发生
  • 发布版本的关键逻辑保护
  • 引擎核心功能验证
  • 内存安全保护

checkf 适用场景#

  • 需要详细错误信息的核心系统验证
  • 复杂条件判断的错误报告
  • 需要包含变量值的错误信息
  • 多条件组合的错误诊断
  • 资源加载状态报告
  • 性能关键点监控

4. 【注意】使用建议#

  1. 根据错误严重程度选择工具
  2. 避免在性能关键路径上过度使用 check/checkf
  3. 合理使用 ensure 进行开发阶段调试
  4. 注意 Shipping 构建中的行为差异
  5. 考虑错误信息的详细程度需求
  6. 注意格式化字符串的性能开销

5. 与 Unity 调试工具对比#

功能Unreal (ensure/check/checkf)Unity
开发调试ensureDebug.Assert
运行时断言checkDebug.Assert
格式化断言checkfDebug.AssertFormat
日志记录自动记录堆栈信息需要手动添加
构建影响ensure 在发布版移除所有版本都生效
性能开销可控制固定开销

6. 【例子】代码示例#

// ensure 示例
void ProcessData(Data* DataPtr)
{
ensure(DataPtr != nullptr); // 开发阶段检查
ensure(DataPtr->IsValid()); // 数据有效性检查
// 处理数据...
}
// check 示例
void InitializeGame()
{
check(GameWorld != nullptr); // 关键逻辑保护
check(PlayerController != nullptr); // 核心组件检查
// 初始化游戏...
}
// checkf 示例
void LoadLevel(const FString& LevelName)
{
// 检查关卡是否存在,并提供详细的错误信息
checkf(LevelExists(LevelName),
TEXT("关卡 %s 不存在,请检查关卡名称是否正确"),
*LevelName);
// 检查资源加载状态
checkf(ResourcesLoaded,
TEXT("资源未加载完成,当前加载进度:%d%%,已加载资源数:%d"),
LoadingProgress,
LoadedResourceCount);
}
// 复杂条件检查示例
void UpdateGameState()
{
// 使用 checkf 进行多条件检查
checkf(GameState != nullptr && GameState->IsValid(),
TEXT("游戏状态无效:State=%p, Valid=%d"),
GameState,
GameState ? GameState->IsValid() : 0);
}

7. 性能优化建议#

  1. 在性能关键路径上优先使用 ensure
  2. 合理控制 check/checkf 的使用范围
  3. 利用 Shipping 构建自动移除 ensure 的特性
  4. 注意日志记录对性能的影响
  5. 避免在循环中使用 checkf
  6. 考虑使用条件编译控制断言行为
  7. 使用宏定义控制断言级别

8. 常见问题与解决方案#

  1. 问题:日志过多影响性能

    • 解决:使用 ensure 的”一次性”特性
    • 解决:调整日志级别
    • 解决:使用条件编译控制日志输出
  2. 问题:发布版本崩溃

    • 解决:检查 check/checkf 的使用是否合理
    • 解决:使用 ensure 替代非关键检查
    • 解决:添加错误恢复机制
  3. 问题:调试信息不足

    • 解决:使用 checkf 提供更详细的错误信息
    • 解决:添加上下文信息
    • 解决:使用自定义断言宏
  4. 问题:格式化字符串性能开销

    • 解决:在性能关键路径上避免使用 checkf
    • 解决:使用静态字符串
    • 解决:优化格式化参数
  5. 问题:断言影响游戏体验

    • 解决:合理设置断言级别
    • 解决:添加用户友好的错误提示
    • 解决:实现优雅的错误恢复机制

9. 【技巧】进阶使用建议#

  1. 自定义断言宏

    • 根据项目需求定义特定断言
    • 添加项目特定的错误处理
    • 实现自定义日志记录
  2. 条件编译控制

    • 使用预处理器控制断言行为
    • 实现不同构建配置的断言策略
    • 优化发布版本的性能
  3. 错误恢复机制

    • 实现优雅的错误处理
    • 添加自动恢复功能
    • 提供用户友好的错误提示
  4. 性能监控

    • 使用断言进行性能检查
    • 实现性能关键点监控
    • 优化资源使用
虚幻中的调试
https://fuwari.vercel.app/wiki/unreal/gas/related/虚幻中的调试/
Author
Qingswe
Published at
2025-04-17
License
CC BY-NC-SA 4.0