主要问题
在使用vsnprintf时,我得到了一些意外的结果。在下面的代码中,我使用了snprintf并传递了一个空目标指针,以了解它需要多少空间
#define long_string 256
typedef char STRING_VARIABLE [long_string + 1];
void SAFE_snprintf( char * buffer, char * format ,... )
{
va_list args;
va_start(args, format);
int m = vsnprintf (0, 0, format, args);
printf("m = %dn", m);
if (m < 0)
{
perror ("snprintf failed");
abort ();
}
// Allocating memory
char *bufferString = (char *) malloc (n - 1);
if (!bufferString)
{
perror ("malloc failed");
abort ();
}
m = vsnprintf (bufferString, (n - 1), format, args);
if (m < 0)
{
perror ("vsnprintf failed");
abort ();
}
strcpy(buffer, bufferString);
free (bufferString);
va_end(args);
}
int main(int argc, char * argv[])
{
char InputString [] = "Hello";
STRING_VARIABLE bufferStrings;
char format [] = "%s_test";
int n = snprintf (0, 0, "%s_test", InputString);
if (n < 0)
{
perror ("vsnprintf failed");
abort ();
}
printf("n = %d", n);
SAFE_snprintf(bufferStrings, format , InputString);
return 0;
}
上述代码返回
n = 7
m = 10
我不知道为什么snprintf返回7(这是正确的(,而vsnprintf返回10。我认为这是错误的,或者当然我的理解在某个地方有缺陷。
我为什么这么做
我想使用snprintf";"安全地";即通过避免字符串截断。这个想法是在使用snprintf之前确定字符串大小。这意味着要使用两次。一旦锻炼了尺寸,然后分配适当的内存再次使用snprintf。当然,任何printf函数都需要可变数量的输入。因此,我们想使用vsnprintf来创建一个执行上述操作的变差函数。
我知道仍然存在检查传递的原始字符串是否太长并且不会导致字符串截断的问题。但对为什么vsnprintf没有像预期的那样工作感到困惑
对另一个调用重用va_list
是无效的。
va_list args;
va_start(args, format);
vsnprintf(..., args);
va_end(args); // basically you have to call it
在调用另一个v*
函数之前,再次调用va_copy
或va_start
。
va_list args;
va_start(args, format);
vsnprintf(..., args);
va_end(args);
va_start(args, format);
vsnprintf(..., args);
va_end(args);
或
va_list args, args2;
va_start(args, format);
vsnprintf(..., args);
va_copy(args2, args);
va_end(args);
vsnprintf(..., args2);
va_end(args2);
您的缓冲区被限制为256个字符的long_string 256
,因此它不能解决任何问题,并且strcpy(buffer, bufferString);
非常不安全。
不要使用typedef
数组-它们非常令人困惑。更喜欢结构。
总的来说,您似乎想要实现asprintf
——请参阅带有自动内存分配的sprintf((,https://man7.org/linux/man-pages/man3/asprintf.3.html。也许asprintf
将成为标准https://en.cppreference.com/w/c/experimental/dynamic。
您的代码中存在多个问题:
- 您可以重用
args
,而无需使用va_start()
或va_copy()
重新启动va_list
- 则分配CCD_ 14字节而不是CCD_
- 用CCD_ 16复制所分配的字符串是不安全的,因为所组成的字符串可能超过256字节
format
的类型应为const char *format
- 你的类型
STRING_VARIABLE
很令人困惑。将ponters隐藏在typedef后面是令人困惑的,但将数组隐藏在typeDef后面更糟糕
为什么不总是分配内存并将指针返回给调用者?一些C库有一个具有这些语义的函数asprintf
,这是标准化的一个很好的候选者,但委员会花了30年的时间才考虑到strdup()
,所以制作自己的函数是有意义的。
这是一个分配版本:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// allocating version of snprintf
char *SAFE_snprintf(const char *format, ...) {
// use a local buffer to avoid calling snprintf twice for short strings
char buf[256];
va_list args;
int len;
va_start(args, format);
len = vsnprintf(buf, sizeof buf, format, args);
va_end(args);
if (len < 0) {
return NULL; // errno was set by snprintf
}
// Allocating memory
char *bufferString = (char *)malloc(len + 1);
if (!bufferString) {
return NULL; // errno was set by malloc to EMEM
}
if (len < (int)sizeof(buf)) {
strcpy(bufferString, buf);
} else {
va_start(args, format);
vsnprintf(bufferString, len + 1, format, args);
va_end(args);
}
return bufferString;
}
int main(int argc, char *argv[]) {
char InputString[] = "Hello";
int n = snprintf(NULL, 0, "%s_test", InputString);
printf("n = %dn", n);
char *str = SAFE_snprintf("%s_test", InputString);
if (str == NULL) {
perror("SAFE_snprintf failed");
return 1;
} else {
printf("len=%zu, str=%sn", strlen(str), str);
free(str);
return 0;
}
}
如果您的目标只是检测截断,只需将snprintf
的返回值与目标数组的长度进行比较即可:
#include <stdio.h>
#include <stdlib.h>
// safe version of snprintf that detects truncation
// complain on error and truncation
// return -1 on error
// return 1 on case of truncation
// otherwise return 0
int SAFE_snprintf(char *dest, size_t size, const char *format, ...) {
int len;
va_start(args, format);
len = vsnprintf(dest, size, format, args);
va_end(args);
if (len < 0) {
perror("vsnprintf failed");
return -1;
} else
if (len < (int)size) {
// no error
return 0;
} else {
perror("vsnprintf caused truncation");
return -1;
}
}
int main(int argc, char *argv[]) {
char hello[] = "Hello";
char long_hello[] = "Pardon my intrusion, I merely want to say hello";
char name[] = "Mrs Robinson";
char buf[32];
int res;
res = SAFE_snprintf(buf, sizeof buf, "%s %s", hello, name);
printf("res=%d, buf=%sn", res, buf);
res = SAFE_snprintf(buf, sizeof buf, "%s %s", long_hello, name);
printf("res=%d, buf=%sn", res, buf);
return 0;
}