sprintf() 具有自动内存分配功能?

问题描述 投票:0回答:8

我正在寻找一个类似

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;
}

但是,当调用带有空指针的

sprintf()
时,这会立即导致段错误。

有什么想法、解决方案或技巧吗?放置在公共领域的类似

sprintf()
的解析器的小型实现就已经足够了,然后我可以自己完成它。

非常感谢!

c malloc printf
8个回答
54
投票

这是原始答案来自 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;
}

39
投票

GNU 和 BSD 有 asprintf

vasprintf
旨在为您做到这一点。它将弄清楚如何为您分配内存,并在任何内存分配错误时返回 null。

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

代码如下所示:

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

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

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

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

    return 0;
}

20
投票

如果您可以接受 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;
}

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


11
投票
  1. 如果可能,请使用
    snprintf
    ——它提供了一种简单的方法来测量将生成的数据的大小,以便您可以分配空间。
  2. 如果您真的无法做到这一点,另一种可能性是使用
    fprintf
    打印到临时文件以获取大小,分配内存,然后使用sprintf。
    snprintf
    绝对是首选方法。

7
投票

GLib 库提供了

g_strdup_printf
函数,如果可以选择链接到 GLib,它可以完全满足您的需求。 来自文档:

与标准C类似

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


3
投票

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) 可能就是您想要的。


0
投票
/*  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);
}

0
投票

我的版本 (v3) https://stackoverflow.com/a/10388547/666907 – 更通用一点 [注意我是 C++ 菜鸟,使用风险自担 :P]:

#include <cstdarg>

char* myFormat(const char* const format...) {
  // `vsnprintf()` changes `va_list`'s state, so using it after that is UB.
  // We need the args twice, so it is safer to just get two copies.
  va_list args1;
  va_list args2;
  va_start(args1, format);
  va_start(args2, format);

  size_t needed = 1 + vsnprintf(nullptr, 0, format, args1);

  // they say to cast in C++, so I cast…
  // https://stackoverflow.com/a/5099675/666907
  char* buffer = (char*) malloc(needed);

  vsnprintf(buffer, needed, format, args2);

  va_end(args1);
  va_end(args2);

  return buffer;
}

char* formatted = myFormat("Foo %s: %d", "bar", 456);
Serial.println(formatted); // Foo bar: 456

free(formatted); // remember to free or u will have a memory leak!
© www.soinside.com 2019 - 2024. All rights reserved.