10

我编写了一个在 linux 上完美运行的 C 程序,但是当我在 windows 上编译它时,它给了我一个错误,说 asprintf() 是未定义的。它应该是 stdio 库的一部分,但似乎许多编译器不包含它。我可以将哪个编译器用于允许我使用 asprintf() 函数的 Windows?我已经尝试了多个编译器,但到目前为止似乎没有一个定义它。

4

8 回答 8

19

asprintf()函数不是 C 语言的一部分,并且并非在所有平台上都可用。Linux 拥有它的事实是不寻常的。

_vscprintf您可以使用和编写自己的_vsprintf_s

int vasprintf(char **strp, const char *fmt, va_list ap) {
    // _vscprintf tells you how big the buffer needs to be
    int len = _vscprintf(fmt, ap);
    if (len == -1) {
        return -1;
    }
    size_t size = (size_t)len + 1;
    char *str = malloc(size);
    if (!str) {
        return -1;
    }
    // _vsprintf_s is the "secure" version of vsprintf
    int r = _vsprintf_s(str, len + 1, fmt, ap);
    if (r == -1) {
        free(str);
        return -1;
    }
    *strp = str;
    return r;
}

这是来自记忆,但它应该非常接近您vasprintf为 Visual Studio 运行时编写的方式。

使用_vscprintf_vsprintf_s是 Microsoft C 运行时独有的奇怪之处,您不会在 Linux 或 OS X 上以这种方式编写代码。_s特别是这些版本虽然是标准化的,但实际上在 Microsoft 生态系统之外并不经常遇到,并且_vscprintf不会甚至在其他地方都不存在。

当然,asprintf这只是一个包装vasprintf

int asprintf(char **strp, const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int r = vasprintf(strp, fmt, ap);
    va_end(ap);
    return r;
}

这不是一种“可移植”的编写方式asprintf,但如果您的唯一目标是支持 Linux + Darwin + Windows,那么这是最好的方式。

于 2016-10-20T16:42:42.913 回答
13

根据7vujy0f0hy 提供的答案,这里是一个为多个平台/编译器(GNU-C 兼容编译器 + MSVC)提供asprintfvasprintfvscprintf的头文件。请注意,由于使用va_copy ,这需要 C99 。请参阅下面的链接以使用在线编译器测试此代码的略微修改版本。

asprintf.h:

#ifndef ASPRINTF_H
#define ASPRINTF_H

#if defined(__GNUC__) && ! defined(_GNU_SOURCE)
#define _GNU_SOURCE /* needed for (v)asprintf, affects '#include <stdio.h>' */
#endif
#include <stdio.h>  /* needed for vsnprintf    */
#include <stdlib.h> /* needed for malloc, free */
#include <stdarg.h> /* needed for va_*         */

/*
 * vscprintf:
 * MSVC implements this as _vscprintf, thus we just 'symlink' it here
 * GNU-C-compatible compilers do not implement this, thus we implement it here
 */
#ifdef _MSC_VER
#define vscprintf _vscprintf
#endif

#ifdef __GNUC__
int vscprintf(const char *format, va_list ap)
{
    va_list ap_copy;
    va_copy(ap_copy, ap);
    int retval = vsnprintf(NULL, 0, format, ap_copy);
    va_end(ap_copy);
    return retval;
}
#endif

/*
 * asprintf, vasprintf:
 * MSVC does not implement these, thus we implement them here
 * GNU-C-compatible compilers implement these with the same names, thus we
 * don't have to do anything
 */
#ifdef _MSC_VER
int vasprintf(char **strp, const char *format, va_list ap)
{
    int len = vscprintf(format, ap);
    if (len == -1)
        return -1;
    char *str = (char*)malloc((size_t) len + 1);
    if (!str)
        return -1;
    int retval = vsnprintf(str, len + 1, format, ap);
    if (retval == -1) {
        free(str);
        return -1;
    }
    *strp = str;
    return retval;
}

int asprintf(char **strp, const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    int retval = vasprintf(strp, format, ap);
    va_end(ap);
    return retval;
}
#endif

#endif // ASPRINTF_H

例子.c:

#include "asprintf.h" /* NOTE: this has to be placed *before* '#include <stdio.h>' */

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

int main(void)
{
    char *str = NULL;
    int len = asprintf(&str, "The answer to %s is %d", "life, the universe and everything", 42);
    if (str != NULL) {
        printf("String: %s\n", str);
        printf("Length: %d\n", len);
        free(str);
    }
    return 0;
}

使用在线编译器进行测试:

