在第三次通话中:
该serial_free()函数将使用的缓冲区空间设置为零:
C
static void serial_free(void* allocator_data, void* ignored)
{
(void)ignored;
unpacked_message_wrapper* wrapper = (unpacked_message_wrapper*)allocator_data;
wrapper->next_free_index = 0;
}
当serial_free()被调用时,它通过设置为零来“释放”所有内存next_free_index,以便可以重用缓冲区:
测试实施该实现在Valgrind. 要在 Valgrind 类型下运行程序:
valgrind ./protobuf-c-custom_allocator
在生成的报告中,您将看到未进行任何分配:
纯文本
==3977== Memcheck, a memory error detector
==3977== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3977== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==3977== Command: ./protobuf-c-custom_allocator
==3977==
==3977==
==3977== HEAP SUMMARY:
==3977== in use at exit: 0 bytes in 0 blocks
==3977== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==3977==
==3977== All heap blocks were freed -- no leaks are possible
==3977==
==3977== For lists of detected and suppressed errors, rerun with: -s
==3977== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
概括
要编写和使用自定义分配器,您必须:
- 确定您希望如何存储数据的方式。
- 围绕您的 proto 消息类型编写某种包装器,并提供适当alloc()的和free()功能替换。
- 创建包装器的对象。
- 创建ProtobufCAllocator使用你的alloc()和free()替换的,然后在x__unpack()函数中使用分配器。
在这个例子中,我们:
- 决定将数据存储在连续的、静态分配的内存块中 (1)
- unpacked_message_wrapper作为 protoMessage和的包装器编写,并为and :和(2)buffer提供替换alloc()free()serial_alloc()serial_free()
- 在堆栈上创建了一个对象unpacked_message_wrapper,我们将其命名为out(3)
- 在unpack_to_message_wrapper_from_buffer中,我们创建并用andProtobufCAllocator填充它并传递给函数 (4)。serial_alloc()serial_free()message__unpack
如果您在一个内存非常有限的系统上工作并且每个字节都是黄金,那么您可以确定需要多大MAX_UNPACKED_MESSAGE_LENGTH。为此,您可以首先为MAX_UNPACKED_MESSAGE_LENGTH. 然后在 中serial_alloc,您需要添加一些工具:
C
static void* serial_alloc(void* allocator_data, size_t size)
{
static int call_counter = 0;
static size_t needed_space_counter = 0;
needed_space_counter = ((size sizeof(int)) & ~(sizeof(int)));
printf("serial_alloc() called for: %d time. Needed space for worst case scenario is = %ld\n", call_counter, needed_space_counter);
...
对于这个示例案例,我们得到:
纯文本
serial_alloc called for: 1 time. The needed space for the worst-case scenario is = 32
当 .proto 消息变得更加复杂时,事情会变得很困难。让我们在 .proto 消息中添加一个新字段:
原始缓冲区
syntax = "proto3";
message Message
{
bool flag = 1;
float value = 2;
repeated string names = 3; // added field, type repeated means "dynamic array"
}
然后我们在消息中添加新条目:
C
int main()
{
Message in;
message__init(&in);
in.flag = true;
in.value = 1.234f;
const char name1[] = "Let's";
const char name2[] = "Solve";
const char name3[] = "It";
const char* names[] = {name1, name2, name3};
in.names = (char**)names;
in.n_names = 3;
// Serialization:
message__pack(&in, pack_buffer);
...
我们将在输出中看到这一点:
纯文本
serial_alloc() called for: 1 time. Needed space for worst case scenario is = 48
serial_alloc() called for: 2 time. Needed space for worst case scenario is = 72
serial_alloc() called for: 3 time. Needed space for worst case scenario is = 82
serial_alloc() called for: 4 time. Needed space for worst case scenario is = 92
serial_alloc() called for: 5 time. Needed space for worst case scenario is = 95
所以现在我们知道,这是我们最坏的情况。我们至少需要一个95字节宽的缓冲区。
在现实世界中,您通常希望设置大于 95 的空间,除非您 100% 确定并彻底测试过。