【问题标题】:snprintf usage for making up formatted stringssnprintf 用于组成格式化字符串的用法
【发布时间】:2019-01-16 07:02:46
【问题描述】:

我正在尝试了解snprintf,并通过示例找到this answer

char buf[20] = "";
char *cur = buf, * const end = buf + sizeof buf;
cur += snprintf(cur, end-cur, "%s", "foo");
printf("%s\n", buf);
if (cur < end) {
    cur += snprintf(cur, end-cur, "%s", " bar");
}
printf("%s\n", buf);
free(str);

我不清楚的是我们分配了一个固定的硬编码缓冲区大小,这似乎受到缓冲区溢出的影响。在 N1570 中我发现 (7.21.6.5)

1

#include <stdio.h>
int snprintf(char * restrict s, size_t n,
const char * restrict format, ...);

2 snprintf 函数等价于 fprintf,除了 输出被写入一个数组(由参数 s 指定)而不是 到一条溪流。如果 n 为零,则不写入任何内容,并且 s 可能为空 指针。

所以在我看来,习惯用法如下:

int need_space = snprintf(NULL, 0, "abs %s", "fgh") + 1; //How much to allocate?
char *const str = malloc(need_space * sizeof(char)); //allocate
int written = snprintf(str, need_space, "abs %s", "fgh"); //do format
printf("Need space = %d, written = %d\n", need_space, written);
printf("%s\n", str);

或者这并不常见,还有其他问题?

【问题讨论】:

  • 它有第二个参数是有原因的。这是一种惯用的 C 方法来尝试避免溢出。
  • 没有“静默”截断,请参阅snprintf.3p - Linux manual page,特别注意"RETURN" 部分。您根据缓冲区大小验证返回。
  • 根据定义sizeof(char)是1
  • 您展示的内容不一定是人们所做的事情的一个原因是,对数据进行两次格式化需要付出一定的代价,一次是为了找出它需要多长时间,第二次是实际执行格式化。在您使用短固定长度字符串的示例中,成本并不高。对于更复杂的格式、更多参数,尤其是更长的字符串参数,存在一些值得关注的问题(但测量仍然是一个好主意——它到底有多慢?)。一些系统(Linux、GNU C 库)有asprintf()vasprintf();使用这些可能是个好主意。
  • 同意之前的评论和只是小的补充 - 您的方法需要两次指定相同的格式化字符串,如果复杂的字符串很容易出错,因为使用预处理器定义声明格式字符串也是一种不好的做法.

标签: c printf


【解决方案1】:

snprintf() 的2x 调用是一个常见的习惯用法。

...还有问题吗?

问题在角落里

格式维护

以下内容存在重复格式的问题 - 随着代码的老化而容易中断,并且只更改了一行。

// Oops!
int need_space = snprintf(NULL, 0, "abs %s", "fgh") + 1;
char *const str = malloc(need_space * sizeof(char));
int written = snprintf(str, need_space, "abs %s ", "fgh");

你注意到区别了吗?

最好一次性说明格式。

#define FMT_ABS_S "abs %s"
int need_space = snprintf(NULL, 0, FMT_ABS_S, "fgh");
char *const str = malloc(sizeof *str * (need_space + 1u));
int written = snprintf(str, need_space, FMT_ABS_S, "fgh");

波动性

代码需要确保值不会在两次调用之间发生变化(非volatile),并且在多线程应用程序中会出现特殊问题。

错误检查

代码缺少检查。然而,即使有支票,如何处理意外结果——也许只是保释?

static const char *fmt_s = "abs %s";
int needed_space = snprintf(NULL, 0, fmt_s, "fgh");
if (needed_space < 0) {
  Handle_EncodingError();
}
char * const str = malloc(sizeof *str * (needed_space + 1u));
if (str == NULL) {
  Handle_OutOfMemory();
}
int written = snprintf(str, needed_space, fmt_s, "fgh");
if (written < 0 || written > needed_space) {
  Handle_Error();
}

去两次

在某些情况下,2 次调用可能会造成浪费,但通常影响比想象的要小。 @Jonathan Leffler

但对我来说,“如果分配失败该怎么办”无论如何都是一个显示停止器,代码报告错误并可能退出。

选择性地,一次就足够了

s*printf() 格式受到严格控制时,我认为 1 次调用就足够了。

// int to string

#define LOG2_N 28
#define LOG2_D 93
// Number of char needed for a string of INT_MIN is log10(bit width) + 3
#define INT_SIZE ((sizeof(int)*CHAR_BIT-1)*LOG2_N/LOG2_D + 3)

char s[INT_SIZE * 2];  // I like to use 2x to handle locale issues, no need to be stingy here.

int len = snprintf(s, sizeof s, "%d", i);

if (len < 0 || (unsigned) len >= sizeof s) {
  Handle_VeryUnusualFailure();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-31
    • 2012-12-10
    • 1970-01-01
    • 1970-01-01
    • 2014-01-09
    相关资源
    最近更新 更多