rextester C (gcc) | rextester C (clang) | rextester C (msvc)

于 2018-04-17T09:02:28.787 回答
6

多平台实现asprintf()

基于@DietrichEpp@MarcusSun在此线程中的回答以及在另一个线程中为 MacOS/Linux 的协作实现。_vscprintf()在 GCC/Linux、MSVC/Windows、MinGW/Windows(TDM-GCC via Code::Blocks)上测试。希望也可以在Android上运行。

头文件

(大概命名为asprintf.h。)

#include <stdio.h> /* needed for vsnprintf */
#include <stdlib.h> /* needed for malloc-free */
#include <stdarg.h> /* needed for va_list */

#ifndef _vscprintf
/* For some reason, MSVC fails to honour this #ifndef. */
/* Hence function renamed to _vscprintf_so(). */
int _vscprintf_so(const char * format, va_list pargs) {
    int retval;
    va_list argcopy;
    va_copy(argcopy, pargs);
    retval = vsnprintf(NULL, 0, format, argcopy);
    va_end(argcopy);
    return retval;}
#endif // _vscprintf

#ifndef vasprintf
int vasprintf(char **strp, const char *fmt, va_list ap) {
    int len = _vscprintf_so(fmt, ap);
    if (len == -1) return -1;
    char *str = malloc((size_t) len + 1);
    if (!str) return -1;
    int r = vsnprintf(str, len + 1, fmt, ap); /* "secure" version of vsprintf */
    if (r == -1) return free(str), -1;
    *strp = str;
    return r;}
#endif // vasprintf

#ifndef asprintf
int asprintf(char *strp[], const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int r = vasprintf(strp, fmt, ap);
    va_end(ap);
    return r;}
#endif // asprintf

用法

#include <stdio.h> /* needed for puts */
#include <stdlib.h> /* needed for free */
#include "asprintf.h"

int main(void) {
    char *b;
    asprintf(&b, "Mama %s is equal %d.", "John", 58);
    puts(b); /* Expected: "Mama John is equal 58." */
    free(b); /* Important! */
    return 0;
}

活生生的例子: rex ( MSVC · gcc · clang ) | repl.it | tio.run | 键盘| ide1 ( gcc · clang · C99 )

于 2017-09-29T01:15:43.793 回答
3

asprintf()不是 C 标准函数。它是 glibc 提供的 GNU 扩展。因此它可以在 Linux 上运行。但是其他 C 实现可能无法提供它——您的库似乎就是这种情况。

您可以改用标准 C 函数malloc()snprintf().

于 2016-10-20T16:39:30.380 回答
3

GNU 自由

该库在 LGPL 下获得许可,包括asprintf()的实现。

“在项目中使用 libiberty 的最简单方法可能是将 libiberty 代码放入项目的源代码中。”<sup> 1

int asprintf (char **resptr, const char *format, ...)

与 sprintf 类似,但不是将指针传递给缓冲区,而是将指针传递给指针。此函数将计算所需缓冲区的大小,使用 malloc 分配内存,并将指向已分配内存的指针存储在 *resptr 中。返回的值与 sprintf 返回的值相同。如果无法分配内存,则返回减一并将 NULL 存储在 *resptr 中。

于 2018-09-28T11:15:56.160 回答
2

对于那些拥有更高版本的 MSVC 编译器(比如你使用的是 VS2010)或者那些使用C++而不是 C 的人来说,这很容易。您可以va_list在此处的另一个答案中使用该实现。这很棒。

如果您使用的是基于 GCC 的编译器asprintf(如 clang、cygwin、MinGW、TDM-GCC 等) ,我不知道应该已经有一个。如果没有,您可以va_list在此处的另一个答案中使用该实现。

VC6 C 实现(不是 C++)

是的,VC6 太旧了,所有其他答案都不支持 VC6。

(也许也适用于 Turbo C、lcc 和任何旧的)

你不能。你必须:

  1. 自己猜测缓冲区大小。

  2. 制作一个足够大的缓冲区(这并不容易),然后您可以获得正确的缓冲区大小。

如果你选择这个,我已经为 VC6 C 语言做了一个方便的实现,基于va_list另一个答案中的实现。

// #include <stdio.h>  /* for _vsnprintf */
// No, you don't need this
#include <stdlib.h> /* for malloc     */
#include <stdarg.h> /* for va_*       */
#include <string.h> /* for strcpy     */

// Note: If it is not large enough, there will be fine
// Your program will not crash, just your string will be truncated.
#define LARGE_ENOUGH_BUFFER_SIZE 256

