C:基于可以自动扩展缓冲区的stringbuffer,实现内存格式化输出(bufprintf)

最近做一个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    char * fmt 格式化字符串,参见vsnprintf
// @param    ... 输出参数
// @return   int 成功返回0,失败返回 -1
//************************************
int bufprintf(stringbuffer* const pbuf, 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);
	/* Forward the '...' to vsnprintf */
	size_t bufsz = pbuf->length - pbuf->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);
	}
	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,运行就能看到效果。
C:基于可以自动扩展缓冲区的stringbuffer,实现内存格式化输出(bufprintf)

gcc下编译也很简单:

>gcc stringbuffer_test.c
>a.exe
sbuf content:
hello jerry
welcome to my party
上一篇:Magarose-NHS,NHS活化琼脂糖磁珠,NHS琼脂糖微球预活化的琼脂糖磁性微球


下一篇:Android CCodec (四) Output Buffer流程分析