Можно ли настроить printf? - PullRequest
       8

Можно ли настроить printf?

11 голосов
/ 13 февраля 2012

У меня есть структура, которую мне нужно часто печатать.На данный момент я использую классическую оболочку для печати вокруг этой структуры:

void printf_mystruct(struct* my_struct)
{
   if (my_struct==NULL) return;
   printf("[value1:%d value2:%d]", struct->value1, struct->value2);
}

Эта функция удобна, но также действительно ограничена.Я не могу подготовить или добавить текст без создания новой оболочки.Я знаю, что могу использовать va_arg семейство, чтобы иметь возможность предварительно добавить или добавить некоторый текст, но мне кажется, что я бы заново внедрил колесо.

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

register2printf("%mys", &printf_mystruct); 
...
if (incorrect)
  printf("[%l] Struct is incorrect : %mys\n", log_level, my_struct);

Возможно ли это?Как я могу это сделать?

Примечание: я работаю под Ubuntu Linux 10.04 и использую gcc.

Ответы [ 7 ]

12 голосов
/ 13 февраля 2012

Извините, но некоторые ответы неверны в Linux с Glibc

В Linux с GNU Glibc вы можете настроить printf : вы бы позвонили register_printf_function например определите значение %Y в строках формата printf.

Однако, это поведение специфично для Glibc и может даже устареть ... Я не уверен, что рекомендую этот подход!

Если кодируется на C ++, потоковая библиотека C ++ имеет манипуляторы, которые вы можете расширить, и вы также можете перегружать для своих типов operator << и т. Д.

добавлено в феврале 2018

Вы можете написать GCC-плагин , помогающий в этом (и улучшающий проверку типов некоторых расширенных printf). Это будет непросто (вероятно, несколько недель или месяцев работы), и это будет зависеть от версии GCC (не тот же код плагина для GCC 7 и GCC 8). Вы можете добавить какой-то конкретный #pragma, чтобы сообщить плагину о дополнительных спецификаторах управляющей строки, таких как ваш %Y и ожидаемый для них тип. Ваш плагин должен изменить обработку атрибута format (возможно, gcc/tree.c)

3 голосов
/ 13 февраля 2012

Это невозможно в стандартном C. Вы не можете расширить printf, чтобы добавить строки нестандартного формата.Ваш подход вспомогательной функции, вероятно, примерно так же хорош, как вы получите в рамках ограничений C.

1 голос
/ 13 февраля 2012

Вы можете использовать функцию sprintf для получения строкового представления вашей структуры:

char* repr_mystruct(char* buffer, struct* my_struct)
{
    sprintf(buffer, "[string:%s value1:%d value2:%d]", struct->value1, struct->value2);
    return buffer;
}

и последующей печати данных в ваш выходной поток

char buffer[512]; //However large you need it to be
printf("My struct is: %s", repr_mystruct(buffer, &my_struct))

Редактировать: Модифицирована функция, позволяющая передавать буфер (см. Обсуждение ниже).

Примечание 2: Строка формата требует трех аргументов, но в примере передаются только два.

1 голос
/ 13 февраля 2012

Нет, это невозможно.Альтернативой является создание собственной обертки вокруг printf().Он будет анализировать строку формата и обрабатывать преобразования, как printf().Если преобразование является одним из ваших пользовательских преобразований, оно напечатает все, что вам нужно, а если нет, то вызовет одну из системных функций *printf(), чтобы оно выполнило преобразование для вас.

Обратите внимание, что этоэто нетривиальная задача, и вы должны быть осторожны при разборе строки формата точно так же, как printf().Смотри man 3 printf.Вы можете прочитать список аргументов переменных, используя функции в <stdarg.h>.

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

1 голос
/ 13 февраля 2012

К сожалению, это невозможно.

Вероятно, самым простым решением было бы взять небольшую реализацию printf (например, из libc для встраиваемых систем) и расширить ее в соответствии с вашими целями.

0 голосов
/ 19 ноября 2016

Если вы хотите переносимый код, расширения glibc отсутствуют. Но даже при соблюдении стандартов C99 и POSIX это очень возможно, я только что написал один.

Вам не нужно повторно реализовывать printf, вам, к сожалению, нужно сделать свой код достаточно умным, чтобы анализировать строки формата printf и выводить из них типы C аргумента variadic.

Когда в стек помещаются переменные аргументы, информация о типе или размере не включается.

void my_variadic_func(fmt, ...)
{

}

my_variadic_func("%i %s %i", 1, "2", 3);

