在Windows上使用asprintf()

时间:2016-10-20 16:34:39

标签: c windows asprintf

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

8 个答案:

答案 0 :(得分:11)

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

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

使用_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,那么这是最好的方法。

答案 1 :(得分:3)

asprintf()

的多平台实施

基于此线程中@DietrichEpp@MarcusSun的答案以及另一个帖子中针对MacOS / Linux的_vscprintf() this collaborative implementation的答案。在GCC / Linux,MSVC / Windows,MinGW / Windows(通过Code :: Blocks的TDM-GCC)上测试。希望也可以在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 | Codepad | ide1(gcc·clang·C99

答案 2 :(得分:3)

根据answer provided by 7vujy0f0hy,这是一个提供 asprintf vasprintf 的头文件, vscprintf 适用于多个平台/编译器(GNU-C兼容编译器+ MSVC)。请注意,由于使用 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

<强> example.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)

答案 3 :(得分:1)

asprintf()不是C标准函数。它是由glibc提供的GNU扩展。因此它适用于Linux。但是其他C实现可能无法提供它 - 这似乎是您的库的情况。

您可以使用标准C函数malloc()snprintf()重写代码。

答案 4 :(得分:1)

此功能位于glibc库中,不受Windows支持。

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

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

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

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

答案 5 :(得分:1)

对于具有更高版本的MSVC编译器(例如您正在使用VS2010)的用户或使用 C ++ 而不是C的用户,这很容易。您可以在此处的另一个答案中使用va_list实现。很好。

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

VC6 C实现(不是C ++)

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

(也许适用于Turbo C,lcc和任何较旧的版本)

您不能。您必须:

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

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

如果您选择此选项,那么我将基于另一个答案中的va_list实现对VC6 C语言进行方便的实现。

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

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

1。 说明

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不会这样做。

如果添加一些puts,您会发现第二个_vsnprintf返回-1不会对s写入任何内容,因为我们将0作为count参数传递了。

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

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实现。

实际上,VC6中有_vsnprintf,请参见下面的3.。但是_vscprint 真的不存在。

3。 _vsnprint_snprintf:存在但不存在

实际上,存在_vsnprintf。如果您尝试调用它,就可以做到。

您可能说,有一个矛盾,您刚刚说过unresolved external symbol _vsnprintf。这很奇怪,但这是事实。如果您直接编写_vsnprintf,则unresolved 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. 如上所述,VC6中没有snprintf
  2. 如上所述,您可以将snprintf替换为_snprintf并成功进行编译。但是,由于您通过了NULL,因此会遇到细分错误。
  3. 即使由于某种原因您的程序没有崩溃,自您通过nbytes以来,-1也将是0。并且size_t通常是unsigned,因此-1会变得很大,就像x86机器中的4294967295一样,您的程序将在下一步malloc中停止。 li>

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

您可以链接一个称为传统stdio定义或其他名称的库,但是我选择自己猜测缓冲区的大小,因为在我看来,这样做并不是很危险。

答案 6 :(得分:0)

GNU自由

根据LGPL许可,该库包含 asprintf()的实现。

“在项目中使用自由的最简单方法是将自由代码放到项目的源代码中。” 1

  

int asprintf(字符** resptr,常量字符*格式,...)

     

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

答案 7 :(得分: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