C变量参数重构

C变量参数重构,c,function,refactoring,C,Function,Refactoring,我有一个函数irc\u sendline,可以像printfcan那样调用它 irc_sendline(s, "A strange game.\nThe only %s is not to play.", "winning move"); 它工作得很好,但我对它的实现不满意: int irc_sendline(irc *iobj, char *msg, ...) { char tmp_msg[BUFSIZE], fmsg[BUFSIZE]; va_list args; int

我有一个函数
irc\u sendline
,可以像
printf
can那样调用它

irc_sendline(s, "A strange game.\nThe only %s is not to play.", "winning move");
它工作得很好,但我对它的实现不满意:

int irc_sendline(irc *iobj, char *msg, ...)
{
   char tmp_msg[BUFSIZE], fmsg[BUFSIZE];
   va_list args;
   int len;

   va_start(args, msg);

   strncpy(tmp_msg, msg, BUFSIZE);
   strncat(tmp_msg, "\r\n", BUFSIZE);

   len = vsnprintf(fmsg, BUFSIZE, tmp_msg, args);
   len = send(iobj->fd, fmsg, len, 0);

   return len;
}
你看,我在这里使用了2个“临时”缓冲区,因为我首先必须将原始消息从函数参数复制到临时缓冲区以附加“\r\n”,然后将该临时缓冲区复制到另一个临时缓冲区以使用函数调用提供的参数进行实际格式化,只有这样我才能把东西送过去

我怎样才能让这个更干净,更好


谢谢大家的意见,我以为我唯一的问题是那里乱七八糟,但实际上这是一颗滴答作响的定时炸弹!我的新函数如下所示:

int irc_sendline(irc *iobj, char *msg, ...)
{
   char buffer[BUFSIZE];
   va_list args;
   int res_str_len;
   int sent;

   va_start(args, msg);

   res_str_len = vsnprintf(buffer, BUFSIZE, msg, args);

   sent =  send(iobj->fd, buffer, res_str_len, 0);
   sent += send(iobj->fd, "\r\n", 2, 0);

   return sent;
}

如果可以的话,我会在这里接受多个答案,但是,嗯。

既然
\r\n
将结束在格式化字符串的末尾,为什么不在后面复制:

va_start(args, msg);
len = vsnprintf(fmsg, BUFSIZE, msg, args);
strncat(fmsg, "\r\n", BUFSIZE - strlen(fmsg) - 1);

请注意,我还修复了strncat的参数。

首先使用
vsnprintf
格式化数据,然后将“\r\n”附加到该结果。或者,只需第二次调用
send
发送“\r\n”。

除非您想对strcat使用msg(不安全和邪恶,因为您不知道字符串大小),否则我认为您将不得不使用2个缓冲区


作为一个旁白,我会考虑StncPy(…,BuffsiZE-2),以便\r\n总是把它放在您的消息上,因此字符串总是被包装。

首先,如果您对“清洁器”感兴趣,请停止使用<代码> STRNCPY 。尽管名称有误导性(与流行的观点相反),但这不是一个长度有限的字符串复制函数。可以肯定地说,
strncpy
是一个如今几乎没有实际用途的函数。当您看到代码中使用的
strncpy
时,“cleaner”立即成为不可质疑的问题(除了
strncpy
实际用于的令人失望的狭窄案例集之外)

事实上,您的代码被破坏是因为您使用了
strncpy
:如果格式字符串的长度大于缓冲区的长度,
strncpy
不会将终止的空字符附加到结果中,这意味着后续的
strncat
调用将崩溃(最多)

标准库中不存在有限长度复制功能,但它通常由名为
strlcpy
的实现提供。如果您正在使用的实现没有提供一个,请自己编写一个并使用它

由于不正确使用
strncat
strncat
未将完整缓冲区的长度作为最后一个参数,因此代码也被破坏。相反,
strncat
需要缓冲区剩余可用部分的长度。因此,如果您想使用strncat,您必须首先计算上次复制后缓冲区末尾剩余的空间。同样,尽管
strncat
strncpy
更有用,但您最好使用非标准(但通常由实现提供)函数
strlcat
,该函数实际上按照您认为的
strncat
工作方式工作


其次,与其事先添加
\r\n
部分,不如以后再添加?使用以下事实,即
vsnprintf
返回写入输出缓冲区的字符数,只需在
vsnprintf
完成工作后的末尾追加
\r
\n
\0
字符即可。您不必为此目的使用strncat。只需直接将字符写入缓冲区,当然,要确保您不会越界。

代码的一个主要问题是——vsnprintf返回缓冲区无限大时将放入缓冲区的字符数,如果缓冲区不够大,则可能大于BUFSIZE。因此,如果您有一条溢出的消息,那么您将在缓冲区结束后发送随机垃圾。您需要添加一行

if (res_str_len >= BUFSIZE) res_str_len = BUFSIZE-1

在vprintf之后,如果您想实际截断消息

感谢您指出BUFSIZE的内容!我一点也没注意到,这可能会把我搞糟。您仍然忘记了如果缓冲区太短,
stncpy
不会向字符串添加空终止符<代码>BUFSIZE-2无法解决此问题。每次缓冲区端被格式字符串击中时,您的代码都会崩溃。您的代码因错误使用
strncpy
(请参阅我的答案)而被破坏。您对
strncat
的使用也被破坏<代码>strncat并没有做你认为它能做的事情。感谢你的警告(实际上有3个:),我目前正在研究这个问题,似乎我必须编写自己的strlcpy,因为Linux没有提供它。在strncpy问题上-因此它被破坏了,因为当目标字符串较长/与目标字符串一样长时,它不会追加\NUL?首先,是的,如果字符串较长,它不会追加
\0
。其次,如果字符串较短,它会用零填充缓冲区的整个剩余部分,这是完全不必要的。同样,
strncpy
不是为以null结尾的字符串创建的函数。它是一个为完全不同的目的而创建的函数——在一些旧版本的Unix文件系统中支持所谓的“固定宽度”字符串。您可以在这里了解更多信息:感谢您澄清这一点,现在我想起了为什么我在其他一些使用strncpy的程序中显式地以零结尾字符串。