В приведенном выше примере в 64-битной системе при 48-битной адресации компилятор, вероятно, в конечном итоге выделит 4 байта + 6 байтов + 4 байта = 14 байтов стековой памяти и упакует в нее значения. Я говорю скорее всего, потому что то, как распределяется память и как упаковываются аргументы, зависит от конкретной реализации.

Это означает, что для доступа к значению указателя для %s в приведенной выше строке вам необходимо знать, что первый аргумент был типа int, поэтому вы можете переместить курсор va_list в нужную точку.

Единственный способ получить эту информацию о типе - это просмотреть строку формата и посмотреть, какой тип указан пользователем (в данном случае %i).

Таким образом, чтобы реализовать предложение @ AmbrozBizjak о передаче строк subfmt в printf, вам необходимо проанализировать строку fmt, а после каждого полного, нестандартного спецификатора fmt продвигать va_list на (как бы много ни было байт) fmt тип был.

Когда вы нажимаете пользовательский спецификатор fmt, ваш va_list находится в правильной точке для распаковки аргумента. Затем вы можете использовать va_arg(), чтобы получить свой пользовательский аргумент (передавая правильный тип), и использовать его для запуска любого кода, который вам нужен, для вывода вашего пользовательского спецификатора fmt.

Вы объединяете выходные данные из вашего предыдущего вызова printf и выходные данные вашего пользовательского спецификатора fmt и продолжаете обработку, пока не достигнете конца, и в этот момент вы снова вызываете printf для обработки остальной части строки формата.

Код более сложный (поэтому я включил его ниже), но это дает вам базовое представление о том, что вы должны делать.

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

