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

Макросы

О специальных макро в ассемблере

16.01.2021 06:18:31 | Автор: admin

Введение

Много лет назад американским специалистом Гарри Килдэллом (Gary Kildall) в рамках создания системы программирования для персональных компьютеров был разработан транслятор с языка ассемблера для процессора Intel 8086, который он назвал RASM-86 (Relocating ASseMbler). Этот во многом типичный для своего времени продукт имел особенность: он позволял, не меняя транслятора, добавлять описания новых команд процессора с помощью специальных макросредств.

Автор статьи, используя и развивая этот транслятор, успешно применял данные средства по мере появления новых поколений процессоров. Конечно, иногда и сам транслятор требовал ряда доработок, например, при переходе на архитектуру IA-32, а затем и на x86-64 (IA-32e). Тем не менее, изначально заложенная идея позволила легко продолжать эволюцию транслятора до настоящего времени. Некоторые итоги этой работы рассматриваются далее.

Организация генерации команд в трансляторе с ассемблера

Обычно программистам безразлично, как именно организована генерация кодов команд внутри используемого ими транслятора. Однако, поскольку в данном случае можно влиять на этот процесс, рассмотрим на примере конкретного транслятора ход генерации.

Транслятор RASM имеет общую внутреннюю таблицу всех возможных команд процессора. Элементы таблицы начинаются с текста мнемоники команды, за которым следует связанный список всех возможных форм данной команды в зависимости от числа, типа и размера ее операндов. Содержимое очередной формы команды состоит из последовательности микрокодов, каждый из которых представляет отдельно обрабатываемую и уже неделимую часть команды.

Сначала транслятор ищет заданную команду в таблице по ее мнемонике, а затем, идя по связанному списку, ищет подходящую комбинацию операндов. Если заданной комбинации операндов (включая и случай отсутствия операндов в команде) не найдено выдается сообщение об ошибке. Иначе транслятор читает последовательность микрокодов и выполняет каждый из них, составляя, таким образом, код заданной команды. При этом частные случаи комбинации операндов должны располагаться ранее общих случаев. Например, если команда имеет отдельную форму для операнда-байта, расширяемого со знаком, такой операнд должен встретиться в связанном списке раньше общего случая операнда-константы иначе, естественно, будет находиться только общий случай.

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

Трехпроходный транслятор RASM на первом проходе переведет это описание в последовательность микрокодов и вставит новое описание в общую таблицу команд. В этом принципиальное отличие данных макросредств от обычных, которыми можно изменить исходный текст программы, но нельзя изменить содержимое самого транслятора. Если такая команда уже существовала, новое описание добавляется в начало имеющегося связанного списка. Если уже существовала и такая команда и такая комбинация операндов для нее новое описание отменит предыдущее, так как будет вставлена выше. После этого в тексте программы можно свободно использовать новую команду так же, как если бы она изначально была представлена в таблице транслятора.

Макросредства описания команд

По виду специальные макросредства RASM похожи на обычные средства макроподстановки: имеются ключевые слова CodeMacro и EndM, между которыми пишется тело макроопределения. В первой строке пишется имя макро и, возможно, список его параметров. Например:

CodeMacro AAA  DB 37HEndMCodeMacro DIV divisor:EbSEGFIX divisor  DB 6FHEndMCodeMacro OR dst:Re, src:Ee  SEGFIX src  DB 0BH  MODRM dst,srcEndM

Описание формальных параметров

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

Типы формальных параметров:

A  сумматор EAX/AX/ALC  выражение типа метка D  непосредственный операндE  адресное выражение, записанное в регистре или памятиM  адресное выражение, может иметь базовые и индексные регистрыR  один из общих регистровS  сегментный регистрX  прямое обращение к памяти при обмене с сумматором

Размеры формальных параметров

n - длина неопределеннаb  байтw  словоe  двойное словоd  длина при использовании адреса смещение+сегментsb  знаковый байт, расширяемый до словаse  знаковый байт, расширяемый до двойного слова

Примеры описания формальных параметров:

CodeMacro IN dst:Aw, port:Rw (DX)CodeMacro ROR dst:Ee, count:Rb (CL)

Директивы макроопределений

Первоначально все описания команд х86 свелись к нескольким директивам, часть из которых используются редко. Перевод транслятора на архитектуру IA-32 потребовал добавления лишь одной новой директивы управления префиксами размера/адреса 66H/67H, причем, чтобы не вводить новых ключевых слов используется уже имевшаяся директива, но с другой формой параметра.

Директивы DB, DW и DD

Данные директивы в макро почти эквиваленты обычным операторам ассемблера и используются для задания констант и адресов. Эти директивы характерны и для любых других (не специальных) макросредств.

Директива DW используется для задания адреса (4 байта в 32-х разрядном режиме), а директива DD для задания адреса в виде смещение+сегмент. Примеры использования DB, DW и DD:

CodeMacro CLC  DB 0F8HEndMCodeMacro XOR dst:Ee,src:De  SEGFIX dst  DB 81H  MODRM 6,dst  DW srcEndMCodeMacro CALLF label:Cd  DB 9AH  DD labelEndM

Директива адресации MODRM

Это главная из специальных директив, определяющая адресацию архитектуры IA-32. Она определяет и основное отличие данных макросредств от обычных. Именно микрокод, порождаемый этой директивой, указывает транслятору генерировать адресную часть команды, включая и байт режимов адресации и смещение и SIB-байт, если операнды подразумевают это. Директива имеет два параметра. Это или два имени формальных параметров макро или константа-число и имя. Например:

