暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Redis sds动态字符串学习五

红帽Linux编程 2021-08-23
728

Redis sds动态字符串学习五

本节介绍的Redis函数,涉及到带可变参数的情况,所以本文最开始先简单学习一下C语言中函数带可变参数的相关知识。

1. C中可变参数

C语言中常用的带可变参数的函数有 int printf(const char * format, ...)
,打印内容,在调试代码看变量值和地址时经常会用到。该函数第二个参数省略号就是一种带可变参数的情况,C语言中,函数参数有可变参数时,最后一个参数用省略号代替。可变参数用到va函数,头文件为 #include <stdarg.h>
,相关定义宏如下:

typedef char * va_list; // x86平台下va_list的定义

// 使ap指向第一个可变参数的地址,v为最后一个确定的参数
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
// 使ap指向下一个可变参数,并同时将ap所指向的参数返回
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
// 销毁ap
#define va_end(ap) ( ap = (va_list)0 )


宏 _INTSIZEOF(n)
,用来求出变量 n 占用内存空间的大小。

#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )


实际函数使用中,函数的固定部分,可以直接从函数定义时的参数名处获得。对于可变参数部分,先将指针指向第一个可变参数,然后依次后移指针,根据与结束标志的比较来判断是否已经获得全部参数,因此 val函数中结束标志必须先约定好,否则,指针会指向无效的地址,导致出错。

简单带可变参数代码验证:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define MY_TYPE char *
int va_test(int count, ... )
{
va_list argp;
int ii = 0;
char *para;
va_start( argp, count ); //argp指向传入的第一个可选参数,msg是最后一个确定的参数
while (ii<count)
{
para = va_arg( argp, MY_TYPE); //取出当前的参数,类型为char *
if ( strcmp( para, "/0") == 0 ) //采用空串指示参数输入结束
break;
printf("Parameter #%d is: %s\n", ii, para);
ii++;
}
va_end( argp ); //将argp置为NULL
return 0;
}
int main( void )
{
va_test(4,"hello", "world","demo!","88888", "/0");
return 0;
}


RHEL.9中执行结果如下:

$./valist
Parameter #0 is: hello
Parameter #1 is: world
Parameter #2 is: demo!
Parameter #3 is: 88888


另外在阅读Redis源码中,发现还有一个 va_copy
 的函数,也是在 stdarg.h 头文件中定义,在收索 va_list 相关的网页中少有提及该函数,查找记录如下:

void va_copy(va_list dest,va_list src);


va_copy宏将src复制到dest。dest 一个要初始化的va_list类型的实例 src 将用于初始化dest的源va_list va_end应在函数返回之前在dest上调用,或者任何后续的dest重新初始化(通过调用va_start或va_copy)。

2. sdscatprintf函数

sds main函数调用处,sdsempty()返回空的char *:

x = sdscatprintf(sdsempty(),"%d",123);


具体函数定义如下:

sds sdscatprintf(sds s, const char *fmt, ...) {
va_list ap;
char *t;
va_start(ap, fmt);
// T = O(N^2)
t = sdscatvprintf(s,fmt,ap);
va_end(ap);
return t;
}


其内部用到va_list相关可变函数,ap就指的是省略号对应部分的可变参数。

sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char staticbuf[1024], *buf = staticbuf, *t;
size_t buflen = strlen(fmt)*2;

if (buflen > sizeof(staticbuf)) {
buf = zmalloc(buflen);
if (buf == NULL) return NULL;
} else {
buflen = sizeof(staticbuf);
}

while(1) {
buf[buflen-2] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
va_end(cpy);
if (buf[buflen-2] != '\0') {
if (buf != staticbuf) zfree(buf);
buflen *= 2;
buf = zmalloc(buflen);
if (buf == NULL) return NULL;
continue;
}
break;
}

t = sdscat(s, buf);
if (buf != staticbuf) zfree(buf);
return t;
}


函数中用到va_cpy是将原来的可变参数拷贝一份,后续处理都是对cpy进行操作。其中用到c库函数:

int vsnprintf (char * s, size_t n, const char * format, va_list arg );
s 指的是要保存的格式化字符的数组。
n 可接受的最大字符数
format 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序
arg 可变参数内容

vsnprintf会自动在写入字符的后面加上停止符'\0'


调用上述函数后,cpy的可变参数列表都会按照fmt的格式存储在buf中,然后再用sdscat将buf的内容追加到s字后,并返回char型指针,即缓冲区的地址。后续就是返回格式化字符串存储区的指针。

在用gdb调试main函数中时,最终打印 sdscatprintf
 的返回值,为格式化后的 "123"

(gdb) p x
$17 = (sds) 0x605080 "123"
(gdb)


3. 小结

本文学习了c语言中,函数可变参数相关的函数和使用方法,简单代码验证。vsnprintf格式化函数,会自动在写入字符的后面加上停止符'\0'。注意使用的va_list变量,在最后都要进行va_end操作。

参考文献:

https://www.cnblogs.com/bwangel23/p/4700496.html

https://blog.csdn.net/swell624/article/details/3211161

https://www.runoob.com/cprogramming/c-variable-arguments.html

本人才疏学浅,理解水平有限,如果有错误及不当之处,请批评指正。

如果对您有一点点帮助,请帮忙转发和关注工作号:hongmaolinux,感激不尽^_^!


文章转载自红帽Linux编程,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论