Замена или временное решение для asprintf в AIX

Я пытаюсь собрать python-kerberos на AIX. kerberospw.c использует вызов asprintf, но, по словам Google, asprintf не существует в AIX.

Я видел http://www.koders.com/c/fidAA9B130D588302673A28B568430A83131B7734C0.aspx?s=windows.h, похоже, я мог бы создать замещающий asprintf, но я не знаю, куда это пойдёт и как включить #include в kerberospw.c.

Есть ли способ использовать пример koders.com или какой-либо другой код для «подделки» asprintf? Могу ли я просто включить функцию asprintf, как показано в kerberospw.c? Я не C-кодер, но

asprintf (char **resultp, const char *format, ...)

не выглядит для меня действительной подписью с точками в конце. Соответствующая строка из kerberospw.c приведена ниже

asprintf(&message, "%.*s: %.*s", (int) result_code_string.length,
(char *) result_code_string.data,
(int) result_string.length,
(char *) ) строка_результата.данные);

Я понимаю, что мог бы связаться с автором python-kerberos, но а) я думаю, что было бы полезно иметь потенциальный патч, если бы я это сделал, и б) может быть другое программное обеспечение, с которым я сталкиваюсь, которое использует asprintf, и это было бы приятно иметь обходной путь.


person bobwood    schedule 04.02.2011    source источник
comment
Обратите внимание, что эта справочная страница для asprintf() говорит, что возвращаемое значение в *resultp неопределенно в случае ошибки. Другие справочные страницы, такие как та, на которую ссылается вопрос, указывают, что при ошибке явно установлено значение NULL. При использовании asprintf() не думайте, что указатель инициализируется в случае сбоя функции. При реализации asprintf() убедитесь, что указатель установлен в нуль при ошибке, чтобы обеспечить детерминированное поведение.   -  person Jonathan Leffler    schedule 04.02.2011


Ответы (4)


asprintf — это вариант семейства функций printf, которые выделяют буфер для хранения отформатированной строки и возвращают ее. Это функция с переменным числом аргументов (отсюда и ... в объявлении, что является допустимым кодом C). Описание можно найти здесь.

Его можно относительно легко переопределить, если vsnprintf работает правильно (т. е. возвращает ошибку, если буфер слишком мал для хранения отформатированной строки).

Вот такая реализация:

#include <stdarg.h>

int asprintf(char **ret, const char *format, ...)
{
    va_list ap;

    *ret = NULL;  /* Ensure value can be passed to free() */

    va_start(ap, format);
    int count = vsnprintf(NULL, 0, format, ap);
    va_end(ap);

    if (count >= 0)
    {
        char* buffer = malloc(count + 1);
        if (buffer == NULL)
            return -1;

        va_start(ap, format);
        count = vsnprintf(buffer, count + 1, format, ap);
        va_end(ap);

        if (count < 0)
        {
            free(buffer);
            return count;
        }
        *ret = buffer;
    }

    return count;
}
person Sylvain Defresne    schedule 04.02.2011
comment
Вы должны сделать va_end(ap); после первого вызова vsnprintf() и еще один вызов va_start(ap, format); перед вторым вызовом. Вам также необходимо #include <stdlib.h>, конечно, и #include <stdio.h> для получения декларации vsnprintf(). - person Jonathan Leffler; 04.02.2011
comment
Ах, спасибо. Я всегда забывал это сделать. Я обновлю свой пост. - person Sylvain Defresne; 04.02.2011
comment
Кроме того, buffer выходит за рамки строки *ret = buffer;. - person Jonathan Leffler; 04.02.2011
comment
Спасибо Сильвен. Я тестировал, и это работало до тех пор, пока код не скомпилировался, а тестовые примеры, отправленные с кодом Python, успешно прошли тестирование. - person bobwood; 04.02.2011
comment
Для тех, кто хочет реализовать asprintf в Windows, вы не можете использовать vsnprintf в том виде, в каком он используется здесь, поскольку Windows обрабатывает ввод буфера NULL как ошибку. Вы можете заменить ее функцией _vscprintf, которая похожа на vprintf, за исключением того, что она ничего не печатает — только возвращает количество символов, что нам и нужно. - person Tyler; 09.05.2014