CodeMacro RCR dst:Ee, count:Rb(CL)  SEGFIX dst  DB 0D3H  MODRM 3,dstEndMCodeMacro XOR dst:Re,src:Ee  SEGFIX src  DB 33H  MODRM dst,srcEndM

Директивы определения относительного адреса RELB, RELW

Эти директивы используются для описания команд передачи управления по относительному адресу, занимающему или байт или 4 байта для IA-32. Пример:

CodeMacro LOOP place:Cb  DB 0E2H  RELB placeEndM

Директива задания кодов DBIT

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

CodeMacro DEC dst:Re  DBIT 5(9), 3(dst(0))EndM

Директива формирования префикса сегмента SEGFIX

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

Директива контроля сегментов NOSEGFIX

Директива имеет параметры в виде имени сегментного регистра и имени формального параметра. Она не генерирует кода, а проверяет, что обращение к данному параметру идет с использованием указанного сегментного регистра, иначе сообщает об ошибке. Эта директива требуется лишь в общих формах команд CMPS и MOVS, где один из операндов может адресоваться только через ES.

Данная директива была расширена для управления префиксами размера и адреса 66H/67H. В этом случае в директиве указывается параметр-число: 0 нет префиксов, 1- может быть префикс 66H, 2 может быть префикс 67H, 3 могут быть оба префикса, 4 всегда есть оба, 5 никогда нет 66H, 6 никогда нет 67H и т.п.

Такими простыми средствами удается описать все множество команд IA-32, например:

CodeMacro FLDCW src:Mw  SEGFIX src  DB 0D9H  MODRM 5, srcEndMCodeMacro CMOVAE dst:Re, src:Ee  SEGFIX src  DB 0FH  DB 43H  MODRM dst,srcEndM

Некоторое исключение из стройной системы описаний составляют команды FPU, имеющие операнд в памяти. Для простоты в RASM разрядность таких команд указывается прямо в мнемонике, а не определяется по размеру операнда в памяти. Поэтому в RASM есть, например, команды FIST16, FIST32 и FIST64. Однако на практике, с точки зрения ясности текста, указание разрядности операнда прямо в имени команды FPU оказалось вполне приемлемым.

Создание псевдокоманд с помощью макросредств

Используя возможность добавления новых комбинаций операндов можно конструировать новые команды процессора. Например, команду MOV ECX,10 часто целесообразно заменять двумя командами с более коротким кодом PUSH 10 и POP ECX. А эти две команды можно описать в виде одного макроопределения:

CodeMacro MOVSX dst:Re, src:Dse  NOSEGFIX 6  DB 6AH  DB src  DBIT 5(0BH),3(dst(0))EndM

Такую псевдокоманду можно создать и обычными макросредствами, однако назвать ее именем уже существующей команды MOVSX другие трансляторы (кроме RASM), скорее всего, не позволят. С точки зрения результата, это именно выполнение MOVSX с параметром-константой. Но такой команды в процессоре нет. А с макросредствами RASM можно считать, что на самом деле есть такая формы команды. Тот факт, что реально выполнение идет за два приема и стек меняется, а затем восстанавливается, в большинстве случаев можно не учитывать.

За время использования транслятора RASM накопился ряд таких полезных псевдокоманд, например:

MOV X,Y, где X,Y переменные в памяти;

MOV DS,0 или MOV DS,ES;

Команды PUSH и POP для нескольких регистров сразу, т.е. PUSH EAX,EBX,ECX;

Обращение к портам без указания регистра DX и т.п.

Добавление новых типов команд

Но, конечно, главное назначение описываемых средств это добавление в имеющийся транслятор новых групп команд по мере появления новых поколений процессоров.

При этом в транслятор иногда приходится добавлять и новую группу имен специальных регистров этих команд (внутри транслятора имена это просто переименованные числа). Так, коды имен регистров CR0-CR7 являются внутри транслятора RASM числами 10H-17H, коды имен регистров MM0-MM7 числами 40H-47H, коды имен регистров XMM0-XMM7 числами 50H-57H, и т.д. Младшая цифра чисел (всегда 0-7) участвует в генерации кода через директиву MODRM, а собственно значения чисел используются для задания допустимого диапазона в формальных параметрах новых макро.

При поиске подходящих операндов транслятор проверит, что указанный в команде регистр входит в допустимый диапазон и поэтому, например, в командах MMX вместо MM0 нельзя указать чужой регистр CR0 или XMM0.

Часто в новых множествах команд требуется применить директиву NOSEGFIX 5, выключающую обычные правила использования префикса 66H (в зависимости от размера операндов), поскольку в описываемых командах этот префикс используется по-своему.

Тогда, например, для команд из множества MMX описания выглядят так:

CodeMacro MOVQ dst:Rn(40H,47H),src:Mn  NOSEGFIX 5  SEGFIX src  DB 0FH  DB 6FH  MODRM dst,srcEndM

Для команд из множества XMM:

CodeMacro ADDPS dst:Rn(50H,57H),src:Mn  NOSEGFIX 5  SEGFIX src  DB 0FH  DB 58H  MODRM dst,srcEndM

Для команд из множества SSE2:

CodeMacro ADDPD dst:Rn(50H,57H),src:Mn  NOSEGFIX 5  DB 66H  SEGFIX src  DB 0FH  DB 58H  MODRM dst,srcEndM

Для команд из множества 3DNow!:

CodeMacro PFACC dst:Rn(40H,47H),src:Mn  NOSEGFIX 5  SEGFIX src  DB 0FH   DB 0FH  MODRM dst,src  DB 0AEHEndM

