Или вполне правдивая история о том где, как, когда, кем, зачем и почему пишутся Си-трансляторы, начиная с самого первого. Материал носит провокационный характер. И если он идёт в разрез с вашими представлениями о личном счастье, не принимайте его близко к сердцу. Реальность многопланова, а изложенный здесь вариант, вполне мог иметь место быть. Только и всего.
Те же, кто уже попробовал примеры из
первой статьи цикла, с удовольствием для себя обнаружили, что получить готовую программу из исходника, технически достаточно просто. Проще даже, чем налиться пивом или вином. И страшненькое слово Компилятор, совсем не страшное, а очень даже ласковое и удобное. Давайте продолжим. Но начнём, как бы из далека.
Некоторое время тому назад, под небом российской империи хаживал некий Жора Гурджиев. Он же Гора, он же Гога, он же Гоша. Замечательный во многих отношениях аффтор. И те немногие, кто знал Жору лично, считали его гуру. Как-то Гурджиев поделился с учениками главным секретом хорошего писателя и своего мастерства. Талант, оказывается, не обязателен. Но первое, что обязательно должно быть, обширная личная библиотека. Сегодня, например, интернет. Когда нет ни денег, ни идей, просто берётся произвольный десяток книг и наугад дёргаются из него цитаты. Сегодня, даже не читая - рерайтинг. И вуаля, новая поделка готова. Знакомо?
А как многие уже догадались, пишите ли вы статьи в журналы или Си-компиляторы, разница в сути процесса и методе выполнения не очень большая. На слуху такие аббревиатуры, как GCC, LCC, TCC, new-PCC, AnyCC и т.д. Это Си-компиляторы. Найти в каждом семь отличий, как водится, вам не удастся. Но зато, кроме дополнительных индивидуальных дефектов, в них очень много общего. Родимые пятна, как говорят.
У всех, ущербная документация и только Тру_i386_ассемблер, например:
movl -48(%ebp),%eax
subl %eax,-8(%ebp)
movl -52(%ebp),%eax
У всех, отсутствует возможность компиляции простых ассемблерных вставок вида
int main(){
unsigned int a = 0;
b:
asm ( "cpuid" );
asm ( "rdtsc" );
asm ( "mov %eax, a" );
printf("%d\n", a);
goto b;}
У всех, идентично организован сам процесс компиляции. Вплоть до названий имён файлов. Короче, одинаково почти всё. А LCC и new-PCC не знают даже о stdcall. Почему? Потому, что то, что в обычной жизни называют плагиатом и воровством, сегодня в программировании именуют рерайтингом или опен_сорс и GNU_GPL. Но обо всём по порядку.
Жил да был на свете Стефен Джонсон. И сподобилось ему в году эдак 1978-м явить миру Portable C Compiler, сокращённо PCC. Который Bell Labs тут же объявила своим, а самого Джонсона произвела в самые главные гуру, тру-Си-менеджмента Юнивёрс.
Dennis Ritchie пришлось тогда задвинуть свои амбиции в самый дальний угол и таинство сие всеблагочестиво благословить. То есть официально признать и подтвердить, что Stephen C. Johnson вроде, как получше его, самого Дениса Ритчи, в этом почётном и многотрудном деле будет.
А проще говоря, Дениса Ритчи в Bell Labs, как бы попёрли. Раб сделал черную работу, раб может уходить. Но урок не прошёл для Дениса даром. И все, кто работал с ним далее, отмечали, что более скрытного и себе на уме человека, больше они не встречали никогда. Земля ему пухом.
PCC оказался на столько удачным, а лучше сказать, что у Bell Labs были такие крепкие и дружеские связи с Беркли, что до 1994-го года он входил в состав BSD в качестве основного компилятора. И породил множество подражаний и массу пародий - клонов, как говорят. Благо, что открытые исходники и лицензия BSD способствовали этому более, чем всегда.
Так-что все эти СИ-СИ в названиях GCC, LCC, TCC, new-PCC, AnyCC это прежде всего зависть к тому успеху и как бы намёк на собственную такую же крутость. Но рерайтеры не могут ничего улучшить или создать и не собираются. Не было в 1978-м stdcall, нет его в LCC и new-PCC и сегодня. Не позаботился Стефен Джонсон или его подчинённые, в своё время, о локальных переменных в инлайн ассемблере, так всё и осталось, у всех. Как и только набор команд i386. И т.д.
Нет, конечно, можно программировать и так:
int main(){
unsigned int a = 0;
b:
asm ( "cpuid" );
asm ( "rdtsc" );
asm ( "mov %eax,-4(%ebp)" );
printf("%d\n", a);
goto b;}
кто мешает? А если не через ёbp? А если локальных переменных - 500? Не собьётесь по 4 вычитать? А если парочка из них, вдруг, двойного размера станет? Это сколько ж пересчитывать надо! И не ошибиться! То-то и оно. В пору парсер садиться писать. И GCC от TCC, для начинающего, отличается только запредельным размером. Кстати, этот финт - "mov %eax,-4(%ebp)" не знают ни Fabrice Bellar - TCC, ни Anders Magnusson - new-PCC, ни команда GCC. Не знал и сам Stephen C. Johnson. Что взять с рерайтеров от GNU_GPL ?
Естественно, что большинство рерайтеров сами компилировать не умеют. Поэтому, новый-старый их супер компилятор, выросший из сорса тридцатилетней давности PCC, для вашей системы, поставляется уже только в исходниках. И требует для своего бинарного перевоплощения вашего умения, знания, навыка и установки MSVC или GCC. Которых у вас естественно нет и быть не должно.
Или является запутанной надстройкой над той же MSVC и GCC. Как LCC, Intel или new-PCC. Это всё вообще отдельный вид спорта. Со множеством поклонников, которым в жизни, больше, видимо совершенно делать нечего. Кстати, во всём мире, до сих пор, нет win-линкера, который бы обходился простым текстовым *.def файлом, как в TCC. У рерайтеров, только так. Кстати, весьма заманчиво выглядел бы Golink, но в мире -coff нет согласия.
Но если вы уже подумали, что всякие там xCC не для вас, взгляните сюда:
int main (void){
int temp0,temp1,temp2,temp3,\
temp4,temp5,temp6,temp7,\
temp8,temp9,temp10,temp11,\
temp12,temp13,temp14,temp15,\
temp16,temp17,temp18,temp19,\
temp20,temp21;
L1: temp11=1;temp12=5;temp13=1;
temp14=4;temp15=2;temp16=3;
temp17=4;temp18=2;temp19=5;
temp20=3;temp21=2;
temp1=temp2=temp3=temp4=\
temp5=temp6=temp7=temp8=\
temp9=temp10 = 0x7fffffff;
temp0=clock();
while (temp10 > 0) {
temp1=temp1 - temp11;
temp2=temp2 - temp12;
temp3=temp3 - temp13;
temp4=temp4 - temp14;
temp5=temp5 - temp15;
temp6=temp6 - temp16;
temp7=temp7 - temp17;
temp8=temp8 - temp18;
temp9=temp9 - temp19;
temp10=temp10 - temp20;
temp21=temp11;temp11=temp12;
temp12=temp13;temp13=temp14;
temp14=temp15;temp15=temp16;
temp16=temp17;temp17=temp18;
temp18=temp19;temp19=temp20;
temp20=temp21; }
temp1=clock();
temp1=temp1 - temp0;
printf("%d\n", temp1);
goto L1;
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
.data
L18:
.ascii "%d\012\0"
.text
.align 4
.globl _start
_start:
pushl %ebp
movl %esp,%ebp
subl $88,%esp
L11:
L13:
L14:
movl $1,-48(%ebp)
movl $5,-52(%ebp)
movl $1,-56(%ebp)
movl $4,-60(%ebp)
movl $2,-64(%ebp)
movl $3,-68(%ebp)
movl $4,-72(%ebp)
movl $2,-76(%ebp)
movl $5,-80(%ebp)
movl $3,-84(%ebp)
movl $2,-88(%ebp)
movl $2147483647,%eax
movl %eax,-44(%ebp)
movl %eax,-40(%ebp)
movl %eax,-36(%ebp)
movl %eax,-32(%ebp)
movl %eax,-28(%ebp)
movl %eax,-24(%ebp)
movl %eax,-20(%ebp)
movl %eax,-16(%ebp)
movl %eax,-12(%ebp)
movl %eax,-8(%ebp)
call clock
movl %eax,-4(%ebp)
L16:
cmpl $0,-44(%ebp)
jle L17
movl -48(%ebp),%eax
subl %eax,-8(%ebp)
movl -52(%ebp),%eax
subl %eax,-12(%ebp)
movl -56(%ebp),%eax
subl %eax,-16(%ebp)
movl -60(%ebp),%eax
subl %eax,-20(%ebp)
movl -64(%ebp),%eax
subl %eax,-24(%ebp)
movl -68(%ebp),%eax
subl %eax,-28(%ebp)
movl -72(%ebp),%eax
subl %eax,-32(%ebp)
movl -76(%ebp),%eax
subl %eax,-36(%ebp)
movl -80(%ebp),%eax
subl %eax,-40(%ebp)
movl -84(%ebp),%eax
subl %eax,-44(%ebp)
movl -48(%ebp),%eax
movl %eax,-88(%ebp)
movl -52(%ebp),%eax
movl %eax,-48(%ebp)
movl -56(%ebp),%eax
movl %eax,-52(%ebp)
movl -60(%ebp),%eax
movl %eax,-56(%ebp)
movl -64(%ebp),%eax
movl %eax,-60(%ebp)
movl -68(%ebp),%eax
movl %eax,-64(%ebp)
movl -72(%ebp),%eax
movl %eax,-68(%ebp)
movl -76(%ebp),%eax
movl %eax,-72(%ebp)
movl -80(%ebp),%eax
movl %eax,-76(%ebp)
movl -84(%ebp),%eax
movl %eax,-80(%ebp)
movl -88(%ebp),%eax
movl %eax,-84(%ebp)
jmp L16
L17:
call clock
movl %eax,-8(%ebp)
movl -4(%ebp),%eax
subl %eax,-8(%ebp)
pushl -8(%ebp)
pushl $L18
call printf
addl $8, %esp
jmp L14
L12:
leave
ret
Разница впечатляет. И не верьте тем, кто говорит, что ассемблер это круто. Ассемблер, это неизбежное зло и появляется оно только тогда, когда вы не можете сделать тоже самое на языке высокого уровня, которым пользуетесь. И это не делает ассемблер хорошим. Это делает язык высокого уровня плохим.
Например, в Си нет оператора gosub. Когда в университете проходили связку call - ret, у Ритчи как раз был грипп. Поэтому в каждой программе, написанной на Си и происходит постоянное умножение сущностей в виде создания бесконечного множества из всё новых и новых функций, ради самих этих функций.
Даже тогда, когда они совсем и не нужны, а только мешают, деться некуда. Простая ассемблерная вставка call loop_0 - ret легко решает эту проблему, если ваш компилятор позволит. Но ведь не позволит. Было бы слишком просто. Ибо, каждая последующая функция не видит локальных переменных предыдущей и поэтому никак не может быть подпрограммой для вызывающей, в классическом смысле этого слова. Не зависимо, понимаете вы сейчас это или нет. Прошу заметить, что в Си, return не является эквивалентом ret. Ибо обычно порождает две инструкции leave и ret одновременно.
Сегодня, можно упрекать Ритчи за радикализм, но тогда, по другому и быть не могло. Повсеместно внедрялась парадигма функционального программирования. А как перегнать всех рабочих обезьян на функции? Только отобрав у них операторы call и gosub. Ритчи с поставленной задачей блестяще справился. Обезьянам были прочитаны лекции о вреде безусловных переходов для их обезьяньей зарплаты и карьеры. И старые, маститые, но от этого ничуть не менее безмозглые обезьяны начали повсеместно отгонять от апельсинов молодых. Как в известном опыте.
Короче, тогда в 1973-м, Ритчи выполнил социальный заказ, а вы все теперь мучаетесь. Поговаривают, что тогда и в ассемблере хотели запретить инструкцию call, заменив её левой фигурной скобкой {, по аналогии. Смотрите, мол, как в Си хорошо получается. Но разработчики процессоров народ стойкий. Сказали, а не пошли бы вы со своими фигурными скобками и функциональным программированием вместе. Тем дело и кончилось.
И сегодня, изучая очередную вариацию на тему и не найдя в ней ничего похожего на call или gosub, помяните не злым, тихим словом старину Ритчи. Семена, посеянные с его помощью, до сих пор живы, всходят и колосятся, принося всё новые и новые урожаи идиотизма. Зачем делать просто, если можно сделать сложно? А то, как выглядит функциональное программирование на ассемблере вы уже увидели.
Хотел промолчать, но не могу удержаться. Мне тоже хочется внести свою лепту в новый стандарт C20. Предлагаю ввести левую фигурную скобку с меткой - { loop_0;. Встретив её компилятор организует переход на loop_0: и далее найдя первую же непарную правую закрывающую скобку } просто возвращается. Конечно, тут возникает чехарда и непонятка с ret и leave-ret, и с точным учётом всех скобок в программе, но что делать, если инструкция call loop_0 выглядит в Си слишком просто, а хочется.
+++++++++++++++++++++++++++++++++++++++
Итак, Fasm мы пока трогать не будем. Так же будем иметь в виду, что вся бесплатная сишная BSD-фигня списана с одной кальки и безнадёжно устарела. Поэтому, если мы себе не враг, среди всех доступных компиляторов выберем самый удачный, удобный и для себя, новичка, перспективный.
Их два. PureBasic и TCC. MSVC имеем в виду, но в расчет пока не берём, святое, только для монастыря. TCC можно было бы даже и не упоминать. Но очень уж он милый. И если когда-нибудь вам, вдруг, захочется романтики командной строки или научиться складывать целые с указателями, каким-нибудь хитрым способом, а не как у плебеев
mov eax, [ loop_0 ]
add eax, loop_0
то TCC точно для вас. Кроме того, TCC ещё и ассемблер, хоть и слегка туповатый - не воспринимает поле .comm, верхний листинг как раз для него. И одновременно расчудесный линкер, и скриптовая машинка, и выдаёт exe, dll, obj.o, lib.a. Не моргнув глазом и без лишних вопросов подключает любые библиотеки и тд, и тп. Короче, сто в одном. А если к нему прикрутить ещё и new-PCC, вы сразу становитесь круче всех BSDxCC вместе взятых. И всё вместе, это помещается в 1 мегабайт. Работает вообще без хедеров, без присущего им постоянного переименовывания, ими же и уже сто раз переименованного, без установки и из любой папки. Но нет IDE.
Хотя, для вас, это уже не проблема. Я так вообще прикрутил к TCC MASM и MSVC. Cool? Ну ничего святого.
У TCC есть такая особенность. Вход в функцию начинается с nop и subl $nn,%esp. По количеству переменных. Если переменных нет, инструкция всё равно есть - subl $0,%esp. Каждый выход из функции, до недавнего времени, был оформлен безусловным переходом на leave-ret, даже тогда, когда leave-ret следующая инструкция, после jmp. Если вы готовы мужественно переносить этот беспредел, тогда TCC для вас. Он вам понравится. Прикрученный к нему new-PCC, просто очарует. А приправив всё это
Universal_Student_IDE, вы сделаете лёгкость своего бытия невыносимой.
Теперь, что касается PureBasic. Лучшей среды, чтобы начать программировать с нуля, саморазвиться и самореализоваться, вы никогда не найдёте. Демо версия полностью функциональна с ограничением на количество строк. Но с компилятором легко договориться, ну скажем на 2 млрд. А два миллиарда строк кода должно хватить всем. Мне лично, нравится версия PureBasic 5.46 LTS, последняя в которой был не только юникод.
Главная особенность PureBasic в удобстве, простоте и структурированности предоставленных новичку и не только, функций. Сегодня, их больше 1400. Фактически, PureBasic это высокоуровневая прослойка между пользователем и winAPI. Программирование в winAPI, это сборка изделия из мелких разносортных болтиков и шурупчиков. Программирование в PureBasic - крупноблочное строительство, с одновременной возможностью неограниченного доступа к тем же болтикам и шурупчикам, везде и всегда.
Если вам надо открыть файл в PureBasic, вы сразу его открываете. Хотите преобразовать строку, сразу преобразуете. А не совершаете часовой магический ритуал, как в Си. Надо открыть десять файлов одновременно и в них не запутаться? - легко! Это сильно приближает желанный результат.
Знать, что есть PureBasic и программировать для себя в Си - надо быть идиотом. PureBasic, это прежде всего лёгкий и безболезненный старт и не ограниченный условностями подбор алгоритмов. Всё, что есть в Си, есть и в PureBasic. Но реализовано проще, на много проще. Плюс много, много, много ещё чего. Включая и стандартную библиотеку Си, тоже. Кип ит симпл, стьюпид. Если вы когда-нибудь услышите что-то плохое о Пурике, не верьте. Это зависть. Люди столько лет упирались рогами в плюсы, а Рай тут.
Если честно, я совершенно не знаю этого языка. 1400 встроенных функций, всё-таки. Но я знаю принципы на которых он работает. И то, какой результат я хочу для себя получить. Далее, просто открываю справочник(и) и творю. Иногда получается. Написание трансмутатора с new-PCC для TCC на PureBasic, при абсолютных нулевых знаниях по теме на входе, заняло у меня 45-50 минут, включая попутный просмотр мануала Пурика. Последующее тестирование и полная отладка, ещё пол часа. При том, что программированием, я только развлекаюсь. И то, эпизодически. Вот, как выглядит минимальное оконное приложение с кнопочками в PureBasic.
; simple_Tereshkov_OE_22:45 01.02.2019
; simple.pb
;===========================================
Title$="simple.pb"
;===========================================
#WindowWidth = 127
#WindowHeight = 17
OpenWindow(0, 20, 30, #WindowWidth, #WindowHeight, Title$)
;========================================
CreateMenu(0, WindowID(0))
MenuItem(1, "Func 1")
MenuItem(2, "Func 2")
MenuItem(3, "Quit")
;========================================
Repeat
Repeat : Until WaitWindowEvent() = #PB_Event_Menu
Select EventMenu()
Case 1
Case 2
Case 3
End
EndSelect
ForEver
End
Добавим кнопочкам функциональности.
; simple2_Tereshkov_OE_22:45 01.02.2019
; simple2.pb
;===========================================
Import "user32.lib"
MessageBoxA(a,a$,b$,b)
EndImport
;===========================================
a$=""
Title$="simple2.pb"
;===========================================
#WindowWidth = 127
#WindowHeight = 17
OpenWindow(0, 150, 130, #WindowWidth, #WindowHeight, Title$)
;========================================
CreateMenu(0, WindowID(0))
MenuItem(1, "Func 1")
MenuItem(2, "Func 2")
MenuItem(3, "Quit")
;========================================
Repeat
Repeat : Until WaitWindowEvent() = #PB_Event_Menu
Select EventMenu()
Case 1
a$="Func 1"
Gosub a
Case 2
a$="Func 2"
Gosub a
Case 3
End
EndSelect
ForEver
End
;========================================
a:
MessageBoxA(0, a$, Title$, 0)
Return
Если вы знаете английский, тут нечего объяснять. PureBasic так же популярен, как и Fasm. И популярность эта добровольная. А толпа сама на плохое не попрёт. Верь мне. PureBasic хорошо документирован. IDE. Дебагер. Тысячи готовых примеров. Делай, как я. И по русски. И доступен. Короче, читай, учись и пробуй. В PureBasic вы реально программируете, а не постоянно согласуете падежи и окончания, как в Си. PureBasic и Си, это, как Волков командер и командная строка. Если кто помнит.
Буду рад, если предложенная информация показалась кому-то полезной. Если вам сейчас видится что-то не вполне ясно, ничего страшного. Я тоже не сразу всё узнал. Пробуйте. Прочитанное вами здесь, на 90% шутка и на 100% правда. Я всегда шучу, хоть и не шучу никогда.
+++++++++++++++++++++++++++++++++++++++++
А если довести пассаж с Ритчи до логического конца, то можно с уверенностью утверждать, что где-то в году 70-м, партия и правительство решили перевести офисный планктон на функциональное программирование. Тогда показалось, что так будет лучше. Было составлено техническое задание, создать компилятор в котором никак нельзя отвертеться от функций, выделены средства, выбран генеральный подрядчик - Bell Labs.
И уже Bell Labs нанял и поручил чёрную работу Ритчи. Как он справился с ней, все знают - 32 незатейливых ключевых слова - auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while. Плюс вымученный, вроде, как им же, мануал - по большей части не очень удачная попытка невнятного пересказа стандартной библиотеки Си, имеющей к самому Си опосредованное отношение, с изданными позднее поверх неё тысячами расшифровок. Вроде, как старина Ритчи сам не отличал для себя Си от стандартной библиотеки Си. И не он один. Чего уж тут о рерайтерах говорить? После чего, компилятор тут же стал официально всем навязываться, всегда и везде, и безудержно пропагандироваться. Что в данном случае является эквивалентом слова успех.
Вы же не хотите сказать, что Ритчи ходил на работу в Bell Labs, получал зарплату Bell Labs и при этом от скуки делал то, что ему вздумается. Изобретал для себя Си, например. Как только появился кто-то более способный - Стефен Джонсон, а лучше сказать, как только Ритчи разгрёб навоз, дальнейшая типа-работа по воплощению в жизнь спущенного сверху плана, + профиты, + бенефиты, тут же была перепоручена другому, Джонсону, левому, своему. И пофиг на Ритчи, паренёк мигом остался без зарплаты. Когда вводили ANSI C, Ритчи, тоже, никто, ни о чём не спрашивал.
Потому, что не принято спрашивать у лошадей, какой именно плуг они сегодня тащить хотят. И если слава Дениса Ритчи не даёт вам покоя, хорошенько запомните мои слова. А то, что Си так старательно волокут и стандартизируют сквозь время и годы, лишний раз доказывает, что это официальная разработка ЦРУ и Пентагон, как и всё в этом мире.
Закончилась эпопея плюсами - C++ и Windows 10. Не возможно поверить, что она написана людьми и на Си. Скорее, каким-то автоматическим монстром-Вельзевулом, зло выполняющим вербальные приказы - Хочу Винду! - например. Папка windows10 (у меня) - 25 гигабайт. Папка после обновлений, ещё 25 гигабайт. Три непонятных файла в корне диска - ещё 25 гигабайт. Итого - сразу на диске минус 75 гигабайт. Эти папки невозможно удалить даже с загрузочной флешки. Нужен диск с Линукс. Короче, всё прозрачно и лаконично. И нами же и оплачено. Ну, и не дураки ли мы?