int vasprintf(char **strp, const char *format, va_list ap)
{
    char buffer[LARGE_ENOUGH_BUFFER_SIZE] = { 0 }, *s;
        // If you don't initialize it with { 0 } here,
        // the output will not be null-terminated, if
        // the buffer size is not large enough.

    int len,
        retval = _vsnprintf(buffer, LARGE_ENOUGH_BUFFER_SIZE - 1, format, ap);
        // if we pass LARGE_ENOUGH_BUFFER_SIZE instead of
        // LARGE_ENOUGH_BUFFER_SIZE - 1, the buffer may not be
        // null-terminated when the buffer size if not large enough
    
    if ((len = retval) == -1) // buffer not large enough
        len = LARGE_ENOUGH_BUFFER_SIZE - 1;
        // which is equivalent to strlen(buffer)
            
    s = malloc(len + 1);
    
    if (!s)
        return -1;
    
    strcpy(s, buffer);
        // we don't need to use strncpy here,
        // since buffer is guaranteed to be null-terminated
        // by initializing it with { 0 } and pass
        // LARGE_ENOUGH_BUFFER_SIZE - 1 to vsnprintf
        // instead of LARGE_ENOUGH_BUFFER_SIZE
    
    *strp = s;
    return retval;
}

int asprintf(char **strp, const char *format, ...)
{
    va_list ap;
    int retval;
    
    va_start(ap, format);
    retval = vasprintf(strp, format, ap);
    va_end(ap);
    
    return retval;
}

int main(void)
{
    char *s;
    asprintf(&s, "%d", 12345);
    puts(s);

    free(s);
    // note that s is dynamically allocated
    // though modern Windows will free everything for you when you exit
    // you may consider free those spaces no longer in need in real programming
    // or when you're targeting older Windows Versions.

    return 0;
}

如果您想了解更多详细信息,例如为什么我们必须设置足够大的缓冲区大小,请参见下文。

、说明

snprintf在 C99 中进入标准库,在 VC6中不存在。你所拥有的只是一个_snprintf,它:

  1. -1如果要写入的字符数小于或等于count(参数),则返回。所以不能用来获取缓冲区大小。

这似乎没有记录(请参阅Microsoft Docs)。但是_vsnprintf在相同的情况下有特殊的行为,所以我想这里可能有一些东西,通过下面的测试,我发现我的假设是正确的。

是的,它甚至不返回它所写的字符数,比如_vsnprintf. 只是一个-1

  1. 这已记录在案:If buffer is a null pointer and count is nonzero, or if format is a null pointer, the invalid parameter handler is invoked, as described in Parameter Validation. 调用了无效的参数处理程序意味着您将遇到分段错误。

测试代码在这里:

#include <stdio.h>

int main(void)
{
    char s[100], s1[100] = { 0 };

#define TEST(s) printf("%s: %d\n", #s, s)
    
    TEST(_snprintf(NULL, 0, "%d", 12345678));
    /* Tested, and segmentation Fault */
        // TEST(_snprintf(NULL, 100, "%d", 12345678));
    TEST(_snprintf(s, 0, "%d", 12345678));
    TEST(_snprintf(s, 100, "%d", 12345678));
    TEST(_snprintf(s1, 5, "%d", 12345678));
    
    puts(s);
    puts(s1);
    
    return 0;
}

VC6编译器的输出:

_snprintf(NULL, 0, "%d", 12345678): -1
_snprintf(s, 0, "%d", 12345678): -1
_snprintf(s, 100, "%d", 12345678): 8
_snprintf(s1, 5, "%d", 12345678): -1
12345678
12345

这支持了我的假设。

s1我用初始化{0},否则它不会被空终止。_snprintf不这样做,因为count论点太小了。

如果添加 some puts,您会发现第二个 _vsnprintf 返回 -1 并没有写入任何内容,因为我们作为 count 参数s传递。0

请注意,当count传入的参数小于要写入的实际字符串长度时,虽然_snprintf返回 -1,但它实际上会将count字符写入缓冲区。

2.使用vscprintf?没门!

snprintf进入C99标准库,没有snprintf、_vsnprintf和__vscprintf:

asprintf.obj : error LNK2001: unresolved external symbol _vsnprintf
asprintf.obj : error LNK2001: unresolved external symbol __vscprintf

因此,您不能va_list在其中一个答案中使用该实现。

实际上,_vsnprintf在 VC6 中有,见下面的 3.。却_vscprint真的缺席。