Расширение макросредств для x86-64 (IA-32e), AVX-команд и т.д.

Разумеется, расширение транслятора для генерации 64-х разрядных команд потребовало очередных доработок макросредств в виде добавления новой длины операнда Q (64-битный операнд/регистр) и новой директивы REX, формирующей REX-префикс команд. Потребовалось также ввести новые диапазоны регистров, ну и конечно дополнить таблицу служебных слов названиями требуемых регистров, вроде SPL или R14D или YMM15.

Однако все эти доработки потребовали именно расширения, но не кардинальной переделки транслятора.

Использование макросредств для генерации команд процессоров другой архитектуры

При выполнении работ по программированию RISK-процессора микроконтроллера AT90S2313 штатный транслятор с ассемблера показался автору после работы с RASM непривычным и поэтому неудобным. Возникла идея использовать специальные макросредства и для того, чтобы генерировать коды команд RISK-архитектуры в соответствии с документацией Atmel, но при этом остаться в привычной среде RASM. Дело упрощалось тем, что RASM имеет режим формирования загрузочного модуля сразу, без использования редактора связей.

Анализ показал, что имеется лишь три препятствия такого использования RASM для генерирования команд RISK-архитектуры: конфликт мнемоники команды ST с названием регистра FPU, форма записи инкремента указателя типа X+ и другой способ вычисления относительного адреса, делающий директиву RELW неподходящей.

Первые два препятствия были обойдены с помощью введения новых директив в RASM, позволяющих исключать из лексического анализа заданную лексему (в данном случае ST) и разрешать синтаксические конструкции инкремента типа X+.

Для вычисления относительного адреса команд RISK-архитектуры были доработаны директивы макро RELW и DBIT. В директиве RELW стало возможно указывать необязательные дополнительные параметры в виде добавки и сдвига вправо, позволяющие не просто вычислить адрес относительно текущего места, но и пересчитать его к нужному виду прибавлением добавки и сдвигом на заданную величину. При этом новая форма директивы RELW сама уже не генерирует адрес, а запоминает его для последующего использования в директиве DBIT. Доработка DBIT заключалась в возможности использования адреса, вычисленного выше директивой RELW. Для указания такого адреса используется строка S вместо имени параметра.

Такие несложные доработки транслятора повысили универсальность макросредств. Все RISK-команды были легко описаны с их помощью, например:

CODEMACRO RJMP  k:Cw  RELW 2,12,k  DBIT 8('S'(1))  DBIT 4(0CH),4('S'(9))ENDMCODEMACRO LDI   R_d:Db(16,31),K:Dn  DBIT 4(R_d(0)),4(K(0))  DBIT 4(0EH),4(K(4))ENDMCODEMACRO OUT   P:Dn(0,63),R1_r:Db(0,31)  DBIT 4(R1_r(0)),4(P(0))  DBIT 5(17H),2(P(4)),1(R1_r(4))ENDMи т.д.

И наконец стало можно программировать микроконтроллер AT90S2313 на RASM:

                 ;---- ПЕРЕХОД ПО RESET (0) ----0000 02C0  0006  rjmp РЕСТАРТ                 ;---- ПЕРЕХОД ПО INT 0 (1) ----0002 CBC0  019A  rjmp ПРЕРВАНИЕ_ОТ_ГПРРЕСТАРТ:                 ;---- ИНИЦИАЛИЗАЦИЯ СТЕКА ---- 0006 BFED       ldi  СЧ_ТМ,СТЕК   ;КОНЕЦ РАБОЧЕЙ ПАМЯТИ 0008 BDBF       out  SPL,СЧ_ТМ    ;УСТАНОВИЛИ СТЕК                 ;---- ИНИЦИАЦИЯ ВХОДОВ ПОРТА "B" ---- 000A 2FE5       ldi  tmp,РАЗР_B 000C 27BB       out  DDRB,tmp                 ;---- ИНИЦИАЦИЯ ВХОДОВ ПОРТА "D" ---- 000E 22E0       ldi  tmp,РАЗР_D 0010 21BB       out  DDRD,tmp                 ;---- ИНИЦИАЦИЯ RS-232 ---- 0012 24E0       ldi  tmp,4           ;115200 БОД 0014 29B9       out  UBRR,tmp 0016 28E1       ldi  tmp,(1 SHL RXEN) OR (1 SHL TXEN) 0018 2AB9       out  UCR,tmp

Заключение

Предложенная много лет назад идея, образно говоря, оставить открытой дверь в транслятор с ассемблера оказалось конструктивной и легко реализуемой. Почти не меняя транслятора можно оперативно отслеживать постоянно дополняемое множество команд современных процессоров. Легкость реализации обусловлена тем, что все множество команд состоит из небольшого числа базовых элементов.

Подобные средства целесообразно было бы включать вообще во все трансляторы, поскольку они позволяют начинать использование новых возможностей процессоров, не дожидаясь обновления соответствующих средств разработки. Кроме этого, с помощью данных средств можно совершенствовать систему команд добавлением псевдокоманд типа MOVSX EAX,10, практически ничем не отличающихся от реальных команд процессора.

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

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

Разумеется также, что очередные изменения архитектуры (например, переход к x86-64) не могут свестись в трансляторе только к использованию подобных макросредств, а объективно требуют и доработок самого транслятора. Тем не менее, как бы в дальнейшем ни продолжилась эволюция команд процессоров, данные средства наверняка окажутся полезными.

Литература

1. RASM-86 Programmers Guide Digital Research, Сalifornia

http://bitsavers.org/pdf/digitalResearch/pl1/

