【问题标题】:vsnprintf with a null destination pointer unexpected answer带有空目标指针的 vsnprintf 意外答案
【发布时间】:2022-01-30 11:31:07
【问题描述】:

主要问题

我在使用 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 = %d\n", 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 没有按预期工作感到困惑

【问题讨论】:

  • by avoiding string truncation 但你的缓冲区是 long_string 256 个字符长,所以无论如何它都会被截断。或者,strcpy(buffer, bufferString); 将无效。 malloc (n - 1); 为什么分配少一个字符?而且,对于您的错误:printf("m = %d\n", m); 您正在打印变量 m,而不是 n
  • 在编写问题代码时出现了一些拼写错误。对此很陌生。道歉。 malloc (n -1) 是一个错误。感谢您指出这一点。
  • n 中的malloc (n - 1); 是什么?哪种类型?它在哪里分配?为什么是-1?

标签: c variadic-functions variadic


【解决方案1】:

再次调用va_list无效。

va_list args;
va_start(args, format);
vsnprintf(..., args);
va_end(args); // basically you have to call it

在调用另一个v*函数之前再次调用va_copyva_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() with automatic memory allocation?https://man7.org/linux/man-pages/man3/asprintf.3.html 。也许asprintf 将成为标准https://en.cppreference.com/w/c/experimental/dynamic

【讨论】:

  • 我完全忽略了我正在重新使用 va_list 进行另一个呼叫。谢谢。我意识到 strcpy 不安全。我会改变它。关于 7 和 10 区别的任何想法(这是主要的混淆)。
  • 不,很可能你打错了。我无法重现,我从两者都得到了10,第一次调用应该返回10
  • 你是对的。一定是笔误我现在两个都10了。奇怪
  • malloc (n - 1) 似乎也是一个问题:)
【解决方案2】:

您的代码中存在多个问题:

  • 您可以重复使用args,而无需使用va_start()va_copy() 重新启动va_list
  • 您分配 n - 1 字节而不是 n + 1
  • 使用 strcpy 复制分配的字符串是不安全的,因为组合的字符串可能超过 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 = %d\n", n);
  
    char *str = SAFE_snprintf("%s_test", InputString);
    if (str == NULL) {  
        perror("SAFE_snprintf failed");
        return 1;
    } else {
        printf("len=%zu, str=%s\n", 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=%s\n", res, buf);
    res = SAFE_snprintf(buf, sizeof buf, "%s %s", long_hello, name);
    printf("res=%d, buf=%s\n", res, buf);
    return 0;
}

【讨论】:

    猜你喜欢
    • 2012-09-04
    • 1970-01-01
    • 2021-06-13
    • 1970-01-01
    • 2019-12-31
    • 1970-01-01
    • 2023-02-22
    • 1970-01-01
    • 2011-09-30
    相关资源
    最近更新 更多