Использование 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;
}
Тест с использованием онлайн-компиляторов:
Мультиплатформенная реализация 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 и любых более старых тоже)
Вы не можете. Вы должны:
Угадайте размер буфера самостоятельно.
Сделайте буфер достаточно большого размера (что непросто), тогда вы сможете получить правильный размер буфера.
Если вы выберете это, я сделаю удобную реализацию для языка 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
если количество символов для записи меньше или равноcount
(Аргумент). Поэтому нельзя использовать для получения размера буфера.
Кажется, это не задокументировано (см. Документацию Microsoft). Но_vsnprintf
имеет особое поведение в той же ситуации, поэтому я предполагаю, что здесь что-то есть, и с помощью теста ниже я нашел свое предположение правильным.
Да, он даже не возвращает количество написанных символов, например _vsnprintf
. Просто-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__);
Это потому что:
- Как объяснялось выше, нет
snprintf
в VC6. - Как объяснялось выше, вы можете заменить
snprintf
с_snprintf
и успешно скомпилируйте его. Но поскольку вы прошлиNULL
, вы получите ошибку сегментации. - Даже если по какой-то причине ваша программа не вылетела,
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