3. _vsnprint& _snprintf: 存在但不存在

实际上,_vsnprintf是存在的。如果您尝试调用它,则可以成功。

你可能会说,有一个矛盾,你刚才说unresolved external symbol _vsnprintf。这很奇怪,但这是真的。_vsnprintf如果您直接编写,则inunresolved external symbol _vsnprintf不是您的代码链接到的那个_vsnprintf

同样的事情发生在_snprintf。您可以自己调用它,但是如果您调用snprintf,链接器会抱怨没有_snprintf.

4.像我们在 *nix 中那样通过传递 0 参数作为计数来获取要写入的缓冲区大小?没门!

更糟糕的是,你不能自己写这个:

size_t nbytes = snprintf(NULL, 0, fmt, __VA_ARGS__) + 1; /* +1 for the '\0' */
char *str = malloc(nbytes);
snprintf(str, nbytes, fmt, __VA_ARGS__);

那是因为:

  1. 如上所述,snprintfVC6 中没有。
  2. 如上所述,您可以替换snprintf_snprintf并成功编译它。但是既然你通过了NULL,你就会得到一个分段错误。
  3. 即使由于某种原因您的程序没有崩溃,nbytes也将是-1因为您通过了0. size_t通常unsigned,所以会-1变成一个很大的数字,比如 x86 机器中的 4294967295,你的程序会在下一步停止malloc

5.也许是更好的解决方案

您可以链接一个名为 legacy stdio definitions 或其他东西的库,但我选择自己猜测缓冲区大小,因为在我的情况下这样做并不是很危险。

于 2020-08-08T16:02:02.237 回答
2

此函数位于 glibc 库中,Windows 不支持。

据我所知, asprintf 与 sprintf 类似,其中有缓冲区分配。

在 Windows 中,最简单的方法可能是编写自己的实现。要计算要分配的缓冲区的大小,只需使用以下内容:

int size_needed = snprintf(NULL, 0, "%s\n", "test");

一旦计算出大小,只需分配缓冲区,调用 snprintf 格式化字符串并返回指针。

于 2016-10-20T16:50:27.637 回答
0

LibreSSL 有自己的 BSD 许可实现

https://github.com/libressl-portable/portable/blob/master/crypto/compat/bsd-asprintf.c

@a4cc953哈希:

/*
 * Copyright (c) 2004 Darren Tucker.
 *
 * Based originally on asprintf.c from OpenBSD:
 * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef HAVE_ASPRINTF

#include <errno.h>
#include <limits.h> /* for INT_MAX */
#include <stdarg.h>
#include <stdio.h> /* for vsnprintf */
#include <stdlib.h>

#ifndef VA_COPY
# ifdef HAVE_VA_COPY
#  define VA_COPY(dest, src) va_copy(dest, src)
# else
#  ifdef HAVE___VA_COPY
#   define VA_COPY(dest, src) __va_copy(dest, src)
#  else
#   define VA_COPY(dest, src) (dest) = (src)
#  endif
# endif
#endif

#define INIT_SZ 128

int
vasprintf(char **str, const char *fmt, va_list ap)
{
    int ret;
    va_list ap2;
    char *string, *newstr;
    size_t len;

    if ((string = malloc(INIT_SZ)) == NULL)
        goto fail;

    VA_COPY(ap2, ap);
    ret = vsnprintf(string, INIT_SZ, fmt, ap2);
    va_end(ap2);
    if (ret >= 0 && ret < INIT_SZ) { /* succeeded with initial alloc */
        *str = string;
    } else if (ret == INT_MAX || ret < 0) { /* Bad length */
        free(string);
        goto fail;
    } else {    /* bigger than initial, realloc allowing for nul */
        len = (size_t)ret + 1;
        if ((newstr = realloc(string, len)) == NULL) {
            free(string);
            goto fail;
        }
        VA_COPY(ap2, ap);
        ret = vsnprintf(newstr, len, fmt, ap2);
        va_end(ap2);
        if (ret < 0 || (size_t)ret >= len) { /* failed with realloc'ed string */
            free(newstr);
            goto fail;
        }
        *str = newstr;
    }
    return (ret);

fail:
    *str = NULL;
    errno = ENOMEM;
    return (-1);
}

int asprintf(char **str, const char *fmt, ...)
{
    va_list ap;
    int ret;
    
    *str = NULL;
    va_start(ap, fmt);
    ret = vasprintf(str, fmt, ap);
    va_end(ap);

    return ret;
}
#endif
于 2021-07-22T12:49:14.960 回答