Умный print для C

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Пример использования:

#include "print.h"

int main() {
  print("number:", 25,
    "fractional number:", 1.2345,
    "expression:", (2.0 + 5) / 3
  );
}
number: 25 fractional number: 1.2345 expression: 2.33333

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

Дженерик print, может выводить все основные типы языка Си, целые, со знаком и без, с плавающей точкой и указатели:

char *s = "abc";
void *p = main;
long l = 1234567890123456789;
unsigned char byte = 222;
char ch = 'A';
print("string:", s, "pointer:", p, "long:", l);
print(byte, ch)
string: "abc" pointer: 0x402330 long: 1234567890123456789
222<0xDE> 'A'65

Разные типы отображаются разным цветом, палитру можно настроить, либо вообще отключить цвет.

Можно даже печатать массивы:

int x[] = { 1, 2, 3 };
char *args[] = { "gcc", "hello.c", "-o", "hello" };
print(x, args);
[1 2 3] ["gcc" "hello.c" "-o" "hello"]

Как это работает? На самом деле print это макрос, точнее говоря variadic macro, который генерирует вызов настоящей функции. Первый параметр, который макрос конструирует для этой функции, это количество аргументов, введённых пользователем. Для этого используется известный трюк:

void __print_func(int count, ...);
#define count_arg(q,w,e,r,t,y,...) y
#define print(a...) __print_func(count_arg(a,5,4,3,2,1,0), a);

Элегантностью такое решение не блещет, спасибо ограничениям препроцессора, как видите, максимальное количество аргументов в этом примере 6, (в моей библиотеке сейчас 26 ).

Второй параметр spread operator ... , это сам список всех аргументов. В функции __print_func()используется обычный stdarg.h для обхода этого списка:

void prn(int count, ...) {
  va_list v;
  va_start(v, types);
  for (int i = 0; i < count; i++) {
    ...
    printf("%'li", va_arg(v, unsigned long));
    ...
  }
  va_end(v);
}

Теперь, сложный вопрос: как узнать типы? Ведь va_argне волшебник, мы ему джолжны указать тип для каждого аргумента. В примере выше -- это unsigned long, но, что на самом деле пользователь передаст, мы ещё не знаем.

Большинство компиляторов Си понимает такую вещь:

int x;
int y = __builtin_types_compatible_p(typeof(x), int);

Это конструкция времени компиляции, принимает типы, а возвращает булевое значение, в данном примере y будет равен 1 или true потому что int == int.

Ещё, есть такой вызов, как __builtin_choose_expr(a, b, с). Это аналог a ? b : c времени компиляции, с помощью этих расширений компилятора можно написать, что-то наподобие свитча, который возвращает тип переменной в виде числа, 3 для int, 2 для double и т.д.:

#define __get_type(x) \
  __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), double), 1, \
  __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), char), 2, \
  __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int), 3, \
  __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), void*), 4, \
  ....... и так далее

Далее, применяя стандартные трюки с variadic macro, то есть, пишем __get_type(), много или, точнее, count раз через запятую, создаём массив char types[], и подставляем его вторым параметром в вызов функции печати, её заголовок станет таким:

void __print_func (int count, char types[], ...) {

Теперь мы можем смело брать аргументы с помощью va_arg, подглядывая их типы из массива:

for (int i = 0; i < count; i++) {
  if (types[i] == 'd') {
    double d = va_arg(v, double);
    printf("%'G", d);
  }
  else if (types[i] == 'i') {
    double d = va_arg(v, int);
    printf("%'i", d);
  }
  ...
}

На самом деле, чтобы печатать массивы, надо ещё пердавать sizeof(), что выглядит, примерно, так:

(short[])(sizeof(a), sizeof(b), sizeof(c),.........)

Для экономии тип и размер упаковываются в unsigned short: __get_type(x) + sizeof(x) << 5.

Вся работа препроцессора и builtins компилируется очень эффективно, вот такой вызов:

print(42, 42);

Компилируется gcc -O1в такой код:

xor     eax, eax
mov     ecx, 42
mov     edx, 42
lea     rsi, [rsp+12]
mov     edi, 2
mov     DWORD PTR [rsp+12], 0x00840084
call    __print_func

Выше описаные расширения поддерживают компиляторы GCC 5.1+, Clang3.4.+1, Intel C 17.0.0+, и TinyC. На MSVC их нет, возможно, есть похожие, но мне не удалось найти соответствующей информации.

Вот как рисуется цвет:

void __print_color(int a) {
  if (!__print_enable_color) return;
  if (a == -1) printf("\x1b(B\x1b[m");
  else printf("\x1b[38;5;%im", a);
}

Поменяв значение глобальной переменной __print_enable_color на 0можно отключить цветной вывод. А функция __print_setup_colors() позволяет задать палитру:

void __print_setup_colors(int normal, int number, int string, int hex, int fractional) {

Надо будет ещё добавить автоматическое отключение цвета если stdout не консоль, а файл или pipe.

Есть fprint(fd...) для работы с stderr и любыми дескрипторами.

Возможно, у вас вопрос, почему не _Generic, а __builtin_types_compatible_p? Дело в том, что _Generic не отличает массивы от указателей, например int* для него то же самое, что и int[]поэтому с _Generic выводить массивы бы не получилось.

Ссылка на github: https://github.com/exebook/generic-print

Источник: https://habr.com/ru/post/544038/


Интересные статьи

Интересные статьи

Коронавирус внес свои коррективы во все. Вот и ежегодная выставка потребительской электроники Consumer Electronics Show 2021 года впервые в истории прошла не живьем, а в ...
“Каждый человек обязан, по меньшей мере, вернуть миру столько, сколько он из него взял.” — Альберт Эйнштейн В своей первой статье на Хабре мне хотелось бы поделиться с уважаемыми читателями по...
Несмотря на то, что “в коробке” с Битриксом уже идут модули как для SOAP (модуль “Веб сервисы” в редакции “Бизнес” и старше), так и для REST (модуль “Rest API” во всех редакциях, начиная с...
Cтатья будет полезна тем, кто думает какую выбрать CMS для интернет-магазина, сравнивает различные движки, ищет в них плюсы и минусы важные для себя.
Основанная в 1998 году компания «Битрикс» заявила о себе в 2001 году, запустив первый в России интернет-магазин программного обеспечения Softkey.ru.