这是来自竞技场分配器的代码的相关部分:
#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。
BSD make 不支持特定于目标的变量赋值,例如:
release: CFLAGS += -O2 -s -DTEST_MAIN
解决这个问题的一种方法是将额外的设置传递给变量中的子品牌:
CFLAGS += $(EXTRA_CFLAGS)
release:
$(MAKE) EXTRA_CFLAGS="-O2 -s -DTEST_MAIN" $(TARGET)
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
函数和其他内容可能会包含在库中!