样例程序如下:
allocate_module.c
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <sys/types.h>
#define UNIT_TESTING 1
#if UNIT_TESTING
extern void* _test_malloc(const size_t size, const char* file, const int line);
extern void* _test_calloc(const size_t number_of_elements, const size_t size,
const char* file, const int line);
extern void _test_free(void* const ptr, const char* file, const int line);
#define malloc(size) _test_malloc(size, __FILE__, __LINE__)
#define calloc(num, size) _test_calloc(num, size, __FILE__, __LINE__)
#define free(ptr) _test_free(ptr, __FILE__, __LINE__)
#endif // UNIT_TESTING
void leak_memory() {
int * const temporary = (int*)malloc(sizeof(int));
*temporary = 0;
}
void buffer_overflow() {
char * const memory = (char*)malloc(sizeof(int));
memory[sizeof(int)] = '!';
free(memory);
}
void buffer_underflow() {
char * const memory = (char*)malloc(sizeof(int));
memory[-1] = '!';
free(memory);
}
这个文件中由一个宏进行控制是否使用cmockery的内存检测功能,在上面程序的第8行是我后期添加的,在源程序中是不存在的。将UNIT_TESTING设置为1则为使用内存检测功能,对应的是cmockery中重写的一组内存相关的操作:_test_malloc, _test_calloc, _test_free。如果UNIT_TESTING设置为0,那么就使用libc中的malloc, calloc, free函数。程序的第9-18行就是这一功能选择的具体代码;
leak_memory函数,使用malloc申请一个int类型的内存空间,并随后将这个内存指针进行赋值为0,人为的丢失掉这块内存,即内存泄露现象。
buffer_overflow函数,先用malloc申请一个int类型的内存空间,然后再在这块内存的最后一个字节之后,写上了一个字符‘!’,也就是说在申请的内存之后的空间内进行赋值操作,即内存上溢。
buffer_underflow函数以memory[-1] = '!';这个方式进行在申请的内存的之前进行赋值操作,人为的制造了一个内存下溢的情况。
allocate_module_test.c
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include "cmockery.h"
extern void leak_memory();
extern void buffer_overflow();
extern void buffer_underflow();
// Test case that fails as leak_memory() leaks a dynamically allocated block.
void leak_memory_test(void **state) {
leak_memory();
}
// Test case that fails as buffer_overflow() corrupts an allocated block.
void buffer_overflow_test(void **state) {
buffer_overflow();
}
// Test case that fails as buffer_underflow() corrupts an allocated block.
void buffer_underflow_test(void **state) {
buffer_underflow();
}
int main(int argc, char* argv[]) {
const UnitTest tests[] = {
unit_test(leak_memory_test),
unit_test(buffer_overflow_test),
unit_test(buffer_underflow_test)
};
return run_tests(tests);
}
以上程序的1-23行进行封装了一下作为一个个的测试单元。然后再main函数里面填充到UnitTest变量中,最后调用run_tests函数进行运行测试。
运行结果为:
很显然是将三种不同的内存错误都进行了有效的识别,想知道单元测试程序是怎么就能够知道我们的内存使用情况的,那就要细致的分析一下cmockery关于内存操作的这一组程序:
#undef malloc
void* _test_malloc(const size_t size, const char* file, const int line) {
char* ptr;
MallocBlockInfo *block_info;
ListNode * const block_list = get_allocated_blocks_list();
const size_t allocate_size = size + (MALLOC_GUARD_SIZE * 2) +
sizeof(*block_info) + MALLOC_ALIGNMENT;
char* const block = (char*)malloc(allocate_size);
assert_true(block);
// Calculate the returned address.
ptr = (char*)(((size_t)block + MALLOC_GUARD_SIZE + sizeof(*block_info) +
MALLOC_ALIGNMENT) & ~(MALLOC_ALIGNMENT - 1));
// Initialize the guard blocks.
memset(ptr - MALLOC_GUARD_SIZE, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE);
memset(ptr + size, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE);
memset(ptr, MALLOC_ALLOC_PATTERN, size);
block_info = (MallocBlockInfo*)(ptr - (MALLOC_GUARD_SIZE +
sizeof(*block_info)));
set_source_location(&block_info->location, file, line);
block_info->allocated_size = allocate_size;
block_info->size = size;
block_info->block = block;
block_info->node.value = block_info;
list_add(block_list, &block_info->node);
return ptr;
}
#define malloc test_malloc
通过在所需要的内存大小之外再从该内存的上下各冗余出来两块保障空间,并进行赋值为0xef,然后将地址信息和内存大小信息填充到申请出来的内存块中,然后再返回给程序所使用的真正的内存位置。
内存所组织的方式所定义的结构体如下:
// Location within some source code.
typedef struct SourceLocation {
const char* file;
int line;
} SourceLocation;
// Doubly linked list node.
typedef struct ListNode {
const void *value;
int refcount;
struct ListNode *next;
struct ListNode *prev;
} ListNode;
// Debug information for malloc().
typedef struct MallocBlockInfo {
void* block; // 由 malloc().分配的内存地址
size_t allocated_size; // 分配的总大小
size_t size; // 请求的内存大小
SourceLocation location; // 分配函数调用的位置
ListNode node; // 在所有的分配内存中的节点(以链表挂接的)
} MallocBlockInfo;
MallocBlockInfo的内存结构如下图所示:
申请出来的内存都以双向链表的形式挂接起来。
所申请内存的计算方式,以及申请到的内存样子。再以一定的格式进行划分debug内存和用户使用内存空间。然后得到如下图的形式,其中用户所申请的空间以 int 类型大小所示例,即为 4 个字节
void* _test_calloc(const size_t number_of_elements, const size_t size,
const char* file, const int line) {
void* const ptr = _test_malloc(number_of_elements * size, file, line);
if (ptr) {
memset(ptr, 0, number_of_elements * size);
}
return ptr;
}
_test_calloc函数,简单的调用_test_malloc函数,并进行将返回的用户数据进行初始化为0,然后进行返回。
// Use the real free in this function.
#undef free
void _test_free(void* const ptr, const char* file, const int line) {
unsigned int i;
char *block = (char*)ptr;
MallocBlockInfo *block_info;
_assert_true((int)ptr, "ptr", file, line);
block_info = (MallocBlockInfo*)(block - (MALLOC_GUARD_SIZE +
sizeof(*block_info)));
// Check the guard blocks.
{
char *guards[2] = {block - MALLOC_GUARD_SIZE,
block + block_info->size};
for (i = 0; i < ARRAY_LENGTH(guards); i++) {
unsigned int j;
char * const guard = guards[i];
for (j = 0; j < MALLOC_GUARD_SIZE; j++) {
const char diff = guard[j] - MALLOC_GUARD_PATTERN;
if (diff) {
print_error(
"Guard block of 0x%08x size=%d allocated by "
SOURCE_LOCATION_FORMAT " at 0x%08x is corrupt ",
(size_t)ptr, block_info->size,
block_info->location.file, block_info->location.line,
(size_t)&guard[j]);
_fail(file, line);
}
}
}
}
list_remove(&block_info->node, NULL, NULL);
block = block_info->block;
memset(block, MALLOC_FREE_PATTERN, block_info->allocated_size);
free(block);
}
#define free test_free
_test_free函数用于释放_test_calloc或者_test_malloc函数所申请到的内存,由block_info = (MallocBlockInfo*)(block - (MALLOC_GUARD_SIZE +sizeof(*block_info)));
计算出来整块内存的索引信息,从而可以拿到保障内存区域(方法:char *guards[2] = {block - MALLOC_GUARD_SIZE,block + block_info->size};),以及用户使用的内存区域。然后循环检测进行校验是否发生了改变,如果有变化,说明了上溢出或者下溢出,异常处理。如果没有异常,那么从链表中将这个内存节点取下来,并进行释放。至于内存泄露的情况是在哪里检查的,有一个函数叫做fail_if_blocks_allocated,在这个里面对于所有的内存节点进行扫描然后得到内存的状态,并进行报错的。