前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C:基于可以自动扩展缓冲区的stringbuffer,实现内存格式化输出(bufprintf)

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

作者头像
10km
发布2021-11-24 15:22:55
3610
发布2021-11-24 15:22:55
举报
文章被收录于专栏:10km的专栏

最近做一个C语言的嵌入式项目,需要分段向指定内存调用vsnprintf输出不定长度的格式化输出,因为是分段输出,而且长度不定,所以一开始就不能分配固定长度内存,每次输出都要从输出到上次的结尾开始,所以还要记录每次的输出长度。还是Java开发方便,有现成的StringBuffer可以用,不停的向StringBuffer调用 append添加就好了,哪有这么麻烦。 为了解决这个麻烦,我参照Java中的StringBuffer对象,实现了一个 stringbuffer,并基于它实现bufprintf函数可以向stringbuffer格式化输出,调用时就不需要再考虑自动分配内存和偏移量的问题了。

以下是可以直接运行的完整代码: stringbuffer_test.c

代码语言:javascript
复制
/*
 * 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下编译也很简单:

代码语言:javascript
复制
>gcc stringbuffer_test.c
>a.exe
sbuf content:
hello jerry
welcome to my party
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/11/19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档