You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单元测试中检测硬实时函数是否调用malloc等特定函数?

可行的解决方案:实时函数内存分配检测

针对你的硬实时模块测试需求,这里有几个实用的方案,既能满足「动态开启/关闭检查」「优雅失败」的要求,也能在Linux下稳定运行,部分方案还能扩展到跨平台:


方案1:LD_PRELOAD 动态拦截(推荐Linux下使用)

你提到不想为所有关注的函数做LD_PRELOAD,但其实可以写一个轻量的拦截库,只在检测阶段触发检查,平时完全透传原函数调用。

实现思路:

  1. 编写一个共享库,拦截malloc、free、operator new等函数。
  2. 在库中维护一个全局的「检测状态标志」(比如bool g_checking = false),以及一个「是否触发违规调用」的标志(bool g_allocation_called = false)。
  3. 当处于检测状态时,任何对拦截函数的调用都会将g_allocation_called设为true,然后可以选择返回一个预分配的内存块(避免程序崩溃)或者直接记录后调用原函数。
  4. 在你的测试代码中,通过START_CHECKING_FUNCTION和STOP_CHECKING_FUNCTION宏来修改这个全局状态,并在检查结束后断言g_allocation_called为false。

示例代码片段:

拦截库(intercept_alloc.c):

#include <stdlib.h>
#include <stdbool.h>
#include <dlfcn.h>

bool g_checking = false;
bool g_allocation_triggered = false;

// 保存原函数指针
static void* (*original_malloc)(size_t) = NULL;
static void* (*original_operator_new)(size_t) = NULL;

__attribute__((constructor)) void init() {
    // 获取原函数地址
    original_malloc = dlsym(RTLD_NEXT, "malloc");
    original_operator_new = dlsym(RTLD_NEXT, "_Znwm"); // operator new(size_t)的 mangled 名
}

void* malloc(size_t size) {
    if (g_checking) {
        g_allocation_triggered = true;
        // 返回一个预分配的 dummy 块,避免程序崩溃
        static char dummy[1024];
        return dummy;
    }
    return original_malloc(size);
}

void* operator new(size_t size) {
    if (g_checking) {
        g_allocation_triggered = true;
        static char dummy[1024];
        return dummy;
    }
    return original_operator_new(size);
}

// 暴露给测试代码的接口
void start_checking() {
    g_checking = true;
    g_allocation_triggered = false;
}

void stop_checking() {
    g_checking = false;
}

bool did_allocate() {
    return g_allocation_triggered;
}

测试代码中的宏定义:

#include <cassert>

// 声明拦截库的接口
extern "C" {
void start_checking();
void stop_checking();
bool did_allocate();
}

#define START_CHECKING_FUNCTION() start_checking()
#define STOP_CHECKING_FUNCTION() stop_checking()

void my_unit_test() {
    my_object obj;
    obj.perform_initialization(); // 先执行允许分配的初始化操作

    START_CHECKING_FUNCTION();
    obj.perform_realtime_stuff(); // 实时操作,禁止分配
    STOP_CHECKING_FUNCTION();

    // 优雅失败:用测试框架的断言(这里用标准assert示例)
    assert(!did_allocate() && "实时函数中意外调用了内存分配函数!");
}

编译与运行:

# 编译拦截库
gcc -shared -fPIC -ldl intercept_alloc.c -o libintercept_alloc.so

# 运行测试时加载库
LD_PRELOAD=./libintercept_alloc.so ./my_test_binary

优点:

  • 无需修改业务代码,完全解耦
  • 支持拦截所有标准分配函数(C和C++)
  • 可以在测试中精准控制检测时机
  • 不会导致程序abort,而是通过断言优雅报告错误

方案2:使用GCC的内存分配钩子(轻量但仅限GCC)

GCC提供了__malloc_hook和__free_hook等钩子函数,可以在运行时替换内存分配的逻辑,不需要LD_PRELOAD。不过注意这些钩子已经被标记为deprecated,但在Linux下的GCC版本中仍然可以使用。

实现思路:

  1. 在测试代码中定义全局的钩子函数,当检测状态开启时,钩子函数会记录分配行为。
  2. 使用__malloc_hook = custom_malloc_hook来注册钩子,测试结束后恢复原钩子。

示例代码:

#include <malloc.h>
#include <cassert>

bool g_checking = false;
bool g_allocated = false;

// 原malloc钩子
static void* (*original_malloc_hook)(size_t, const void*) = NULL;

// 自定义malloc钩子
void* custom_malloc_hook(size_t size, const void* caller) {
    if (g_checking) {
        g_allocated = true;
    }
    // 调用原钩子
    return original_malloc_hook(size, caller);
}

#define START_CHECKING_FUNCTION() \
    do { \
        g_checking = true; \
        g_allocated = false; \
        original_malloc_hook = __malloc_hook; \
        __malloc_hook = custom_malloc_hook; \
    } while(0)

#define STOP_CHECKING_FUNCTION() \
    do { \
        __malloc_hook = original_malloc_hook; \
        g_checking = false; \
    } while(0)

void my_unit_test() {
    my_object obj;
    obj.init(); // 允许分配

    START_CHECKING_FUNCTION();
    obj.perform_realtime_stuff();
    STOP_CHECKING_FUNCTION();

    assert(!g_allocated && "实时函数调用了malloc!");
}

优点:

  • 不需要额外编译共享库,代码更紧凑
  • 同样支持动态开启/关闭检查

缺点:

  • 仅支持GCC,兼容性差
  • 钩子是全局的,多线程测试可能有线程安全问题
  • 无法直接拦截operator new,需要额外重载全局operator new配合使用

方案3:重载全局new/delete(C++专属)

如果你的代码都是C++,可以重载全局的operator new和operator delete,在其中加入检测逻辑。

实现思路:

  1. 重载全局operator new,加入检测状态判断。
  2. 用全局标志控制检测阶段,记录是否有分配行为。

示例代码:

#include <cstdlib>
#include <cassert>

bool g_checking_new = false;
bool g_new_called = false;

// 重载全局operator new
void* operator new(size_t size) {
    if (g_checking_new) {
        g_new_called = true;
        // 返回一个预分配块避免崩溃
        static char dummy[4096];
        return dummy;
    }
    return malloc(size);
}

void operator delete(void* ptr) noexcept {
    if (!g_checking_new) {
        free(ptr);
    }
}

#define START_CHECKING_FUNCTION() \
    do { \
        g_checking_new = true; \
        g_new_called = false; \
    } while(0)

#define STOP_CHECKING_FUNCTION() \
    do { \
        g_checking_new = false; \
    } while(0)

void my_unit_test() {
    my_object obj;
    obj.init();

    START_CHECKING_FUNCTION();
    obj.perform_realtime_stuff();
    STOP_CHECKING_FUNCTION();

    assert(!g_new_called && "实时函数调用了operator new!");
}

优点:

  • 纯C++实现,不需要依赖系统特性
  • 直接拦截C++的内存分配

缺点:

  • 无法拦截C风格的malloc,如果代码中混用C和C++分配,需要结合方案1或2
  • 重载全局new可能影响整个程序,需要确保测试环境隔离

总结

如果你的测试环境主要是Linux,**方案1(LD_PRELOAD拦截)**是最灵活可靠的选择,既能覆盖C和C的分配函数,又能完全解耦业务代码,还能在CI流水线中轻松集成。如果是纯C项目,方案3也可以作为轻量替代,但要注意覆盖所有分配路径。

所有方案都能实现「优雅失败」——通过断言而非abort来报告错误,完全符合你的需求。

内容的提问来源于stack exchange,提问作者Jean-Michaël Celerier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:52:37