Использование asprintf() в Windows

Я написал программу на C, которая отлично работает на Linux, но когда я компилирую ее на Windows, она выдает ошибку, в которой говорится, что asprintf() не определена. Он должен быть частью библиотеки stdio, но, похоже, многие компиляторы его не включают. Какой компилятор я могу использовать для Windows, который позволит мне использовать функцию asprintf()? Я пробовал несколько компиляторов, и ни один из них, похоже, не определил его до сих пор

8 ответов

asprintf() Функция не является частью языка Си и доступна не на всех платформах. То, что у 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, то это лучший способ сделать это.

Основываясь на ответе 7vujy0f0hy, здесь находится заголовочный файл, содержащий asprintf, vasprintf и vscprintf для нескольких платформ / компиляторов (GNU-C-совместимые компиляторы + MSVC). Обратите внимание, что это требует C99 из-за использования va_copy. Обратитесь по ссылкам ниже, чтобы протестировать слегка модифицированную версию этого кода с помощью онлайн-компиляторов.

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

Тест с использованием онлайн-компиляторов:

ректестер C (gcc) | ректестер С (лязг) | ректестер С (msvc)

Мультиплатформенная реализация asprintf()

На основе ответов @DietrichEpp и @MarcusSun в этой теме и этой совместной реализации _vscprintf() для MacOS/Linux в другой теме. Протестировано на GCC/Linux, MSVC/Windows, MinGW/Windows (TDM-GCC через 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;
}

Живые примеры: рекс ( MSVC · gcc · clang) | repl.it | https://tio.run/ | Кодовая панель | ide1 ( gcc · clang · C99)

GNU libiberty

Эта библиотека, лицензированная по лицензии LGPL, включает в себя реализацию asprintf ().

"Возможно, самый простой способ использовать libiberty в ваших проектах - это поместить код libiberty в исходные тексты вашего проекта". 1

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

Как и sprintf, но вместо передачи указателя на буфер вы передаете указатель на указатель. Эта функция вычислит размер необходимого буфера, выделит память с помощью malloc и сохранит указатель на выделенную память в *resptr. Возвращаемое значение совпадает с возвращаемым sprintf. Если память не может быть выделена, возвращается минус один, и NULL сохраняется в *resptr.

asprintf() не является стандартной функцией C. Это расширение GNU, предоставляемое glibc. Следовательно это работает на Linux. Но другие реализации C могут не обеспечивать это - что, похоже, имеет место с вашей библиотекой.

Вместо этого вы можете переписать свой код, используя стандартные функции C malloc() а также snprintf(),

Для тех, у кого есть более поздняя версия компилятора MSVC (например, вы используете VS2010) или тех, кто использует C++ вместо C, это легко. Вы можете использоватьva_listреализация в другом ответе здесь. Это великолепно.

Если вы используете компилятор, основанный на GCC (например, clang, cygwin, MinGW, TDM-GCC и т. Д.), Он должен бытьasprintfуже не знаю. Если нет, вы можете использоватьva_list реализация в другом ответе здесь.

Реализация VC6 C (не C++)

Да, VC6 слишком старый, и все остальные ответы не поддерживают VC6.

(возможно, для Turbo C, lcc и любых более старых тоже)

Вы не можете. Вы должны:

  1. Угадайте размер буфера самостоятельно.

  2. Сделайте буфер достаточно большого размера (что непросто), тогда вы сможете получить правильный размер буфера.

Если вы выберете это, я сделаю удобную реализацию для языка C VC6 на основе 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;
}

Если вы хотите узнать более подробную информацию, например, почему мы должны установить достаточно большой размер буфера, см. Ниже.

1. Объяснение

snprintfвходит в стандартную библиотеку в C99, и нет в VC6. Все, что у вас есть, это_snprintf, который:

  1. Возврат -1 если количество символов для записи меньше или равно count(Аргумент). Поэтому нельзя использовать для получения размера буфера.

Кажется, это не задокументировано (см. Документацию Microsoft). Но_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 переданный аргумент меньше фактической длины строки для записи, хотя _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 в unresolved external symbol _vsnprintf не тот, на который ссылается ваш код, если вы пишете _vsnprintf прямо.

То же самое происходит на _snprintf. Вы можете назвать это сами, но вы, если вы позвонитеsnprintf, компоновщик будет жаловаться, что нет _snprintf.

4. Получите размер буфера для записи, передав аргумент 0 в качестве count, как в *nix? Ни за что!

Что еще хуже, вы не можете написать это для себя:

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. Как объяснялось выше, нет snprintf в VC6.
  2. Как объяснялось выше, вы можете заменить snprintf с _snprintfи успешно скомпилируйте его. Но поскольку вы прошлиNULL, вы получите ошибку сегментации.
  3. Даже если по какой-то причине ваша программа не вылетела, nbytes будет -1 с тех пор как ты прошел 0. А такжеsize_t обычно unsigned, так -1 станет большим числом, например 4294967295 на машине x86, и ваша программа остановится в следующем malloc шаг.

5. Может быть, лучшее решение

Вы можете связать библиотеку, называемую устаревшими определениями stdio или чем-то еще, но я предпочитаю угадывать размер буфера самостоятельно, поскольку в моем случае это не очень опасно.

Эта функция находится в библиотеке glibc и не поддерживается Windows.

Насколько я знал, asprintf похож на sprintf с выделенным буфером.

В Windows самый простой способ - написать собственную реализацию. Чтобы рассчитать размер буфера, который будет выделен, просто используйте что-то вроде:

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

Как только размер будет вычислен, просто выделите буфер, вызовите snprintf для форматирования строки и возврата указателя.

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
Другие вопросы по тегам