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

仅FreeBSD存在的arena分配器单元测试失败问题求助

Arena分配器FreeBSD 14.0测试失败排查

问题描述

我开发的arena内存分配器在Linux、Windows、macOS系统上运行单元测试全部通过,但在FreeBSD 14.0系统中出现测试失败:测试检查curr_pool[96]至curr_pool[99]是否被填充为0xA5时失败,实际值均为0。


核心分配函数代码

#ifdef DEBUG
    #include <string.h>
    #define D(x) x
#else
    #define D(x) (void) 0
#endif

/* Allocates a pointer from `arena`.
 *
 * The allocated pointer is at least aligned to `alignment`.
 *
 * `alignment` must be a power of 2.
 * 
 * `size` must be a multiple of `alignment`.
 *
 * If a request can not be entertained, i.e. would overflow, or `arena` is full,
 * the function returns `nullptr`. The function also returns a `nullptr` if the 
 * requested `size` or `alignment` is 0 or if `alignment` is not a power of 2,
 * or if `size` is not a multiple of `alignment`.
 *
 * Any allocations made prior to this call are not freed on failure, and remain 
 * valid until the arena is either reset or destroyed. */
void *arena_alloc(Arena *arena, size_t alignment, size_t size)
{
    if (size == 0
        || alignment == 0 || (alignment != 1 && !is_power_of_two(alignment))
        || !is_multiple_of(size, alignment)) {
        return nullptr;
    }

    M_Pool *curr_pool = arena->pools[arena->current - 1];
    uint8_t *const p = curr_pool->buf + curr_pool->offset;
    const uintptr_t original = ((uintptr_t) p);

    if (original > UINTPTR_MAX - alignment) {
        return nullptr;
    }

    const uintptr_t remain = original & (alignment - 1);
    const uintptr_t aligned =
        remain != 0 ? original + (alignment - remain) : original;
    const size_t offset = aligned - original;

    if (size > SIZE_MAX - offset) {
        return nullptr;
    }

    size += offset;

    if (size > curr_pool->buf_len - curr_pool->offset) {
        return nullptr;
    }

    /* Set the optional padding for alignment immediately before a user block, 
     * and the bytes immediately following such a block to non-zero. 
     * The intent is to trigger OBOB failures to inappropiate app use of 
     * strlen()/strnlen(), which keep forging ahead till encountering ascii NUL. */
    D(
        /* 0xA5 is used in FreeBSD's PHK malloc for debugging purposes. */
        if (remain) {
            memset(p + (alignment - remain), 0xA5, alignment - remain);
        }
    );

    curr_pool->offset += size;
    D(memset(curr_pool->buf + curr_pool->offset, 0xA5,
            curr_pool->buf_len - curr_pool->offset));

    arena->last_alloc_size = size;

    /* Equal to "aligned", but preserves provenance. */
    return p + offset;
}

单元测试代码

static void test_arena_alloc(void)
{
    Arena *const arena = arena_new(nullptr, 100);

    TEST_ASSERT(arena);

    TEST_CHECK(arena_alloc(arena, 1, 112) == nullptr);
    TEST_CHECK(arena_alloc(arena, 0, 1) == nullptr);
    TEST_CHECK(arena_alloc(arena, 1, 0) == nullptr);
    TEST_CHECK(arena_alloc(arena, 2, 5) == nullptr);
    TEST_CHECK(arena_alloc(arena, 3, 5) == nullptr);

    TEST_CHECK(arena_alloc(arena, 1, 95));
    uint8_t *const curr_pool = arena->pools[0]->buf;

    TEST_CHECK(curr_pool[96] == 0xA5 && curr_pool[97] == 0xA5
        && curr_pool[98] == 0xA5 && curr_pool[99] == 0xA5);

    arena_reset(arena);

#ifdef HAVE_STDALIGN_H
    const int *const a = arena_alloc(arena, alignof (int), 5 * sizeof *a);
    const double *const b = arena_alloc(arena, alignof (double), 2 * sizeof *b);
    const char *const c = arena_alloc(arena, 1, 10);
    const short *const d = arena_alloc(arena, alignof (short), 5 * sizeof *d);

    TEST_CHECK(a && is_aligned(a, alignof (int)));
    TEST_CHECK(b && is_aligned(b, alignof (double)));
    TEST_CHECK(c && is_aligned(c, 1));
    TEST_CHECK(d && is_aligned(d, alignof (short)));
#endif
    arena_destroy(arena);
}

各平台测试输出

Linux/Windows/macOS测试通过输出

cc -std=c11 -fPIC -Wall -Wextra -Werror -Wwrite-strings -Wno-unused-variable -Wno-parentheses -Wpedantic -Warray-bounds -Wno-unused-function -Wstrict-prototypes -Wdeprecated -DDEBUG    tests.c   -o tests
./tests arena_alloc --verbose=3
Test arena_alloc:
  tests.c:92: arena... ok
  tests.c:94: arena_alloc(arena, 1, 112) == nullptr... ok
  tests.c:95: arena_alloc(arena, 0, 1) == nullptr... ok
  tests.c:96: arena_alloc(arena, 1, 0) == nullptr... ok
  tests.c:97: arena_alloc(arena, 2, 5) == nullptr... ok
  tests.c:98: arena_alloc(arena, 3, 5) == nullptr... ok
  tests.c:100: arena_alloc(arena, 1, 95)... ok
  tests.c:103: curr_pool[96] == 0xA5 && curr_pool[97] == 0xA5 && curr_pool[98] == 0xA5 && curr_pool[99] == 0xA5... ok
  tests.c:125: a && is_aligned(a, alignof (int))... ok
  tests.c:126: b && is_aligned(b, alignof (double))... ok
  tests.c:127: c && is_aligned(c, 1)... ok
  tests.c:128: d && is_aligned(d, alignof (short))... ok
  SUCCESS: All conditions have passed.

