【问题标题】:sprintf() with automatic memory allocation?sprintf() 自动分配内存?
【发布时间】:2011-04-16 00:32:15
【问题描述】:

我正在寻找一个类似于 sprintf() 的函数实现,它可以自动分配所需的内存。所以我想说

char* my_str = dynamic_sprintf( "Hello %s, this is a %.*s nice %05d string", a, b, c, d );

my_str 检索保存此 sprintf() 结果的已分配内存的地址。

在另一个论坛上,我读到可以这样解决:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    char*   ret;
    char*   a = "Hello";
    char*   b = "World";
    int     c = 123;

    int     numbytes;

    numbytes = sprintf( (char*)NULL, "%s %d %s!", a, c, b );
    printf( "numbytes = %d", numbytes );

    ret = (char*)malloc( ( numbytes + 1 ) * sizeof( char ) );
    sprintf( ret, "%s %d %s!", a, c, b );

    printf( "ret = >%s<\n", ret );
    free( ret );

    return 0;
}

但是当调用带有 NULL 指针的 sprintf() 时,这会立即导致段错误。

那么有什么想法、解决方案或提示吗?放置在公共域中的类似 sprintf() 的解析器的小型实现已经足够了,然后我可以自己完成。

非常感谢!

【问题讨论】:

标签: c malloc printf


【解决方案1】:

这是from Stack Overflow的原始答案。正如其他人所提到的,您需要snprintf 而不是sprintf。确保snprintf 的第二个参数是zero。这将阻止snprintf 写入作为第一个参数的NULL 字符串。

需要第二个参数,因为它告诉snprintf 没有足够的空间可用于写入输出缓冲区。当没有足够的空间可用时,snprintf 返回如果有足够的可用空间,它将写入的字节数。

在此处复制该链接中的代码 ...

char* get_error_message(char const *msg) {
    size_t needed = snprintf(NULL, 0, "%s: %s (%d)", msg, strerror(errno), errno) + 1;
    char  *buffer = malloc(needed);
    sprintf(buffer, "%s: %s (%d)", msg, strerror(errno), errno);
    return buffer;
}

【讨论】:

  • 你不应该在needed 中加 1 来说明终止的空字符吗?
  • 一开始没有发现第一行末尾的+1(它在可见区域之外):size_t needed = snprintf(...) + 1;
  • 我有点担心在此处传递 NULL 是否会引发未定义的行为,因此我检查并确认它是 C 标准明确允许的 - 请参阅 stackoverflow.com/a/57646312/1709587
  • 从技术上讲,这段代码是不安全的,因为errno 可能会在对snprintf 的调用和对sprintf 的调用之间发生变化,这不受缓冲区溢出的保护。两次调用都应使用snprintf,并应在第一次调用之前将errno 保存到局部变量中。
【解决方案2】:

GNU 和 BSD 有 asprintf 和 vasprintf,它们旨在为您做这件事。它将弄清楚如何为您分配内存,并在任何内存分配错误时返回 null。

asprintf 在分配字符串方面做了正确的事情——它首先测量大小,然后尝试使用 malloc 进行分配。否则,它将返回 null。除非您有自己的内存分配系统,不使用 malloc,否则 asprintf 是完成这项工作的最佳工具。

代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    char*   ret;
    char*   a = "Hello";
    char*   b = "World";
    int     c = 123;

    ret = asprintf( "%s %d %s!", a, c, b );
    if (ret == NULL) {
        fprintf(stderr, "Error in asprintf\n");
        return 1;
    }

    printf( "ret = >%s<\n", ret );
    free( ret );

    return 0;
}

【讨论】:

  • asprintf() 将是我选择的功能 - 但不幸的是,它非标准且不可移植 - 不好!
  • @the-shamen - 根据定义,您要求的是非标准且不可移植的。获取asprintf 的源代码,并在需要时将其拉入您的项目,或者独立重新实现。
  • 我还没有听说过返回指针的asprintf()。 GNU 和 BSD 附带的(由 gnulib 和 libstrl 提供)具有与等效的 printf() 调用相同的返回值,并将指向指针的指针作为第一个参数。所以,char *s; int ret = asprintf(&amp;s, "%s %d %s!", a, c, b);ret == -1 上有错误。只是想知道,哪些系统/库提供了一个 asprintf() ,它返回一个像这个答案一样的指针?
【解决方案3】:

如果您可以接受 GNU/BSD 扩展,那么问题已经得到解答。您可以使用asprintf()(和vasprintf() 来构建包装函数)并完成。

但是根据手册页,snprintf()vsnprintf() 是 POSIX 强制要求的,后者可用于构建您自己的简单版本的 asprintf()vasprintf()

