背景
scratch_buffer 是 Glibc 中的一个实用工具,提供一块默认大小的栈空间,并可以动态扩展到空间大小(实用堆空间),使用 scratch_buffer 不用考虑 buffer 申请/扩容/释放,只需按照顺序调用相关函数操作 scratch_buffer 即可。在很多需要内存块,但又不必细究大小,需要动态扩容的场景很好用。
接下来,我们就来一起看一下 scratch_buffer 的具体实现。
scratch_buffer 的结构体定义
void* data:指向数据域起始位置的指针
size_t length:分配的数据域长度(以字节为单位)
union {...}__space:数据域的联合体
max_align_t __align:对齐参数,当使用堆内存时生效,记录对齐参数
char __c[1024]:预先在栈上面分配的 1024 字节内存
// glibc/include/scratch_buffer.h
/* Scratch buffer. Must be initialized with scratch_buffer_init
before its use. */
struct scratch_buffer {
void *data; /* Pointer to the beginning of the scratch area. */
size_t length; /* Allocated space at the data pointer, in bytes. */
union { max_align_t __align; char __c[1024]; } __space;
};
复制代码
scratch_buffer 相关的函数
1.scratch_buffer_init---初始化 scratch_buffer
在使用 scratch_buffer 时,先进行定义,然后必须先调用 scratch_buffer_init 才可以进行使用,该函数主要是初始化最重要的 data 和 length 参数。
/* Initializes *BUFFER so that BUFFER->data points to BUFFER->__space
and BUFFER->length reflects the available space. */
static inline void
scratch_buffer_init (struct scratch_buffer *buffer)
{
buffer->data = buffer->__space.__c;
buffer->length = sizeof (buffer->__space);
}
复制代码
2.scratch_buffer_free---释放 scratch_buffer
如果此时的 data 指针不等于预先分配的 1024 字节的首地址,说明进行了扩容,此时分配了堆空间,需要调用 free 进行内存释放。
/* Deallocates *BUFFER (if it was heap-allocated). */
static inline void
scratch_buffer_free (struct scratch_buffer *buffer)
{
if (buffer->data != buffer->__space.__c)
free (buffer->data);
}
复制代码
3.scratch_buffer_grow---scratch_buffer 扩容(不保留原有内容)
/* Grow *BUFFER by some arbitrary amount. The buffer contents is NOT
preserved. Return true on success, false on allocation failure (in
which case the old buffer is freed). On success, the new buffer is
larger than the previous size. On failure, *BUFFER is deallocated,
but remains in a free-able state, and errno is set. */
bool __libc_scratch_buffer_grow (struct scratch_buffer *buffer);
libc_hidden_proto (__libc_scratch_buffer_grow)
/* Alias for __libc_scratch_buffer_grow. */
static __always_inline bool
scratch_buffer_grow (struct scratch_buffer *buffer)
{
return __glibc_likely (__libc_scratch_buffer_grow (buffer));
}
复制代码
__libc_scratch_buffer_grow 的具体实现在 glibc/malloc/scratch_buffer_grow.c 中
按照如下流程进行扩容操作:
(1).计算扩容后的新长度---扩容因子为 2
新长度为原有长度的两倍
bool
__libc_scratch_buffer_grow (struct scratch_buffer *buffer)
{
void *new_ptr;
size_t new_length = buffer->length * 2;
复制代码
(2).调用 scratch_buffer_free 释放原有 buffer
/* Discard old buffer. */
scratch_buffer_free (buffer);
复制代码
(3).检查 new_length 是否溢出
/* Check for overflow. */
if (__glibc_likely (new_length >= buffer->length))
new_ptr = malloc (new_length);
else
{
__set_errno (ENOMEM);
new_ptr = NULL;
}
复制代码
(4).兼容 new_ptr == NULL 的情况
如果出现这种情况,说明上一步中分配内存失败,那么我们需要调用 scratch_buffer_init 来保证该块 scratch_buffer 是可以被 free 的,这时返回 false,表示扩容失败。
if (__glibc_unlikely (new_ptr == NULL))
{
/* Buffer must remain valid to free. */
scratch_buffer_init (buffer);
return false;
}
复制代码
(5).对 scratch_buffer 的 data 域和 length 域赋值
分别赋值为最新分配的内存首地址和长度。
/* Install new heap-based buffer. */
buffer->data = new_ptr;
buffer->length = new_length;
return true;
}
libc_hidden_def (__libc_scratch_buffer_grow)
复制代码
4.scratch_buffer_grow_preserve---scratch_buffer 扩容(保留原有内容)
/* Like __libc_scratch_buffer_grow, but preserve the old buffer
contents on success, as a prefix of the new buffer. */
bool __libc_scratch_buffer_grow_preserve (struct scratch_buffer *buffer);
libc_hidden_proto (__libc_scratch_buffer_grow_preserve)
/* Alias for __libc_scratch_buffer_grow_preserve. */
static __always_inline bool
scratch_buffer_grow_preserve (struct scratch_buffer *buffer)
{
return __glibc_likely (__libc_scratch_buffer_grow_preserve (buffer));
}
复制代码
__libc_scratch_buffer_grow_preserve 的具体实现在 glibc/malloc/scratch_buffer_grow_preserve.c 中
按照如下流程进行扩容操作:
(1).计算扩容后的新长度---扩容因子为 2
新长度为原有长度的两倍
bool
__libc_scratch_buffer_grow_preserve (struct scratch_buffer *buffer)
{
size_t new_length = 2 * buffer->length;
void *new_ptr;
复制代码
(2).根据 data 域的指向进行不同的操作
如果 data 域与预先分配的 1024 字节栈空间首地址一致,说明是第一次扩容,那么我们需要调用 malloc 分配内存,并做内存分配检查,如果分配失败则直接返回 false,然后重要的一点是,我们还需要将栈空间中的数据拷贝到分配出的堆空间上,保留原有数据;
如果 data 域已经是堆空间上面的内存,那么我们检查是否出现越界异常
若无越界异常,则调用 realloc 函数在原有地址基础上分配 new_length 大小的内存(这里 realloc 函数内部会在空间不够时进行拷贝操作,若空间足够则不需要拷贝);
若出现越界异常,则处理方式同 scratch_buffer_grow
然后检查 new_ptr 是否等于 NULL,以便做兼容,保证扩容失败后,仍可正常 free scratch_buffer,注意,这里先 free(buffer->data)即原有数据,因为这种情况原有数据将要扩容越界,无需再进行保存。
if (buffer->data == buffer->__space.__c)
{
/* Move buffer to the heap. No overflow is possible because
buffer->length describes a small buffer on the stack. */
new_ptr = malloc (new_length);
if (new_ptr == NULL)
return false;
memcpy (new_ptr, buffer->__space.__c, buffer->length);
}
else
{
/* Buffer was already on the heap. Check for overflow. */
if (__glibc_likely (new_length >= buffer->length))
new_ptr = realloc (buffer->data, new_length);
else
{
__set_errno (ENOMEM);
new_ptr = NULL;
}
if (__glibc_unlikely (new_ptr == NULL))
{
/* Deallocate, but buffer must remain valid to free. */
free (buffer->data);
scratch_buffer_init (buffer);
return false;
}
}
复制代码
(3).对 scratch_buffer 的 data 域和 length 域赋值
分别赋值为最新分配的内存首地址和长度。
/* Install new heap-based buffer. */
buffer->data = new_ptr;
buffer->length = new_length;
return true;
}
libc_hidden_def (__libc_scratch_buffer_grow_preserve)
复制代码
5.scratch_buffer_set_array_size---scratch_buffer 扩容(大小至少可以容纳 nelem 个大小为 size 的元素)
入参:
scratch_buffer 结构体指针
size_t nelem:元素个数,可以为 0
size_t size:每个元素的 size 大小,可以为 0
出参:返回 true 或 false
true 代表扩容成功,获得一块更大的 buffer,不保留原始内容
fail 代表扩容失败,原有 buffer 被释放,但当前的结构体还是可以继续调用释放函数
注意:这个函数是是否可以减少 buffer size 大小是不确定的
/* Grow *BUFFER so that it can store at least NELEM elements of SIZE
bytes. The buffer contents are NOT preserved. Both NELEM and SIZE
can be zero. Return true on success, false on allocation failure
(in which case the old buffer is freed, but *BUFFER remains in a
free-able state, and errno is set). It is unspecified whether this
function can reduce the array size. */
bool __libc_scratch_buffer_set_array_size (struct scratch_buffer *buffer,
size_t nelem, size_t size);
libc_hidden_proto (__libc_scratch_buffer_set_array_size)
/* Alias for __libc_scratch_set_array_size. */
static __always_inline bool
scratch_buffer_set_array_size (struct scratch_buffer *buffer,
size_t nelem, size_t size)
{
return __glibc_likely (__libc_scratch_buffer_set_array_size
(buffer, nelem, size));
}
复制代码
__libc_scratch_buffer_set_array_size 的具体实现在 glibc/malloc/scratch_buffer_set_array_size.c 中
按照如下流程进行 size 切换操作:
(1).计算切换后的 size 大小
size 大小就等于元素个数*每个元素的 size 大小
bool
__libc_scratch_buffer_set_array_size (struct scratch_buffer *buffer,
size_t nelem, size_t size)
{
size_t new_length = nelem * size;
复制代码
(2).查看是否有 nelem 和 size 数值过大,导致的越界异常
如果满足上面两个条件,说明是出现了越界的情况,需要释放掉原有 buffer,之后重新初始化,保证后续可以调用 free 函数。
/* Avoid overflow check if both values are small. */
if ((nelem | size) >> (sizeof (size_t) * CHAR_BIT / 2) != 0
&& nelem != 0 && size != new_length / nelem)
{
/* Overflow. Discard the old buffer, but it must remain valid
to free. */
scratch_buffer_free (buffer);
scratch_buffer_init (buffer);
__set_errno (ENOMEM);
return false;
}
复制代码
(3).如果需要的 size 大小小于现在已分配的 size 大小,则直接返回 true,无需分配
这里也说明了,__libc_scratch_buffer_set_array_size 操作可能并不能减小 buffersize
if (new_length <= buffer->length)
return true;
复制代码
(4).后续操作与之前基本一致
/* Discard old buffer. */
scratch_buffer_free (buffer);
char *new_ptr = malloc (new_length);
if (new_ptr == NULL)
{
/* Buffer must remain valid to free. */
scratch_buffer_init (buffer);
return false;
}
/* Install new heap-based buffer. */
buffer->data = new_ptr;
buffer->length = new_length;
return true;
}
libc_hidden_def (__libc_scratch_buffer_set_array_size)
复制代码
6.scratch_buffer_dupfree---返回 scratch_buffer 的 size 字节数据的拷贝(堆空间)
/* Return a copy of *BUFFER's first SIZE bytes as a heap-allocated block,
deallocating *BUFFER if it was heap-allocated. SIZE must be at
most *BUFFER's size. Return NULL (setting errno) on memory
exhaustion. */
void *__libc_scratch_buffer_dupfree (struct scratch_buffer *buffer,
size_t size);
libc_hidden_proto (__libc_scratch_buffer_dupfree)
/* Alias for __libc_scratch_dupfree. */
static __always_inline void *
scratch_buffer_dupfree (struct scratch_buffer *buffer, size_t size)
{
void *r = __libc_scratch_buffer_dupfree (buffer, size);
return __glibc_likely (r != NULL) ? r : NULL;
}
复制代码
__libc_scratch_buffer_dupfree 的具体实现在 glibc/malloc/scratch_buffer_dupfree.c 中
按照如下流程进行 dupfree 操作:
void *
__libc_scratch_buffer_dupfree (struct scratch_buffer *buffer, size_t size)
{
void *data = buffer->data;
if (data == buffer->__space.__c)
{
void *copy = malloc (size);
return copy != NULL ? memcpy (copy, data, size) : NULL;
}
else
{
void *copy = realloc (data, size);
return copy != NULL ? copy : data;
}
}
libc_hidden_def (__libc_scratch_buffer_dupfree)
复制代码
scratch_buffer 的使用流程
通过上面函数的解析,我们大致了解了 scratch_buffer 的操作流程,实际上,它的使用流程如下所示,特别要注意 init 和 free 的流程。
struct scratch_buffer tmpbuf;
scratch_buffer_init (&tmpbuf);
while (!function_that_uses_buffer (tmpbuf.data, tmpbuf.length))
if (!scratch_buffer_grow (&tmpbuf))
return -1;
scratch_buffer_free (&tmpbuf);
return 0;
复制代码
总结
scratch_buffer 对原有的 malloc/realloc/free 进行了封装,使得可以方便地使用一块栈或堆上的 buffer,能够自由地进行 buffer 扩容,也提供了一些方便的操作函数,其原理也相对简单,容易理解,是一个很实用的工具。
评论