Передайте указатель void на printf, и его тип будет определяться строкой формата - PullRequest
1 голос
/ 14 июля 2020

Я собираюсь упростить свою ситуацию, чтобы сосредоточиться на реальной проблеме.

Допустим, я пишу функцию обложки для printf под названием print_data . Пользователь вызывает print_data и передает одну строку формата, например "% .1f" вместе с void *, представляющими данные:

void print_data(const char* format, void* data);

Моя задача - принять эти аргументы и каким-то образом передать их на printf.

Моя проблема в том, что printf ожидает значение , а не указатель (кроме строк). У меня нет способа определить тип данных, которые передал пользователь, за исключением того, что я вручную сам прочитал строку формата и соответствующим образом преобразовал данные (например, если было передано «f», преобразовать в float).

A Решением "magi c" может быть возможность каким-то образом отменить ссылку на void *, но это, конечно, невозможно.

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

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

Есть какие-нибудь мысли о том, как я могу выполнить sh это?

Ответы [ 4 ]

4 голосов
/ 14 июля 2020

Вы должны проанализировать строку формата в своей функции и вызвать printf с соответствующим типом значения. Чтобы прочитать значение, вы можете привести указатель void к соответствующему типу, определенному спецификатором преобразования.

Вот быстрый пример:

#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>

#define printf printf__
int printf(const char *, ...);

int print_data(const char *format, void *data) {
    const char *p = format;
    enum {
        FMT_none = 0,
        FMT_c   = 1,
        FMT_i   = 2,
        FMT_u   = 3,
        FMT_f   = 4,
        FMT_pc  = 5,
        FMT_pv  = 6,
        PREF_l  = (1 << 3),
        PREF_ll = (1 << 4),
        PREF_h  = (1 << 5),
        PREF_hh = (1 << 6),
        PREF_j  = (1 << 7),
        PREF_z  = (1 << 8),
        PREF_t  = (1 << 9),
        PREF_L  = (1 << 10),
    };
    int fmt = FMT_none;
    
    for (;;) {
        int cur_fmt = FMT_none;
        int prefix = 0;
        p = strchr(p, '%');
        if (!p)
            break;
        p++;  // skip the '%'
        // skip the flag characters, width and precision
        // note that invalid combinations will not be detected
        // such as %..d or %.+d
        p += strspn(p, " -#+0123456789.");
        // parse the length modifier if present
        switch (*p) {
        case 'l':
            p++;
            prefix = PREF_l;
            if (*p == 'l') {
                p++;
                prefix = PREF_ll;
            }
            break;
        case 'h':
            p++;
            prefix = PREF_h;
            if (*p == 'h') {
                p++;
                prefix = PREF_hh;
            }
            break;
        case 'j':
            p++;
            prefix = PREF_j;
            break;
        case 'z':
            p++;
            prefix = PREF_z;
            break;
        case 't':
            p++;
            prefix = PREF_t;
            break;
        case 'L':
            p++;
            prefix = PREF_L;
            break;
        }
        switch (*p++) {
        case '%':
            if (p[-2] != '%')
                return -1;
            continue;
        case 'c':
            cur_fmt = FMT_c;
            break;
        case 'd':
        case 'i':
            cur_fmt = FMT_i;
            break;
        case 'o':
        case 'u':
        case 'x': case 'X':
            cur_fmt = FMT_u;
            break;
        case 'a': case 'A':
        case 'e': case 'E':
        case 'f': case 'F':
        case 'g': case 'G':
            cur_fmt = FMT_f;
            break;
        case 's':
            cur_fmt = FMT_pc;
            break;
        case 'p':
            cur_fmt = FMT_pv;
            break;
        default:
            return -1;
        }
        if (fmt != FMT_none)
            return -1; // more than one format
        fmt = cur_fmt | prefix;
    }
    switch (fmt) {
    case FMT_none:
        return printf(format);
    case FMT_c:
        return printf(format, *(char *)data);
    case FMT_c | PREF_l:
        // the (wint_t) cast is redundant, omitted
        return printf(format, *(wchar_t *)data);
    case FMT_i:
        return printf(format, *(int *)data);
    case FMT_i | PREF_l:
        return printf(format, *(long *)data);
    case FMT_i | PREF_ll:
        return printf(format, *(long long *)data);
    case FMT_i | PREF_h:
        return printf(format, *(short *)data);
    case FMT_i | PREF_hh:
        return printf(format, *(signed char *)data);
    case FMT_i | PREF_j:
        return printf(format, *(intmax_t *)data);
    case FMT_i | PREF_z:
    case FMT_u | PREF_z:
        return printf(format, *(size_t *)data);
    case FMT_i | PREF_t:
    case FMT_u | PREF_t:
        return printf(format, *(ptrdiff_t *)data);
    case FMT_u:
        return printf(format, *(unsigned *)data);
    case FMT_u | PREF_l:
        return printf(format, *(unsigned long *)data);
    case FMT_u | PREF_ll:
        return printf(format, *(unsigned long long *)data);
    case FMT_u | PREF_h:
        return printf(format, *(unsigned short *)data);
    case FMT_u | PREF_hh:
        return printf(format, *(unsigned char *)data);
    case FMT_u | PREF_j:
        return printf(format, *(uintmax_t *)data);
    case FMT_f:
        // the cast (double) is redundant, but useful to prevent warnings
        return printf(format, (double)*(float *)data);
    case FMT_f | PREF_l:
        return printf(format, *(double *)data);
    case FMT_f | PREF_L:
        return printf(format, *(long double *)data);
    case FMT_pc:
        return printf(format, *(char **)data);
    case FMT_pc | PREF_l:
        return printf(format, *(wchar_t **)data);
    case FMT_pv:
        return printf(format, *(void **)data);
    default:
        return -1;
    }
}