int
vasprintf(char **strp, const char *fmt, va_list ap)
{
    va_list ap1;
    int len;
    char *buffer;
    int res;

    va_copy(ap1, ap);
    len = vsnprintf(NULL, 0, fmt, ap1);

    if (len < 0)
        return len;

    va_end(ap1);
    buffer = malloc(len + 1);

    if (!buffer)
        return -1;

    res = vsnprintf(buffer, len + 1, fmt, ap);

    if (res < 0)
        free(buffer);
    else
        *strp = buffer;

    return res;
}

int
asprintf(char **strp, const char *fmt, ...)
{
    int error;
    va_list ap;

    va_start(ap, fmt);
    error = vasprintf(strp, fmt, ap);
    va_end(ap);

    return error;
}

你可以做一些预处理器的魔法,只在不支持它们的系统上使用你的函数版本。

【讨论】:

  • 您只能将va_list 变量传递给一个函数。要像在 vasprintf() 中一样使用两次 vsnprintf(),您应该使用 va_copy()
  • 如果您在从vasprintf() 返回之前添加va_end(ap1)(例如,在调用vsnprintf() 之后)。
  • 你忘记在 vasprintf 的末尾设置 strp
  • 代码中有一些错误处理(它处理 calloc 的失败),但如果 vsnprintf 失败,它仍然会爆炸:如果小于 -1,大小计算将环绕作为第一个 vsnprintf 的错误代码返回。如果第二次调用vsnprintf 失败,则缓冲区泄漏。
  • @user2421739 我修复了这个问题,因为这个答案已经很老了。
【解决方案4】:
  1. 如果可能,请使用 snprintf -- 它提供了一种简单的方法来衡量将产生的数据大小,以便您分配空间。
  2. 如果您真的不能这样做,另一种可能性是使用fprintf 打印到临时文件以获取大小、分配内存,然后使用 sprintf。 snprintf 绝对是首选方法。

【讨论】:

    【解决方案5】:

    GLib 库提供了一个 g_strdup_printf 函数,如果链接到 GLib 是一个选项,它可以完全满足您的需求。来自文档:

    类似于标准 C sprintf() 功能但更安全,因为它 计算所需的最大空间 并分配内存来保存 结果。返回的字符串应该是 不再使用g_free() 释放 需要。

    【讨论】:

    • 您好,谢谢!但这只是glibc,我需要一个独立于平台的解决方案。所以也许自己做这件事更好?
    • GLib(GTK+ 的基础),而不是 GNU C 库 (glibc)。但是相当于glibc的asprintf。
    • glib 独立于平台
    【解决方案6】:

    POSIX.1(又名 IEEE 1003.1-2008)提供 open_memstream:

    char *ptr;
    size_t size;
    FILE *f = open_memstream(&ptr, &size);
    fprintf(f, "lots of stuff here\n");
    fclose(f);
    write(1, ptr, size); /* for example */
    free(ptr);
    

    open_memstream(3) 至少可以在 Linux 和 macOS 上使用,并且已经有好几年了。 open_memstream(3) 的反面是 fmemopen(3),它使缓冲区的内容可供读取。

    如果您只想要一个 sprintf(3),那么广泛实施但非标准的 asprintf(3) 可能就是您想要的。

    【讨论】:

      【解决方案7】:
      /*  casprintf print to allocated or reallocated string
      
      char *aux = NULL;
      casprintf(&aux,"first line\n");
      casprintf(&aux,"seconde line\n");
      printf(aux);
      free(aux);
      */
      int vcasprintf(char **strp,const char *fmt,va_list ap)
      {
        int ret;
        char *strp1;
        char *result;
        if (*strp==NULL)
           return vasprintf(strp,fmt,ap);
      
        ret=vasprintf(&strp1,fmt,ap); // ret = strlen(strp1) or -1
        if (ret == -1 ) return ret;
        if (ret==0) {free(strp1);return strlen(*strp);}
      
        size_t len = strlen(*strp);
        *strp=realloc(*strp,len + ret +1);
        memcpy((*strp)+len,strp1,ret+1);
        free(strp1);
        return(len+ret);
      }
      
      int casprintf(char **strp, const char *fmt, ...)
      {
       int ret;
       va_list ap;
       va_start(ap,fmt);
       ret =vcasprintf(strp,fmt,ap);
       va_end(ap);
       return(ret);
      }
      

      【讨论】:

      • 欢迎来到 Stack Overflow。虽然您的代码可能会提供问题的答案,但请在其周围添加上下文,以便其他人了解它的作用以及它存在的原因。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 2021-02-15
      相关资源
      最近更新 更多