Можно ли настроить printf?
У меня есть структура, которую мне нужно печатать часто. На данный момент я использую классическую оболочку для печати вокруг этой структуры:
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);
Возможно ли это? Как я могу это сделать?
NB: Я под Ubuntu Linux 10.04, и я использую gcc.
Ответы
Ответ 1
Извините, но некоторые ответы неверны в Linux с помощью Glibc
В Linux с GNU Glibc вы можете настроить printf: вы бы вызвали
register_printf_function
определите значение %Y
в строках формата printf
.
Однако это поведение специфично для Glibc и может даже устареть... Я не уверен, что рекомендую этот подход!
Если кодирование в С++, библиотека потоков С++ имеет манипуляторы, которые вы можете расширить, и вы также можете перегружать для своих типов operator <<
и т.д.
добавлено в феврале 2018 года
Вы можете рассмотреть возможность создания плагина
Ответ 2
Это невозможно в стандартном C. Вы не можете расширить printf
, чтобы добавить строки пользовательского формата. Ваш подход к вспомогательной функции, вероятно, примерно так же хорош, как вы получите в пределах ограничений C.
Ответ 3
К сожалению, это невозможно.
Вероятно, самым простым решением будет принятие небольшой реализации printf
(например, из libc для встроенных систем) и расширения ее в соответствии с вашими задачами.
Ответ 4
Нет, это невозможно. Альтернативой является создание собственной оболочки вокруг printf()
. Он будет анализировать строку формата и конверсии процессов, например, printf()
. Если преобразование является одним из ваших пользовательских преобразований, оно будет печатать все, что вам нужно, а если нет, оно вызовет одну из функций системы *printf()
, чтобы выполнить преобразование для вас.
Обратите внимание, что это нетривиальная задача, и вы должны быть осторожны, чтобы разбирать строку формата точно так же, как printf()
. См. man 3 printf
. Вы можете прочитать список переменных аргументов, используя функции в <stdarg.h>
.
Как только у вас есть такая оболочка, вы можете сделать ее расширяемой, используя указатели на функции (пользовательские преобразования не должны быть жестко закодированы в оболочку).
Ответ 5
Вы можете использовать функцию 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: Строка формата требует трех аргументов, но в этом примере передаются только два.
Ответ 6
Просто оставьте это здесь:
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));
Ответ 7
Предполагая, что вам нужен переносимый код, расширения glibc отсутствуют. Но даже придерживаясь стандартов C99 и POSIX, это очень возможно, я просто написал один.
Вам не нужно повторно реализовывать printf, вам, к сожалению, нужно сделать ваш код достаточно умным для анализа строк формата printf и вывести из них вариационный аргумент C.
Когда переменные аргументы помещаются в стек, информация типа или размера не включена.
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 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 не жалуются.