Опираясь на Sylvain ответ, вот простая реализация как с asprintf(), так и с vasprintf(), потому что там, где вам нужен один, вам обычно нужен и другой. И, учитывая макрос va_copy() из C99, легко реализовать asprintf() в терминах vasprintf(). Действительно, при написании функций с переменным числом аргументов очень часто полезно иметь их парами, одну с многоточием, а другую с аргументом va_list вместо многоточия, и вы тривиально реализуете первое в терминах второго.

Это приводит к коду:

int vasprintf(char **ret, const char *format, va_list args)
{
    va_list copy;
    va_copy(copy, args);

    /* Make sure it is determinate, despite manuals indicating otherwise */
    *ret = NULL;

    int count = vsnprintf(NULL, 0, format, args);
    if (count >= 0)
    {
        char *buffer = malloc(count + 1);
        if (buffer == NULL)
            count = -1;
        else if ((count = vsnprintf(buffer, count + 1, format, copy)) < 0)
            free(buffer);
        else
            *ret = buffer;
    }
    va_end(copy);  // Each va_start() or va_copy() needs a va_end()

    return count;
}

int asprintf(char **ret, const char *format, ...)
{
    va_list args;
    va_start(args, format);
    int count = vasprintf(ret, format, args);
    va_end(args);
    return(count);
}

Сложная часть использования этих функций в системе, где они не предусмотрены, заключается в том, чтобы решить, где должны быть объявлены функции. В идеале они должны быть в <stdio.h>, но тогда вам не нужно будет их записывать. Итак, у вас должен быть какой-то другой заголовок, который включает <stdio.h>, но объявляет эти функции, если они не объявлены в <stdio.h>. И, в идеале, код должен определять это полуавтоматически. Возможно, заголовок "missing.h" и содержит (частично):

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdarg.h>

#ifndef HAVE_ASPRINTF
extern int asprintf(char **ret, const char *format, ...);
extern int vasprintf(char **ret, const char *format, va_list args);
#endif /* HAVE_ASPRINTF */

Также обратите внимание, что на этой справочной странице для asprintf() говорится, что возвращаемое значение в указатель не определен в случае ошибки. Другие справочные страницы, включая ту, на которую есть ссылка в вопросе, указывают, что она явно устанавливается в NULL при ошибке. Документ комитета по стандарту C (n1337.pdf) не определяет поведение ошибки при нехватке памяти.

  • При использовании asprintf() не думайте, что указатель инициализируется в случае сбоя функции.
  • При реализации asprintf() убедитесь, что указатель установлен в значение null при ошибке, чтобы обеспечить детерминированное поведение.
