最近做一个C语言的嵌入式项目,需要分段向指定内存调用vsnprintf
输出不定长度的格式化输出,因为是分段输出,而且长度不定,所以一开始就不能分配固定长度内存,每次输出都要从输出到上次的结尾开始,所以还要记录每次的输出长度。还是Java开发方便,有现成的StringBuffer
可以用,不停的向StringBuffer
调用 append
添加就好了,哪有这么麻烦。
为了解决这个麻烦,我参照Java中的StringBuffer
对象,实现了一个 stringbuffer
,并基于它实现bufprintf函数可以向stringbuffer
格式化输出,调用时就不需要再考虑自动分配内存和偏移量的问题了。
以下是可以直接运行的完整代码:
stringbuffer_test.c
/*
* stringbuffer_test.c
* Created on: 2021年11月19日
* Author: guyadong
*/
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
/** stringbuffer 结构定义 */
typedef struct
{
/** 输出缓冲区地址 */
char *buffer;
/** 输出缓冲区长度 */
size_t length;
/** bufprintf 向输出缓冲区输出的起始偏移,初始为0 */
size_t offset;
} stringbuffer;
//************************************
// 如果 stringbuffer 缓冲区长度小于 needed,则重新分配内存以满足长度需要
// @param stringbuffer * const p
// @param size_t needed
// @return char* 返回 stringbuffer 下次输出的起始地址.
//************************************
static char* ensure(stringbuffer * const p, size_t needed)
{
char *newbuffer = NULL;
size_t newsize = 0;
if ((p == NULL) || (p->buffer == NULL))
{
return NULL;
}
if ((p->length > 0) && (p->offset >= p->length))
{
/* make sure that offset is valid */
return NULL;
}
if (needed > INT_MAX)
{
/* sizes bigger than INT_MAX are currently not supported */
return NULL;
}
needed += p->offset + 1;
if (needed <= p->length)
{
/* buffer 容量满足要求 */
return p->buffer + p->offset;
}
/* calculate new buffer size */
if (needed > (INT_MAX / 2))
{
/* overflow of int, use INT_MAX if possible */
if (needed <= INT_MAX)
{
newsize = INT_MAX;
}
else
{
return NULL;
}
}
else
{
/** 以256整数倍扩容 */
newsize = ((needed + 256 - 1) >> 8 << 8);
}
/* reallocate with realloc if available */
newbuffer = (char*)realloc(p->buffer, newsize);
if (newbuffer == NULL)
{
free(p->buffer);
p->length = 0;
p->buffer = NULL;
return NULL;
}
p->length = newsize;
p->buffer = newbuffer;
return newbuffer + p->offset;
}
//************************************
// 基于vsnprintf函数实现向 stringbuffer 格式化输出,
// 输出成功后缓冲区起始偏移自动增加
// @param stringbuffer * const pbuf
// @param const char * fmt 格式化字符串,参见vsnprintf
// @param ... 输出参数
// @return int 成功返回0,失败返回 -1
//************************************
int bufprintf(stringbuffer* const pbuf, const char*fmt, ...)
{
if (!pbuf->buffer || !pbuf->length)
{
printf("INVALID ARGUMENT:pbuf is EMPTY\n");
return -1;
}
/* Declare a va_list type variable */
va_list args;
/* Initialize the va_list variable with the ... after fmt */
va_start(args, fmt);
size_t bufsz = pbuf->length - pbuf->offset;
/* 输出缓冲区的起始地址从 offset 开始 */
int wsz = vsnprintf(pbuf->buffer + pbuf->offset, bufsz, fmt, args);
va_end(args);
if (wsz < 0)
{
/** GCC在调用失败时会返回负值,需要根据errno判断是否为 buffer 溢出 */
if (errno == ERANGE)
{
/** buffer 溢出 */
va_list args1;
va_start(args1, fmt);
/** 计算实际需要的数据长度 */
wsz = vsnprintf(NULL, 0, fmt, args1);
char *output = ensure(pbuf, wsz);
va_end(args1);
if(!output)
{
/** 内存分配失败 */
printf("MEM ERROR\n");
return -1;
}
bufsz = pbuf->length - pbuf->offset;
va_list args2;
va_start(args2, fmt);
wsz = vsnprintf(output, bufsz, fmt, args2);
va_end(args2);
assert(wsz < bufsz);
}
else {
printf("vsnprintf ERROR %d:%s for fmt:[%s]\n", errno, strerror(errno), fmt);
return -1;
}
}
else if (wsz >= bufsz)
{
/** buffer 溢出 */
/** MSVC在 buffer 溢出时会返回应该写入的数据长度 */
char *output = ensure(pbuf, wsz);
if(!output)
{
/** 内存分配失败 */
printf("MEM ERROR\n");
return -1;
}
bufsz = pbuf->length - pbuf->offset;
va_list args;
va_start(args, fmt);
wsz = vsnprintf(output, bufsz, fmt, args);
va_end(args);
assert(wsz < bufsz);
}
/** 更新缓冲区偏移(offset),指向写入的字符串的末尾,下次输出从末尾开始 */
pbuf->offset += wsz;
return 0;
}
//************************************
// stringbuffer 初始化
// @param stringbuffer * const pbuf
// @param size_t length 初始分配的缓存区长度,为0使用默认值256
// @return int 成功返回0,失败返回 -1
//************************************
int stringbuffer_init(stringbuffer *const pbuf, size_t length)
{
static const size_t default_buffer_size = 256;
if (!pbuf)
{
printf("INVALID ARGUMENT:pbuf is NULL\n");
return -1;
}
if (!length) {
length = default_buffer_size;
}
void *p = malloc(length);
if(!p)
{
/** 内存分配失败 */
printf("MEM ERROR\n");
return -1;
}
pbuf->buffer = (char*)p;
pbuf->length = length;
pbuf->offset = 0;
return 0;
}
//************************************
// stringbuffer 对象销毁,释放 stringbuffer.buffer指针指向的内存,并将所有字段清零
// @param stringbuffer * const pbuf
// @param bool free_self 是否释放 pbuf 对象本身
// @return void
//************************************
void stringbuffer_uninit(stringbuffer *const pbuf, bool free_self)
{
if (pbuf && pbuf->buffer)
{
free(pbuf->buffer);
pbuf->buffer = NULL;
pbuf->length = 0;
pbuf->offset = 0;
if (free_self)
{
free(pbuf);
}
}
}
/************************************************************************/
/* stringbuffer 测试及调用示例 */
/************************************************************************/
int main()
{
stringbuffer sbuf;
/************************************************************************/
/* 初始化 stringbuffer,初始缓冲长度使用默认值256 */
/************************************************************************/
int c = stringbuffer_init(&sbuf, 0);
if (c == 0)
{
bufprintf(&sbuf, "hello %s\n", "jerry");
/** 从上次输出的结尾继续输出 */
bufprintf(&sbuf, "welcome to my party\n", "jerry");
/** 输出 buffer 内容 */
printf("sbuf content:\n%s\n", sbuf.buffer);
/************************************************************************/
/* 销毁 stringbuffer */
/** sbuf 是局部变量,不能被free,所以 这里 free_self 为 false */
/************************************************************************/
stringbuffer_uninit(&sbuf, false);
}
else
{
printf("stringbuffer init fail\n");
}
}
如果使用MSVC编译器,如在VS 开发人员提示(CMD)下执行 cl stringbuffer_test.c
就可以直接编译出stringbuffer_test.exe
,运行就能看到效果。
gcc下编译也很简单:
>gcc stringbuffer_test.c
>a.exe
sbuf content:
hello jerry
welcome to my party