Примечания:

  • форматы с плавающей запятой ведут себя как scanf(): используйте %f, если data указывает на float и %lf, если он указывает на double. l будет игнорироваться printf, поскольку значения float преобразуются в double при передаче в функции vararg.

  • эта функция ожидает указатель на char для формат %c, хотя printf ожидает int, который будет преобразован в unsigned char.

  • эта функция ожидает указатель на wchar_t для формата %lc хотя printf ожидает wint_t.

  • спецификаторы преобразования %zd и %tu, разрешенные стандартом C, но соответствующие типы не определены стандартом. Передача типа с другой подписью не является строго правильной для отрицательных значений, но вряд ли создаст проблему.

2 голосов
/ 14 июля 2020

Есть какие-нибудь мысли о том, как я мог бы sh это сделать?

Анализировать формат - и есть много возможностей, учитывая, что формат не только имеет спецификатор "acdefginopsuxAEFGX%" но модификаторы "hlhhlljztL". Это быстро дает десятки, если не 100+ допустимых необработанных комбинаций.

Существует упрощение: спецификаторы "aefgAEFG" все имеют double или больше. "uoxX" - это unsigned или шире. Я думаю, что тогда количество допустимых комбинаций спецификатор / модификатор сводится к (2 (fp) +8 (i) +8 (u) +2 (c) +2 (s) +1 ( 1)).

Спецификатор "%n" может потребоваться здесь запретить - это не имеет особого смысла.

Пример синтаксического анализа формата: Как проверить, что две строки формата совместим?

Неясно, что OP хочет делать с шириной и точностью , как в "%*.*Lf", для которого требуется более 1 аргумента.

Альтернатива написанию функции обложки для printf путем передачи формата и void * будет печатать без формата. Просто объект макроса и _Generic для управления функцией печати. Изучено в Форматированная печать без необходимости указывать спецификаторы соответствия типов с помощью _Generi c.

0 голосов
/ 14 июля 2020

В итоге я использовал свободную от зависимостей библиотеку printf, доступную по адресу https://github.com/mpaland/printf.

После загрузки это было в основном так же просто, как изменить все экземпляры va_arg, которые принимали например, "int", чтобы вместо этого принять указатель этого типа, а затем разыменовать это значение.

Например,

... = (int)va_arg(va, int)

было изменено на

... = (int)*va_arg(va, int*)

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

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

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

0 голосов
/ 14 июля 2020

Как указано в ответах на связанный вопрос, вам придется написать все разные приведения. В вашем случае это выполнимо, поскольку есть только один void*, поэтому набор приведений конечен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...