2. М.Гук, В. Юров Процессоры Pentium 4, Athlon и Duron. СПб.: Из-во Питер, 2001

Подробнее..

Макросы в С и С

14.03.2021 16:17:43 | Автор: admin

Макросы - один из моих самых любимых инструментов в языках С и С++. Умные люди и умные книжки советуют по максимуму избегать использования макросов, по возможности заменяя их шаблонами, константами и inline-функциями, и на то есть веские основания. С помощью макросов можно создавать не только изящный код, но и плодить не менее изящные баги, которые потом будет очень сложно отловить и пофиксить. Но если соблюдать ряд несложных правил при работе с макросами, они становятся мощным оружием, которое не стреляет по твоим собственным коленям. Но сперва давай разберемся, что вообще такое макросы в С и С++?

Что есть макросы?

В языках С и С++ есть такой механизм, как препроцессор. Он обрабатывает исходный код программы ДО того, как она будет скомпилированна. У перпроцессора есть свои директивы, такие как #include, #pragma, #if и тд. Но нам интересна только директива #define.

В языке Си довольно распространенной практикой является объявление глобальных констант с помощью директивы #define:

#define PI 3.14159

А потом, на этапе препроцессинга все использования PI будут заменены указанным значением:

double area = 2 * PI * r * r;

После препроцессинга, который по сути является банальной подстановкой, это выражение превратится в:

double area = 2 * 3.14159 * r * r;

PI - макрос, в самом простом его исполнении. Естественно, макросы в таком виде не работают как переменные. Им нельзя присваивать новое значение или использовать их адрес.

// Так нельзя:PI = 3; // после препроцессинга: 3.14159 = 3int *x = Π    // после препроцессинга: int *x = &3.14159

О макросах важно понимать, что область видимости у них такая же, как у нестатических функций в языке Си, то есть они видны везде, куда их "заинклюдили". Однако в отличии от функций, объявление макроса можно отменить:

#undef PI

После этой строчки обращаться к PI будет уже нельзя.

Макросы с параметрами

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

#define MAX(a, b) a >= b ? a : b

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

#define SWAP(type, a, b) type tmp = a; a = b; b = tmp;

Поскольку мы первым параметром передаем тип, данный макрос будет работать с переменными любого типа:

SWAP(int, num1, num2)SWAP(float, num1, num2)

Макросы так же можно записывать в несколько строк, но тогда каждая строка, кроме последней, должна заканчиваться символом '\':

#define SWAP(type, a, b) type tmp = a; \ a = b; \b = tmp;

Параметр макроса можно превратить в строку, добавив перед ним знак '#':

#define PRINT_VAL(val) printf("Value of %s is %d" #val, val);int = 5;PRINT_VAL(x)  // -> Value of x is 5

А еще параметр можно приклеить к чему-то еще, чтобы получился новый идентификатор. Для этого между параметром и тем, с чем пы его склеиваем, нужно поставить '##':

#define PRINT_VAL (number) printf("%d", value_##number);int value_one = 10, value_two = 20;PRINT_VAL(one)  // -> 10PRINT_VAL(two)  // -> 20

Техника безопасности при работе с макросами

Есть несколько основных правил, которые нужно соблюдать при работе с макросами.

1. Параметрами макросов не должны быть выражения и вызовы функций.

Ранее я уже объявлял макрос MAX. Но что получится, если попытаться вызвать его вот так:

int x = 1, y = 5;int max = MAX(++x, --y);

Со стороны все выглядит нормально, но вот что получится в результате макроподстановки:

int max = ++x >= --y ? ++x : --y;

В итоге переменная max будет равна не 4, как мы ожидали, а 3. Потом можно уйму времени потратить, отлавливая эту ошибку. Так что в качестве аргумента макроса нужно всегда передавать уже конечное значение, а не какое-то выражение или вызов функции. Иначе выражение или функция будут вычислены столько раз, сколько используется этот параметр в теле макроса.

2. Все аргументы макроса и сам макрос должны быть заключены в скобки.

Это правило я уже нарушил при написании макроса MAX. Что получится, если мы захотим использовать этот макрос в составе какого-то математического выражения?

int result = 5 + MAX(1, 4);

По логике, переменная result должна будет иметь значение 9, однако вот что мы получаем в результате макроподстановки:

int result = 5 + 1 > 4 ? 1 : 4;

И переменная result внезапно примет значение 1. Чтобы такого не происходило, макрос MAX должен быть объявлен следующим образом:

#define MAX(a, b) ((a) >= (b) ? (a) : (b))

В таком случае все действия произойдут в нужном порядке.

3. Многострочные макросы должны иметь свою область видимости.

Например у нас есть макрос, который вызывает две функции:

#define MACRO() doSomething(); \ doSomethinElse();

А теперь попробуем использовать этот макрос в таком контексте:

if (some_condition) MACRO()

После макроподстановки мы увидим вот такую картину:

if (some_condition) doSomething();doSomethinElse();

Нетрудно заметить, что под действие if попадет только первая функция, а вторая будет вызываться всегда. Именно для того, чтобы избежать подобных багов, у макросов должна быть объявлена своя область видимости. Для удобства в этих целях принято использовать цикл do {} while (0); .

#define MACRO() do { \ doSomething(); \           doSomethinElse(); \         } while(0)

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

Еще немного примеров

В языке Си при помощи макросов можно эффективно избавляться от дублирования кода. Банальный пример - объявим несколько функций сложения для работы с разными типами данных:

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

DEF_SUM(int)DEF_SUM(float)DEF_SUM(double)int main() {     sum_int(1, 2);  sum_float(2.4, 6,3); sum_double(1.43434, 2,546656);}

Таким образом у нас получился аналог шаблонов из С++. Но стоит сразу обратить внимание, что данный способ не подойдет для типов, название которых состоит более чем из одного слова, например long long или unsigned short, потому что не получится нормально склеить название функции (sum_##type). Для этого сперва придется объявить для них новый тип, состоящий из одного слова.

Но все же, если есть возможность обходится без макросов, лучше обходится без макросов. Даже если ты прекрасно усвоил, как они работают, и понимаешь их силу, это никак не страхует тебя от того, что ты допустишь какую-то глупую ошибку при объявлении макроса, а потом 20 минут потратишь на ее поиски. Так что не стоит оборачивать в макросы вообще все, что только можно.

Мой блог в Телеграме

Подробнее..
Категории: C++ , С++ , C , Си , Macro , Макросы , Define

Произвольное число аргументов любых типов на C11 и выше с помощью _Generic и variadic макросов

21.04.2021 18:22:57 | Автор: admin
Функция print на Си, принимающая любые аргументы в любом количествеФункция print на Си, принимающая любые аргументы в любом количестве

Функция print на Си, принимающая любые аргументы в любом количестве

О себе

Я сам программист на C++, вернее я только начинающий, без коммерческого опыта. Изначально я познакомился с языком Си, а C++ открыл как мощное расширение языка C. В нем на мой взгляд добавлены те необходимые полезные вещи, которых нет в C (перегрузка функций, классы, пространства имен и др), при этом эти вещи отлично расширяют философию языка

Задумка

Я узнал что в C стандарта 2011 года добавили небольшую возможность "перегрузки" функций с помощью макроса. (Generic selection) Мне, очень интересно стало написать какую-нибудь функцию, которая максимально использовала бы эту возможность

Задумка: написать (макро) функцию print, которая выводит через пробел все переданные в нее аргументы. Звучит невероятно для Си, где обычно указывают тип принимаемого аргумента одной буквой в имени, и явно указывают число переданных аргументов. Но с джейнериками из C11 это возможно

Простой пример с одним аргументом

Ввиду в суть работы этой перегрузки по типу на простом примере с одним аргументом

Напишем три функции print(x) для типов int, float и char* (cstring):

void print_int(int x) {printf("%d ", x); }void print_float(float x) {printf("%.4f ", x); }void print_string(char* x) {printf("%s ", x); }

С помощью данного макроса соединим из под одним именем print:

#define print(x) _Generic((X),    int: print_int,    float: print_float,    char*: print_string)(x)

В итоге получим, что запись print("hi") вызывает print_string("hi"), print(5.5) вызывает print_float(5.5) и так далее

После обработки препроцессором запись print("hi") превратится в _Generic(("hi"), int: print_int, float: print_float, char*: print_string)("hi"), и компилятор в зависимости от типа первого аргумента выберет имя функции, которую надо подставить вместо всего выражения _Generic(...)

Неопределенное число однородных аргументов

С помощью макросов также можно передавать неопределенное число аргументов без явного указания их числа. Покажу на примере для функции print_int

void print_int(int n, ...){  va_list argptr;  va_start(argptr, n);  int x;  for (int i = 0; i < n; i++)  {    x = va_arg(argptr, int);    printf("%d ", x);  }  va_end(argptr);}

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

Макрос PP_NARG(...), возвращающий число аргументов

#ifndef PP_NARG/*    The PP_NARG macro returns the number of arguments that have been   passed to it.   https://groups.google.com/g/comp.std.c/c/d-6Mj5Lko_s/#define PP_NARG(...)     PP_NARG_(VA_ARGS,PP_RSEQ_N())#define PP_NARG_(...)     PP_ARG_N(VA_ARGS)#define PP_ARG_N( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, _61,_62,_63, N, ...) N#define PP_RSEQ_N() 63,62,61,60, 59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40, 39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20, 19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0#endif
#define print(...)    print_int(PP_NARG(VA_ARGS), VA_ARGS)

Примечание: VA_ARGS содержит в себе аргументы, которые попали в ...

Соответственно для джейнериков, если все аргументы одного типа, мы должны написать

#define function(x, ...) Generic((x),    int: function_int,  float: function_float,  char*: function_string)(PP_NARG(VA_ARGS) + 1, x, VA_ARGS)

В итоге получим функцию function, которая обрабатывает неопределенное число однотипных аргументов. Я написал function, а не print, так как для нашей функции это точно не подойдет. Однако если вам известно, что все аргументы одного и того же типа, то такой способ будет намного проще, чем когда любой аргумент любого типа

Неопределенное число аргументов любого типа

I. Хранение информации о типах

Мы создадим универсальную функцию вида (синтаксис вольный) hidden_print(sep, n, x1, x2, x3, ...), которая в зависимости от типа следующей переменной xi выполняет нужный printf. Для любой другой реализации можно вызывать нужную функцию с уже известным типом

Для определенности максимальное число аргументов будет 12. Для print'а этого достаточно.

Так же немного поясню по поводу названий

Все глобальные имена будут начинаться с приставки cool. Это что-то вроде пространства имен, просто я решил создать отдельную несерьезную, удобно подключаемую библиотечку, в которой хранятся такие интересные, но практически не очень полезные штучки. В этой библиотеке на c++ все функции обьявлены в пространстве имен cool, однако в Си пространств имен нет, так что пользуюсь приставками. Однако в любой момент можно сделать #define print cool_print, а затем #undef print

Для хранения информации о типах аргументов буду использовать массив cool_hidden_types[12], индекс cool_hidden_last, куда надо добавить следущий элемент cool_hidden_add_int, cool_hidden_add_float и т.д. для каждого типа, которые добавляют в массив значение о типах. Всего наша функция будет поддерживать 7 типов: int, char*, float, double, char (?), uint, long

char (?)

Почему-то при записи _Generic(('a'), char: fun_char)() компилятор выдает что-то вроде "не найдена функция для int", так что на практике если передать символ в одинарных кавычках ничего не получится и он дай бог выведется как int

Значения о типах я решил определить с помощью #define, хотя в си есть и enum. Но раз уж тут почти весь код на макросах, то гулять так гулять!

Код работы с массивом типов

#define COOL_HIDDEN_INT 0#define COOL_HIDDEN_STRING 1#define COOL_HIDDEN_FLOAT 2#define COOL_HIDDEN_DOUBLE 3#define COOL_HIDDEN_CHAR 4#define COOL_HIDDEN_UINT 5#define COOL_HIDDEN_LONG 6#define COOL_HIDDEN_VOID 7int cool_hidden_types[12];int cool_hidden_last = 0;void cool_hidden_add_int(){    cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_INT;    cool_hidden_last += 1;}void cool_hidden_add_string(){    cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_STRING;    cool_hidden_last += 1;}/*аналогично для каждого типа */void cool_hidden_add_void(){    cool_hidden_types[cool_hidden_last] = COOL_HIDDEN_VOID;    cool_hidden_last += 1;}

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

Создадим теперь generic макро функцию cool_hidden_add(x), которая будет добавлять элемент в массив в зависимости от типа x

#define cool_hidden_add(x)              Generic((x),           int: cool_hidden_add_int,           char*: cool_hidden_add_string,     float: cool_hidden_add_float,     double: cool_hidden_add_double,     char: cool_hidden_add_char,     unsigned int: cool_hidden_add_uint,     long: cool_hidden_add_long,     default: cool_hidden_add_void )()

Это было самое простое...

II. Определение числа аргументов на уровне макроса

Идея заключается в том, чтобы определить макрос вида cool_print##n(x1, x2, ..., xn) ("##" означает конкатенацию со значением n), который по очереди добавляет информацию о типе каждого xi, а затем передает в функцию реализации cool_hidden_print(sep, n, x1, x2, ...) разделитель, n, и все xi. Разделитель я определю как глобальную (если так вообще можно называть переменные с уникальной приставкой) переменную cool_print_sep = " ", которую можно изменить в любой момент

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

В общем виде это выглядит так:#define cool_print_n(x1, x2, x3, x4, ..., xn, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     ...................     cool_hidden_add(xn);     cool_hidden_print(cool_print_sep, 6, x1, x2, x3, x4, ..., xn)

Полный код

#if 1 or "hide this a big part of code"#define cool_print_12(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_add(x5);     cool_hidden_add(x6);     cool_hidden_add(x7);     cool_hidden_add(x8);     cool_hidden_add(x9);     cool_hidden_add(x10);     cool_hidden_add(x11);     cool_hidden_add(x12);     cool_hidden_print(cool_print_sep, 12, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)#define cool_print_11(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_add(x5);     cool_hidden_add(x6);     cool_hidden_add(x7);     cool_hidden_add(x8);     cool_hidden_add(x9);     cool_hidden_add(x10);     cool_hidden_add(x11);     cool_hidden_print(cool_print_sep, 11, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)#define cool_print_10(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_add(x5);     cool_hidden_add(x6);     cool_hidden_add(x7);     cool_hidden_add(x8);     cool_hidden_add(x9);     cool_hidden_add(x10);     cool_hidden_print(cool_print_sep, 10, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)#define cool_print_9(x1, x2, x3, x4, x5, x6, x7, x8, x9, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_add(x5);     cool_hidden_add(x6);     cool_hidden_add(x7);     cool_hidden_add(x8);     cool_hidden_add(x9);     cool_hidden_print(cool_print_sep, 9, x1, x2, x3, x4, x5, x6, x7, x8, x9)#define cool_print_8(x1, x2, x3, x4, x5, x6, x7, x8, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_add(x5);     cool_hidden_add(x6);     cool_hidden_add(x7);     cool_hidden_add(x8);     cool_hidden_print(cool_print_sep, 8, x1, x2, x3, x4, x5, x6, x7, x8)#define cool_print_7(x1, x2, x3, x4, x5, x6, x7, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_add(x5);     cool_hidden_add(x6);     cool_hidden_add(x7);     cool_hidden_print(cool_print_sep, 7, x1, x2, x3, x4, x5, x6, x7)#define cool_print_6(x1, x2, x3, x4, x5, x6, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_add(x5);     cool_hidden_add(x6);     cool_hidden_print(cool_print_sep, 6, x1, x2, x3, x4, x5, x6)#define cool_print_5(x1, x2, x3, x4, x5, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_add(x5);     cool_hidden_print(cool_print_sep, 5, x1, x2, x3, x4, x5)#define cool_print_4(x1, x2, x3, x4, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_add(x4);     cool_hidden_print(cool_print_sep, 4, x1, x2, x3, x4)#define cool_print_3(x1, x2, x3, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_add(x3);     cool_hidden_print(cool_print_sep, 3, x1, x2, x3)#define cool_print_2(x1, x2, ...)       cool_hidden_add(x1);     cool_hidden_add(x2);     cool_hidden_print(cool_print_sep, 2, x1, x2)#define cool_print_1(x, ...)       cool_hidden_add(x);     cool_hidden_print(cool_print_sep, 1, x)#endif //"hide this a big part of code"

Немного проспойлерил, что аргументов макро функции cool_print_n всегда 12, и что после нее идет ..., но об этом далее

Значение выполнения макроса PP_NARG(VA_ARGS) не возможно подставить напрямую в выражение cool_print_##PP_NARG(VA_ARGS), так как оно развернется в что-то вроде cool_print_PP_NARG("x", 5, "i", 8,), что не имеет никакого смысла. Поэтому надо использовать код из макроса, но не возвращать число аргументов, а сразу конкатенировать

код PP_NARG(VA_ARGS) еще раз

#ifndef PP_NARG/*    The PP_NARG macro returns the number of arguments that have been   passed to it.   https://groups.google.com/g/comp.std.c/c/d-6Mj5Lko_s/#define PP_NARG(...)     PP_NARG_(VA_ARGS,PP_RSEQ_N())#define PP_NARG_(...)     PP_ARG_N(VA_ARGS)#define PP_ARG_N( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, _61,_62,_63, N, ...) N#define PP_RSEQ_N() 63,62,61,60, 59,58,57,56,55,54,53,52,51,50, 49,48,47,46,45,44,43,42,41,40, 39,38,37,36,35,34,33,32,31,30, 29,28,27,26,25,24,23,22,21,20, 19,18,17,16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0#endif

cool_print(...) - тот же PP_NARG, но измененный

#define cool_print(...)     cool_print_(VA_ARGS , COOL_RSEQ_N())#define cool_print_(...)     COOL_ARG_N(VA_ARGS)#define COOL_ARG_N(     _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16,_17,_18,_19,_20,     _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, _31,_32,_33,_34,_35,_36,_37,_38,_39,_40,     _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, _51,_52,_53,_54,_55,_56,_57,_58,_59,_60,     _61,_62,63, n, ...)                             cool_print##n(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11,_12)#define COOL_RSEQ_N()     63,62,61,60,59,58,57,56,55,54,53,52,51,50,     49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,     29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,     9,8,7,6,5,4,3,2,1,0

Как это работает:

1. Условно происходит вызов cool_print("a", 4, "b")Это превращается в cool_print_("a", 4, "b", 63,62,61,60,59,58,57,56,55,54,53,52,51,50,  49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,  29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,  9,8,7,6,5,4,3,2,1,0Затем из cool_print_ это все передается в макрос COOL_ARG_N,  который способен принять 64 аргумента. В 64-й аргумент, названный nпопадает как раз количество исходных аргументов из-за того, что придобавление аргументов VA_ARGS перед последовательностью COOL_RSEQ_N (63..0) часть чисел из этой последовательности вытесняетсяВ конце концов в макросе COOL_ARG_N просиходит конкатенация cool_print_##n  и вызов этого макроса. В данном примере это cool_print_3cool_print_3("a", 4, "b", 63,62,61,60,59,58,57,56,55,54,53,52,51,50,  49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,  29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,  9,8,7,6,5,4,3)Всего передается 12 аргументов, так как невозможно обрезать лишние.   И это и не надо, так как благодаря "..." в списке аргументов каждогомакроса cool_print_##n они способны проглотить ненужный хвостТак же можно было бы сократить число аргументов с 64 до 12,   но я посчитал это не очень важным

III. Собственно реализация функции

Отлично, у нас при вызове cool_print("наши", "аргументы", 10) происходит заполнение массива cool_hidden_types информацией о типе каждого аргумента, а затем вызывается функция реализации cool_hidden_print(int sep, int n, ...)! Давайте напишем эту функцию

Небольшое пояснение как в C работать с неопределенным числом аргументов вообще

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

  1. В обьявлении функции последним параметром надо указать ...

  2. va_list argptr- это определение указателя argptr, который будет в использоваться в дальнейшем

  3. va_start(argptr, n)- установка указателя на последний определенный аргумент

  4. va_arg(argptr, float)- возвращает значение слудующего аргумента

  5. va_end(argptr)- завершает работу с аргументами

Итого просто в Си без всяких этих макросов нам необходимо вычислять какими-нибудь образом тип следующего аргумента, и условии прекращения перебора аргументов

Суть такова: в цикле перебираются все элементы все элементы. В swich'e каждый элемент кастится в void'ый указатель x, а затем вызывается в виде printf("...%s", *((type) x), sep), где type - это тип аргумента, а "..." - это специфичный для данного типа формат вывода. Например для int это printf("%d%s", *((type) x), sep). Для упрощенной записи приведения типов я использую вспомогательный макрос #define COOL_CAST(T, x) ((T) (x))

код cool_hidden_print(sep, n, ...)

#define COOL_CAST(T, x) ((T) (x))void cool_hidden_print(char* sep, int n, ...){    va_list argptr;    va_start(argptr, n);void* x;for (int i = 0; i &lt; n; i++){    switch (cool_hidden_types[i])    {    case COOL_HIDDEN_INT:        x = &amp;va_arg(argptr, int);        printf("%d%s", COOL_CAST(int, x), sep);        break;    case COOL_HIDDEN_STRING:        x = &amp;va_arg(argptr, char*);        printf("%s%s", COOL_CAST(char*, x) , sep);        break;    case COOL_HIDDEN_FLOAT:        x = &amp;va_arg(argptr, float);        printf("%.4f%s", COOL_CAST(float, x), sep);        break;    case COOL_HIDDEN_DOUBLE:        x = &amp;va_arg(argptr, double);        printf("%.4f%s", COOL_CAST(double, x), sep);        break;    case COOL_HIDDEN_CHAR:        x = &amp;va_arg(argptr, char);        printf("%c%s", COOL_CAST(char, x), sep);        break;    case COOL_HIDDEN_UINT:        x = &amp;va_arg(argptr, unsigned int);        printf("%.4u%s", COOL_CAST(unsigned int, x), sep);        break;    case COOL_HIDDEN_VOID:        printf("unsupported type%s", sep);        break;    default:        printf("Internal COOL/CPRINT error line: %d in %s", __LINE__, __FILE__);        break;    } }va_end(argptr);cool_hidden_last = 0;}

Дополнительные мелочи

После подключения библиотеки можно избавиться от приставки cool_ с помощью

#define print cool_print

Идеально, теперь наша функция print полностью работает! В качестве вишенки на торте определим функцию println, которая после вывода переводит нас на новую строку

#define cool_println(...)     cool_print(VA_ARGS);    printf("\n")#define cool_printlnn() printf("\n")

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

ни так#define cool_print(...)     cool_print_(VA_ARGS , ## COOL_RSEQ_N()ни так#define cool_print(...)     cool_print_(VA_ARGS ## , COOL_RSEQ_N()все равно не работает

Как советуют в интернете делать при таком случае ничего не происходит. Видимо эта запись работает только для ,## VA_ARGS, когда VA_ARGS идет в конце, а не в начале

Возможно можно как-нибудь еще одним вложенным макросом определить, является ли VA_ARGS пустым. Я нашел в гугле решение только тогда когда максимум 2 аргумента

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

У меня почему-то сработало один раз (,), но после перезапуска visual stidio стало VAR_OPT не определено. К тому же в том месте VAR_OPT ставить нельза, так как макрос будет считать что у нас 63 а не 64 аргумента (что приводила к ошибке вызова не того cool_print##n (на единицу меньше). Нужно что-то вроде

#define cool_print(...)     VAR_OPT(  cool_print_(VA_ARGS , COOL_RSEQ_N())  )вместо текущего#define cool_print(...)        cool_print_(VA_ARGS , COOL_RSEQ_N())

Проблемы этого метода

  • Первое - это конечно же невозможность вызвать функцию без аргументов. К тому же огромным минусом является то, что в случае ошибки генерируется очень невнятное сообщение об ошибке, и я не вижу куда можно вставить его

  • Второе - сложность реализации. На C++ аналогичная функция выглядит намного проще. Хотя, имея в качестве шаблона мою функцию print будет не так сложно реализовать любую другую

  • Третье - статический анализатор. В visual studio у меня подчеркнут красным каждый print и println со словами "требуется выражение", и висит по одной ошибки (прям красным цветом) на каждый вызов этой функции. Не смотря на это все нормально компилируется. И даже не думаю что на это должно тратиться сильно больше времени, чем на раскрытие variadic templates в c++, хотя я тесты не проводил (а как вообще замерить время компиляции - это отдельный вопрос)

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

При этом компилируется прекрасноПри этом компилируется прекрасно

При этом компилируется прекрасно


Полный код данной библиотечки можете найти по ссылке на моем гитхабе: print.h

А вот пример использования c_example.c

Заключение

Удивительно сколько всего можно сделать на чистом Си с очень слабыми шаблонами. Но все же Си предназначен не совсем для этого. Данная статья нужна в большей мере для интереса, хотя может кому-нибудь и поможет в работе. Для удобств написания кода на Си как раз и был создан C++, который позволяет работать с обьектами через класс, а не писать id обьекта первым параметром в методах; он позваляет делать перегрузку функций для тех случаев, когда это необходимо (чтобы не городить функции типа pow, powi, powf); упрощает работу с указателями, добавляя ссылки, добавляет пространства имен, чтобы не городить приставок, добавляет контейнеры. Но как итог всего этого - медленная компиляция

Вот пример реализации этой же функции print на C++:

print на C++

#ifndef COOL_PRINT_HPP#define COOL_PRINT_HPP#include <string>#include <iostream>#include <iomanip>namespace{    std::ostream* out = &std::cout;}namespace cool{    inline void setCyrillic()    {        setlocale(LC_ALL, "Russian");    }void setPrintOut(std::ostream&amp; os){    ::out = &amp;os;}std::ostream* getPrintOutPtr(){    return ::out;}inline void printFlush(){    *::out &lt;&lt; std::flush;}inline void print() {     *::out &lt;&lt; ' ';}template &lt;typename Arg&gt;inline void print(const Arg&amp; arg){    *::out &lt;&lt;  std::fixed &lt;&lt; std::setprecision(4) &lt;&lt; arg &lt;&lt; ' ';}template &lt;typename Arg, typename... Args&gt;void print(const Arg&amp; arg, const Args&amp;... args){    print(arg);    print(args...);}////inline void println(){    *::out &lt;&lt; '\n';}template &lt;typename Arg&gt;inline void println(const Arg&amp; arg){    *::out &lt;&lt; std::fixed &lt;&lt; std::setprecision(4) &lt;&lt; arg &lt;&lt; '\n';}template &lt;typename... Args&gt;void println(const Args&amp;... args){    print(args...);    println();}///void print0() { }template &lt;typename Arg&gt;inline void print0(const Arg&amp; arg){    *::out &lt;&lt; std::fixed &lt;&lt; std::setprecision(4) &lt;&lt; arg;}template &lt;typename Arg, typename... Args&gt;void print0(const Arg&amp; arg, const Args&amp;... args){    print0(arg);    print0(args...);}}#endif

Более понятно, примерно в 3 раза меньше кода, и реализация более полноценная. Но в то же время стандартный 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