char *custom_vasprintf(TALLOC_CTX *ctx, char const *fmt, va_list ap)
{
    char const  *p = fmt, *end = p + strlen(fmt), *fmt_p = p, *fmt_q = p;
    char        *out = NULL, *out_tmp;
    va_list     ap_p, ap_q;

    out = talloc_strdup(ctx, "");
    va_copy(ap_p, ap);
    va_copy(ap_q, ap_p);

    do {

        char        *q;
        char        *custom;
        char        len[2] = { '\0', '\0' };
        long        width = 0, group = 0, precision = 0, tmp;

        if ((*p != '%') || (*++p == '%')) {
            fmt_q = p + 1;
            continue;   /* literal char */
        }

        /*
         *  Check for parameter field
         */
        tmp = strtoul(p, &q, 10);
        if ((q != p) && (*q == '$')) {
            group = tmp;
            p = q + 1;
        }

        /*
         *  Check for flags
         */
        do {
            switch (*p) {
            case '-':
                continue;

            case '+':
                continue;

            case ' ':
                continue;

            case '0':
                continue;

            case '#':
                continue;

            default:
                goto done_flags;
            }
        } while (++p < end);
    done_flags:

        /*
         *  Check for width field
         */
        if (*p == '*') {
            width = va_arg(ap_q, int);
            p++;
        } else {
            width = strtoul(p, &q, 10);
            p = q;
        }

        /*
         *  Check for precision field
         */
        if (*p == '.') {
            p++;
            precision = strtoul(p, &q, 10);
            p = q;
        }

        /*
         *  Length modifiers
         */
        switch (*p) {
        case 'h':
        case 'l':
            len[0] = *p++;
            if ((*p == 'h') || (*p == 'l')) len[1] = *p++;
            break;

        case 'L':
        case 'z':
        case 'j':
        case 't':
            len[0] = *p++;
            break;
        }

        /*
         *  Types
         */
        switch (*p) {
        case 'i':                               /* int */
        case 'd':                               /* int */
        case 'u':                               /* unsigned int */
        case 'x':                               /* unsigned int */
        case 'X':                               /* unsigned int */
        case 'o':                               /* unsigned int */
            switch (len[0]) {
            case 'h':
                if (len[1] == 'h') {                    /* char (promoted to int) */
                    (void) va_arg(ap_q, int);
                } else {
                    (void) va_arg(ap_q, int);           /* short (promoted to int) */
                }
                break;

            case 'L':
                if ((*p == 'i') || (*p == 'd')) {
                    if (len [1] == 'L') {
                        (void) va_arg(ap_q, long);      /* long */
                    } else {
                        (void) va_arg(ap_q, long long);     /* long long */
                    }
                } else {
                    if (len [1] == 'L') {
                        (void) va_arg(ap_q, unsigned long); /* unsigned long */
                    } else {
                        (void) va_arg(ap_q, unsigned long long);/* unsigned long long */
                    }
                }
                break;

            case 'z':
                (void) va_arg(ap_q, size_t);                /* size_t */
                break;

            case 'j':
                (void) va_arg(ap_q, intmax_t);              /* intmax_t */
                break;

            case 't':
                (void) va_arg(ap_q, ptrdiff_t);             /* ptrdiff_t */
                break;

            case '\0':  /* no length modifier */
                if ((*p == 'i') || (*p == 'd')) {
                    (void) va_arg(ap_q, int);           /* int */
                } else {
                    (void) va_arg(ap_q, unsigned int);      /* unsigned int */
                }
            }
            break;

        case 'f':                               /* double */
        case 'F':                               /* double */
        case 'e':                               /* double */
        case 'E':                               /* double */
        case 'g':                               /* double */
        case 'G':                               /* double */
        case 'a':                               /* double */
        case 'A':                               /* double */
            switch (len[0]) {
            case 'L':
                (void) va_arg(ap_q, long double);           /* long double */
                break;

            case 'l':   /* does nothing */
            default:    /* no length modifier */
                (void) va_arg(ap_q, double);                /* double */
            }
            break;

        case 's':
            (void) va_arg(ap_q, char *);                    /* char * */
            break;

        case 'c':
            (void) va_arg(ap_q, int);                   /* char (promoted to int) */
            break;

        case 'p':
            (void) va_arg(ap_q, void *);                    /* void * */
            break;

        case 'n':
            (void) va_arg(ap_q, int *);                 /* int * */
            break;

        /*
         *  Custom types
         */
        case 'v':
        {
            value_box_t const *value = va_arg(ap_q, value_box_t const *);

            /*
             *  Allocations that are not part of the output
             *  string need to occur in the NULL ctx so we don't fragment
             *  any pool associated with it.
             */
            custom = value_box_asprint(NULL, value->type, value->datum.enumv, value, '"');
            if (!custom) {
                talloc_free(out);
                return NULL;
            }

        do_splice:
            /*
             *  Pass part of a format string to printf
             */
            if (fmt_q != fmt_p) {
                char *sub_fmt;

                sub_fmt = talloc_strndup(NULL, fmt_p, fmt_q - fmt_p);
                out_tmp = talloc_vasprintf_append_buffer(out, sub_fmt, ap_p);
                talloc_free(sub_fmt);
                if (!out_tmp) {
                oom:
                    fr_strerror_printf("Out of memory");
                    talloc_free(out);
                    talloc_free(custom);
                    va_end(ap_p);
                    va_end(ap_q);
                    return NULL;
                }
                out = out_tmp;

                out_tmp = talloc_strdup_append_buffer(out, custom);
                TALLOC_FREE(custom);
                if (!out_tmp) goto oom;
                out = out_tmp;

                va_end(ap_p);       /* one time use only */
                va_copy(ap_p, ap_q);    /* already advanced to the next argument */
            }

            fmt_p = p + 1;
        }
            break;

        case 'b':
        {
            uint8_t const *bin = va_arg(ap_q, uint8_t *);

            /*
             *  Only automagically figure out the length
             *  if it's not specified.
             *
             *  This allows %b to be used with stack buffers,
             *  so long as the length is specified in the format string.
             */
            if (precision == 0) precision = talloc_array_length(bin);

            custom = talloc_array(NULL, char, (precision * 2) + 1);
            if (!custom) goto oom;
            fr_bin2hex(custom, bin, precision);

            goto do_splice;
        }

        default:
            break;
        }
        fmt_q = p + 1;
    } while (++p < end);

    /*
     *  Print out the rest of the format string.
     */
    if (*fmt_p) {
        out_tmp = talloc_vasprintf_append_buffer(out, fmt_p, ap_p);
        if (!out_tmp) goto oom;
        out = out_tmp;
    }

    va_end(ap_p);
    va_end(ap_q);

    return out;
}

EDIT:

Вероятно, стоит делать то, что делают пользователи Linux, и перегружать% p для создания новых спецификаторов формата, то есть% pA% pB. Это означает, что проверки статического формата printf не жалуются.

0 голосов
/ 26 августа 2015

Просто оставьте это здесь:

printf("%s: pid = %lu, ppid = %lu, pgrp = %lu, tpgrp = %lu\n", name, 
        (unsigned long int)getpid(), (unsigned long int)getppid(), (unsigned long int)getpgrp(), 
        (unsigned long int)tcgetpgrp(STDIN_FILENO));
...