person Jonathan Leffler    schedule 04.02.2011
comment
Спасибо Джонатан. Вы правы, что AIX не имеет vasprint и при новой загрузке. Я также проверил это, и, похоже, он отлично скомпилировался. Я бы проголосовал и за это, и за ответы Сильвена, но на самом деле у меня недостаточно представителей, потому что я не очень хороший участник SO. - person bobwood; 04.02.2011
comment
@bobwood: вы должны принять - и приняли - ответ Сильвена; на мой взгляд, это совершенно правильно. Он выполнял ослиную работу (с небольшой помощью); мой ответ строго производный. Насколько я знаю, вы всегда можете проголосовать за ответы - и проголосовать за более чем один ответ, если вы чувствуете, что более одного ответа полезны. Принятый ответ предназначен для «наиболее полезного» ответа и дает отвечающему (и вам, как спрашивающему) дополнительный бонус. (Один черновик моего ответа начинался с «Не голосовать за это». Я пожадничал и удалил его.) - person Jonathan Leffler; 04.02.2011
comment
Полученная репутация и положительные голоса, полученные за ответы на этот вопрос. - person bobwood; 07.02.2011
comment
При ошибке выделения вы возвращаете длину отформатированной строки, а указатель ret указывает на не равный NULL. Это означает, что случай неудачного выделения неотличим от успешного случая, если вы ссылаетесь на неинициализированный указатель (что довольно часто встречается при вызовах asprintf()). - person Patrick Schlüter; 30.09.2014
comment
Незначительный вопрос: зачем использовать *ret = 0; и buffer != NULL, а не 0 или NULL для обоих? - person chux - Reinstate Monica; 30.09.2014
comment
Извините, я не видел *ret = 0 (может быть, с NULL я бы его увидел ;-), как предложил Чукс), поэтому в случае неудачного выделения есть определенное поведение. Однако это не меняет ошибки, так как во всей документации asprintf(), которую я видел (и без исключения с вашими двумя ссылками), указано, что в этом случае должно быть возвращено -1 и что значение указателя не определено. - person Patrick Schlüter; 30.09.2014
comment
@chux: это демонстрирует, что (а) я человек и (б) у меня не было никого вне SO для проверки кода. Кроме того, учитывая, что ответу 3,5 года, это не имеет решающего значения. Я отмечаю, что версия спецификации комитета C (N1337) не упоминает поведение при ошибке. Однако легко (тривиально) исправить код, чтобы он соответствовал «возврату -1 при ошибке выделения памяти» на справочных страницах. - person Jonathan Leffler; 30.09.2014
comment
@tristopia: спасибо, что указали на ошибку; Я исправил это в коде. - person Jonathan Leffler; 30.09.2014
comment
@Jonathan Leffler Мой комментарий о NULL/0 совпадает с тристопией и не имеет ничего общего с проблемой возвращаемого значения -1. Я согласен, что мой комментарий не имеет решающего значения, поэтому мой префикс второстепенного вопроса. Я уважаю ваши многочисленные прекрасные ответы, и мне просто было любопытно узнать разницу. Вероятно, это была либо тривиальная несогласованность, либо, возможно, какой-то проницательный аспект, который пытался выразить ваш код. - person chux - Reinstate Monica; 30.09.2014

Я пришел сюда в поисках быстрой реализации для Windows и Linux, которая устанавливает указатель возврата на NULL в случае сбоя.

Ответ Джонатана Леффлера выглядел лучше, но потом я заметил, что он не устанавливает -1, когда malloc терпит неудачу.

Я провел дополнительные поиски и наткнулся на это обсуждение реализации asprintf, который затем просветил меня, что Джонатан и Сильвен тоже неправильно обрабатывали переполнение.

Теперь я рекомендую это решение, предоставленное с вышеупомянутым обсуждением, которое, похоже, охватывает все важные платформы и, по-видимому, обрабатывает все сценарий отказа правильно.

person Joe    schedule 03.07.2014
comment
Хотя я согласен с этими пунктами, этот ответ является комментарием к различным ответам. Это не ответ. В лучшем случае это ответ только по ссылке. - person chux - Reinstate Monica; 30.09.2014

Здесь реализация, которая в большинстве случаев не вызывает snprintf() дважды. Я пропустил включения и определения, как показано в других ответах.

Как и должно быть, определите asprintf() как вызов vasprintf()

int asprintf(char **dst, const char * pcFormat, ...)
{
va_list ap;

  va_start(ap, pcFormat);
  int len = vasprintf(dst, pcFormat, ap);
  va_end(ap);
  return len;
}

Мы заранее выделяем буфер до заранее определенного соответствующего размера и только в случае переполнения вызываем vsnprintf() второй раз. Причина в том, что функция s*printf() считается очень тяжелой, а перераспределение памяти допустимо.

int vasprintf(char **dst, const char * pcFormat, va_list ap)
{
  int len = 512;      /* Worked quite well on our project */
  int allocated = 0;
  va_list ap_copy;
  char *buff = NULL;

  while(len >= allocated) {
    free(buff);
    buff = malloc(len+1);
    if(buff) {
      allocated = len+1;
      va_copy(ap_copy, ap);
      len = vsnprintf(buff, len+1, pcFormat, ap_copy);
      va_end(ap_copy);
    }
    else   /* malloc() failed */
      return -1;
  }
  *dst = buff;
  return len;
}

EDIT: я заменил вызов realloc() простым malloc(), так как он дешевле. В случае переполнения пара free()/malloc() стоит меньше, чем realloc() из-за ее внутреннего скрытого memcpy(). Поскольку мы все равно перезаписываем весь буфер с последующим вызовом vsnprintf(), в этой копии нет смысла.

person Patrick Schlüter    schedule 30.09.2014