Русский
Русский
English
Статистика
Реклама

Умный print для C

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

#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: 1234567890123456789222<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, eaxmov     ecx, 42mov     edx, 42lea     rsi, [rsp+12]mov     edi, 2mov     DWORD PTR [rsp+12], 0x00840084call    __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

Источник: habr.com
К списку статей
Опубликовано: 26.02.2021 06:14:08
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

C

Printf

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru