создать my_printf, который отправляет данные как в sprintf, так и в обычный printf? - PullRequest
3 голосов
/ 31 января 2009

Я играю с принтом и идеей написать my_printf (...), который вызывает обычный printf и sprintf, который отправляет результат в специальную функцию. (Я думал о sprintf, поскольку на большинстве платформ он работает так же, как printf).

Моя идея заключалась в том, чтобы написать небольшой макрос, который сделал это:

#define my_printf(X, Y...) do{ printf(X, ## Y); \
    char* data = malloc(strlen(X)*sizeof(char)); \
    sprintf(data, X, ## Y); \
    other_print(data);\
    free(data);}while(0)

Но поскольку sprintf может расширять строку до гораздо большего размера, чем X, этот метод ломается почти напрямую.

И если просто добавить число, то malloc кажется неправильным способом решения проблемы, с тех пор я просто перенесу проблему в будущее и в тот день, когда я захочу напечатать большое выражение ...

Кто-нибудь имеет лучшее представление о том, как решить эту проблему? Или как я узнаю, насколько большим будет результат sprintf?

Спасибо Johan


Обновление: Я забыл, что printf возвращает количество напечатанных символов, и так как я уже вызываю printf в макросе, было очень легко добавить int, который сохраняет номер.

#define buf_printf(X, Y...) do{ int len = printf(X, ## Y); \
    char* data = malloc((len+1)*sizeof(char)); \
    sprintf(data, X, ## Y); \
    other_print(data);\
    free(data);}while(0)

Обновление: Я думал об этом и, возможно, использовать обычную функцию это очень похоже на то, что предложил эфимент, это хорошая идея. Ключ, кажется, v-версия различных функций printf (vprintf, vsprintf и vsnprintf). Спасибо за указание на это.

Еще раз спасибо Johan

Ответы [ 4 ]

8 голосов
/ 31 января 2009

Используйте snprintf для расчета размера. Со страницы руководства:

"Если выходные данные были усечены из-за этого ограничения, тогда возвращаемое значение - это количество символов (не включая завершающий '\ 0'), которое было бы записано в последнюю строку, если бы было достаточно места"

snprintf является стандартным от C99. Если у вас есть только компилятор C89, проверьте документацию: стандартные версии могут не возвращать нужное вам значение. Опять же, согласно man-странице, glibc до версии 2.1 возвращал -1, если вывод был усечен, а не требуемого размера.

Кстати, sizeof (char) определяется как 1 всегда в каждой реализации C: -)

5 голосов
/ 01 февраля 2009

Поскольку вы работаете в Linux, я бы предложил использовать asprintf() - это расширение GNU, которое выделяет вам строку. А благодаря переменным макросам C99 вам не нужно связываться с varagrs.

Итак, ваш макрос будет выглядеть так:

#define MY_PRINT(...) do { \
                          char *data; \
                          asprintf(&data, __VA_ARGS__); \
                          printf("%s", data); \
                          other_print(data); \
                          free(data); \
                      } while (0)

NB! Это код C99 и только для GNU

Edit: Теперь он будет оценивать аргументы макроса только один раз, поэтому вызов макроса с чем-то вроде ("% d", i ++) будет работать правильно.

5 голосов
/ 31 января 2009

Лучший способ сделать это с помощью varargs. Создайте функцию с тем же прототипом, что и printf (), и используйте функции varargs для передачи данных в sprintf для заполнения нужного буфера, а также передайте этот буфер в printf ("% s") перед возвратом.

Многие ранние реализации имели ограничение в 4 КБ для вызова самого низкого уровня printf (), но я бы предпочел больше. Вам, вероятно, нужно просто установить верхний предел и придерживаться его.

Один прием, который мы использовали в системе ведения журналов, заключался в записи данных с использованием printf () в дескриптор / dev / null. Поскольку printf () возвращает количество написанных символов, мы затем использовали это для выделения буфера. Но это было не очень эффективно, так как требовало вызова функции типа printf () дважды.

2 голосов
/ 01 февраля 2009

Если вы всегда работаете в системе с glibc (т. Е. Linux и любой другой ОС с пользовательским пространством GNU), asprintf действует так же, как sprintf, но может автоматически обрабатывать выделение.

int my_printf(const char *fmt, ...) {
    char *buf = NULL;
    int len;
    va_list ap;

    va_start(ap, &fmt);
    len = vasprintf(&buf, fmt, ap);
    va_end(ap);

    if (len < 0) {
        /* error: allocation failed */
        return len;
    }

    puts(buf);
    other_print(buf);

    free(buf);
    return len;
}

Ответ Pax более переносим, ​​но вместо печати на /dev/null, вот лучший трюк: POSIX может дать snprintf буфер NULL и размер 0, и он вернет, сколько это написало бы - но очевидно, что на самом деле ничего не напишет.

int my_printf(const char *fmt, ...) {
    char *buf;
    int len, len2;
    va_list ap;

    va_start(ap, &fmt);
    len = vsnprintf(NULL, 0, fmt, ap);
    va_end(ap);

    buf = malloc(len + 1);
    if (!buf) {
        /* error: allocation failed */
        return -1;
    }

    va_start(ap, &fmt);
    len2 = snprintf(buf, len + 1, fmt, ap);
    buf[len] = '\0';
    va_end(ap);

    /* has another thread been messing with our arguments?
       oh well, nothing we can do about it */
    assert(len == len2);

    puts(buf);
    other_printf(buf);

    free(buf);
    return len;
}

Ну, как говорится, некоторые старые системы не имеют совместимости snprintf. Не могу победить их всех ...

...