3

我正在使用 printf 和编写调用普通 printf 的 my_printf(...) 和将结果发送到特殊函数的 sprintf 的想法。(我在考虑 sprintf 因为它在大多数平台上的行为就像 printf 一样)。

我的想法是写一个小宏来做到这一点:

#define my_printf(X, Y...) do{ printf(X, ## Y); \
    char* 数据 = malloc(strlen(X)*sizeof(char)); \
    sprintf(数据,X,##Y);\
    其他打印(数据);\
    免费(数据);}同时(0)

但是由于 sprintf 可以将字符串扩展为比 X 大得多的大小,因此该方法几乎直接中断。

而且只是添加一个数字做 malloc 似乎是解决问题的错误方法,从那时起我就把问题移到未来和我想要打印一个大表达式的一天......

有没有人对如何解决这个问题有更好的想法?或者我怎么知道 sprintf 结果有多大?

谢谢约翰


更新:我忘记了 printf 返回它打印多少个字符,并且由于我已经在宏中调用 printf ,因此添加一个保存数字的 int 是一件非常容易的事情。

#define buf_printf(X, Y...) do{ int len = printf(X, ## Y); \
    char* 数据 = malloc((len+1)*sizeof(char)); \
    sprintf(数据,X,##Y);\
    其他打印(数据);\
    免费(数据);}同时(0)

更新:我正在考虑这个问题,也许使用一个看起来很像 ehemient 建议的正常功能是个好主意。那里的关键似乎是不同 printf 函数(vprintf、vsprintf 和 vsnprintf)的 v 版本。感谢您指出了这一点。

再次感谢约翰

4

4 回答 4

8

使用 snprintf 计算大小。从手册页:

" 如果输出由于此限制而被截断,则返回值是在有足够空间可用的情况下将写入最终字符串的字符数(不包括尾随的 '\0')

snprintf 是 C99 的标准。如果您只有 C89 编译器,请查看文档:预标准版本可能不会返回您想要的值。再次根据手册页,2.1 版之前的 glibc 如果输出被截断,则返回 -1,而不是所需的大小。

顺便说一句,sizeof(char) 在每个 C 实现中总是被定义为 1 :-)

于 2009-01-31T12:02:26.893 回答
5

最好的方法是使用可变参数。创建一个与 printf() 具有相同原型的函数,并使用 varargs 函数将数据传递给 sprintf 以填充所需的缓冲区,并在返回之前将该缓冲区传递给 printf("%s")。

许多早期的实现对最低级别的 printf() 调用都有 4K 限制,但我会选择更多。您可能只需要设置一个上限并坚持下去。

我们在日志系统中使用的一个技巧是使用 printf() 将数据写入 /dev/null 句柄。由于 printf() 返回写入的字符数,然后我们使用它来分配缓冲区。但这不是很有效,因为它涉及两次调用 printf() 类型的函数。

于 2009-01-31T12:02:15.520 回答
5

由于您使用的是 Linux,我建议您使用asprintf()它 - 它是为您分配字符串的 GNU 扩展。多亏了 C99 可变参数宏,您不需要弄乱可变参数。

所以你的宏看起来像:

#define MY_PRINT(...) do { \
                          char *data; \
                          asprintf(&data, __VA_ARGS__); \
                          printf("%s", data); \
                          other_print(data); \
                          free(data); \
                      } while (0)

注意!这是仅限 C99 和 GNU 的代码

编辑:现在它将只评估一次宏参数,因此使用 ("%d", i++) 之类的调用宏将正常工作。

于 2009-02-01T08:50:09.560 回答
2

如果您总是在一个系统上运行glibc(即 Linux 和任何其他具有 GNU 用户空间的操作系统),asprintf那么行为就像sprintf但可以自动处理分配本身。

int my_printf(const char *fmt, ...) {
    char *buf = NULL;
    int len;
    va_list ap;

    va_start(ap, &fmt);
    len = vasprintf(&buf, fmt, ap);
    va_end(ap);

    if (len < 0) {
        /* error: allocation failed */
        return len;
    }

    puts(buf);
    other_print(buf);

    free(buf);
    return len;
}

Pax 的答案更便携,但不是打印到/dev/null,这是一个更好的技巧:通过 POSIX,snprintf可以给定一个NULL缓冲区和0大小,它会返回它会写多少——但显然它实际上不会写任何东西.

int my_printf(const char *fmt, ...) {
    char *buf;
    int len, len2;
    va_list ap;

    va_start(ap, &fmt);
    len = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);

    buf = malloc(len + 1);
    if (!buf) {
        /* error: allocation failed */
        return -1;
    }

    va_start(ap, &fmt);
    len2 = snprintf(buf, len + 1, fmt, ap);
    buf[len] = '\0';
    va_end(ap);

    /* has another thread been messing with our arguments?
       oh well, nothing we can do about it */
    assert(len == len2);

    puts(buf);
    other_printf(buf);

    free(buf);
    return len;
}

好吧,正如 onebyone 所说,一些旧系统没有兼容的snprintf. 不能全部赢...

于 2009-02-01T05:15:58.897 回答