该错误仅在 FreeBSD 上出现,但在 Windows、Linux 和 MacOS 上运行良好

问题描述 投票:0回答:1

这是来自竞技场分配器的代码的相关部分:

#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(Ubuntu 和 Linux Mint)、Windows 10 和 Windows 11 以及 MacOS(macos-latest,无论 Github Actions 使用的版本)上,所有这些都可以顺利通过。

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 上编译这段代码,一个测试就会失败:

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.

如果我打印出

curr_pool[96]
-
curr_pool[99]
的值,它们都是0,而不是0xA5。

这是我正在使用的 Makefile,它定义了

DEBUG
标志:

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:

测试是用以下内容编译的:

make test

如果有任何遗漏,或者需要更多代码,请在评论中询问。 不过,整个代码都存在于:arena,假设有人想要编译它。

uname -a
显示 FreeBSD 14.0。

c makefile freebsd
1个回答
0
投票
  1. BSD make 不支持特定于目标的变量赋值,例如:

    release: CFLAGS += -O2 -s -DTEST_MAIN
    

    解决这个问题的一种方法是将额外的设置传递给变量中的子品牌:

    CFLAGS += $(EXTRA_CFLAGS)
    
    release:
        $(MAKE) EXTRA_CFLAGS="-O2 -s -DTEST_MAIN" $(TARGET)
    
  2. BSD make 不支持

    $^
    自动变量,因此需要在命令中明确包含先决条件。比如原文:

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

    需要更改为:

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

调整后,Makefile 可能会变成这样(使用调试选项时,我还需要在从

-fsanitize=address
构建
$(TARGET)
时添加
$(TARGET).o
):

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

# Add extra flags from parent $(MAKE)
CFLAGS += $(EXTRA_CFLAGS)
LDFLAGS += $(EXTRA_LDFLAGS)


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

RM := /bin/rm -f

release:
    $(MAKE) EXTRA_CFLAGS="-O2 -s -DTEST_MAIN" $(TARGET)

debug:
    $(MAKE) EXTRA_CFLAGS="-DTEST_MAIN -DDEBUG -g3 -ggdb -fsanitize=address,leak,undefined" \
        EXTRA_LDFLAGS="-fsanitize=address" $(TARGET)

static: $(SLIB_TARGET)

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

shared: $(DLIB_TARGET)

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

test:
    $(MAKE) EXTRA_CFLAGS="-DDEBUG" $(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:

其他问题是由于构建环境的条件方面造成的。我建议更改它,以便发布和调试构建的目标文件和目标可执行文件具有不同的名称(或至少不同的路径),以便可以在互不干扰的情况下构建两者。同样,应更改使用

-DTEST_MAIN
有条件编译的代码,以便将代码放置在与库中不同的目标文件中,否则
main
函数和其他内容可能会包含在库中!

© www.soinside.com 2019 - 2024. All rights reserved.