Summary:
  Count of run unit tests:           1
  Count of successful unit tests:    1
  Count of failed unit tests:        0
SUCCESS: No unit tests have failed.

FreeBSD 14.0测试失败输出

Test arena_alloc:
  tests.c:92: arena... ok
  tests.c:94: arena_alloc(arena, 1, 112) == nullptr... ok
  tests.c:95: arena_alloc(arena, 0, 1) == nullptr... ok
  tests.c:96: arena_alloc(arena, 1, 0) == nullptr... ok
  tests.c:97: arena_alloc(arena, 2, 5) == nullptr... ok
  tests.c:98: arena_alloc(arena, 3, 5) == nullptr... ok
  tests.c:100: arena_alloc(arena, 1, 95)... ok
  tests.c:104: curr_pool[96] == 0xA5 && curr_pool[97] == 0xA5 && curr_pool[98] == 0xA5 && curr_pool[99] == 0xA5... failed
  tests.c:114: a && is_aligned(a, alignof (int))... ok
  tests.c:115: b && is_aligned(b, alignof (double))... ok
  tests.c:116: c && is_aligned(c, 1)... ok
  tests.c:117: d && is_aligned(d, alignof (short))... ok
  FAILED: 1 condition has failed.

编译配置(Makefile)

CFLAGS += -std=c11
CFLAGS += -fPIC
CFLAGS += -Wall
CFLAGS += -Wextra
CFLAGS += -Werror
CFLAGS += -Wwrite-strings
CFLAGS += -Wno-unused-variable
CFLAGS += -Wno-parentheses
CFLAGS += -Wpedantic
CFLAGS += -Warray-bounds
CFLAGS += -Wno-unused-function
CFLAGS += -Wstrict-prototypes
CFLAGS += -Wdeprecated


TARGET := arena
TEST_TARGET := tests
SLIB_TARGET := libarena.a
DLIB_TARGET := libarena.so

RM := /bin/rm -f

release: CFLAGS += -O2 -s -DTEST_MAIN
release: $(TARGET)

debug: CFLAGS += -DTEST_MAIN -DDEBUG -g3 -ggdb -fsanitize=address,leak,undefined
debug: $(TARGET)

static: $(SLIB_TARGET)

$(SLIB_TARGET): $(TARGET).o
    $(AR) rcs $@ $^

shared: $(DLIB_TARGET)
shared: LDFLAGS += -shared

$(DLIB_TARGET): $(TARGET).o
    $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

test: CFLAGS += -DDEBUG
test: $(TEST_TARGET)
    ./$(TEST_TARGET) --verbose=3

clean: 
    $(RM) $(TARGET) $(TEST_TARGET) $(TARGET).o $(SLIB_TARGET) $(DLIB_TARGET)

.PHONY: release debug static shared test clean
.DELETE_ON_ERROR:

问题排查与解决方案

可能原因分析

  1. DEBUG宏未正确定义:填充0xA5的逻辑被包裹在D()宏中,仅在DEBUG模式下执行。虽然Makefile的test目标指定了-DDEBUG,但FreeBSD的clang编译器可能存在宏定义优先级或传递问题,导致实际编译时未启用DEBUG逻辑。
  2. Pool缓冲区长度不符预期:测试中创建的arena指定每个pool大小为100,但如果arena_new函数在FreeBSD下对内存大小做了对齐调整(比如向上对齐到系统页边界或其他值),会导致curr_pool->buf_len不等于100,从而memset填充的字节数为0或不足。
  3. 编译器优化干扰:FreeBSD下的clang可能在DEBUG模式下仍优化掉无直接副作用的memset操作。

排查与修复步骤

  1. 验证DEBUG宏是否生效:在测试代码中添加如下代码,编译时若未定义DEBUG会触发编译错误:
    #ifndef DEBUG
    #error "DEBUG macro is not defined!"
    #endif
    
  2. 检查Pool缓冲区实际长度:在测试中添加打印语句,确认curr_pool->buf_len的值:
    TEST_CHECK(arena_alloc(arena, 1, 95));
    uint8_t *const curr_pool = arena->pools[0]->buf;
    printf("buf_len: %zu, offset: %zu, fill count: %zu\n", 
           arena->pools[0]->buf_len, 
           arena->pools[0]->offset, 
           arena->pools[0]->buf_len - arena->pools[0]->offset);
    
  3. 强制禁用优化:在Makefile的test目标中添加-O0,避免编译器优化掉memset操作:
    test: CFLAGS += -DDEBUG -O0
    
  4. 检查arena_new的内存分配逻辑:确认arena_new中是否对传入的pool大小做了调整,若有则确保调整后的大小符合测试预期。

内容的提问来源于stack exchange,提问作者Madagascar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 07:34:53