Начало программирования в Виндовс 2

Твой лучший компилятор

Или вполне правдивая история о том где, как, когда, кем, зачем и почему пишутся Си-трансляторы, начиная с самого первого. Материал носит провокационный характер. И если он идёт в разрез с вашими представлениями о личном счастье, не принимайте его близко к сердцу. Реальность многопланова, а изложенный здесь вариант, вполне мог иметь место быть. Только и всего.

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

Некоторое время тому назад, под небом российской империи хаживал некий Жора Гурджиев. Он же Гора, он же Гога, он же Гоша. Замечательный во многих отношениях аффтор. И те немногие, кто знал Жору лично, считали его гуру. Как-то Гурджиев поделился с учениками главным секретом хорошего писателя и своего мастерства. Талант, оказывается, не обязателен. Но первое, что обязательно должно быть, обширная личная библиотека. Сегодня, например, интернет. Когда нет ни денег, ни идей, просто берётся произвольный десяток книг и наугад дёргаются из него цитаты. Сегодня, даже не читая - рерайтинг. И вуаля, новая поделка готова. Знакомо?

А как многие уже догадались, пишите ли вы статьи в журналы или Си-компиляторы, разница в сути процесса и методе выполнения не очень большая. На слуху такие аббревиатуры, как 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 гигабайт. Эти папки невозможно удалить даже с загрузочной флешки. Нужен диск с Линукс. Короче, всё прозрачно и лаконично. И нами же и оплачено. Ну, и не дураки ли мы?

Ритчи старался не зря. Это мой компьютер! Мой! Я за него заплатил! Какого вхера я не могу удалить с него ваши долбаные файлы? Майкрософт совсем одурела. Чем Windows 10 отличается от Пети_2? Может для Майкрософт, тоже, надо выкупы платить? Зачем папки удалять? Забыл спросить. Моё дело. Затем, что Windows 10 ворочается, как дохлая черепаха. Плюс новая игра от Майкрософт - «Установи на Windows 10 звук». Аля ранний Линукс. Вроде не дурак, но я не смог. И не хочу. Не работает? - нах?й. Прощай Windows 10. Здравствуй MicroXP by eXPerience. И пошло оно всё!

Реальность многопланова. И планы ея проявляются иногда самым невероятным, неожиданным и причудливым образом. Стоит ввести операторы call или gosub и функциональное программирование, ради самого функционального программирования, уже никому не надо. Кто, будучи в здравом уме, станет определять функцию там, где можно поставить простой gosub? Bell Labs? По словам самого Ритчи, Си это примитивная, убогая, путанная, над процессорная и над системная пародия на универсальный типа-ассемблер, на спринтера с ампутированными ногами и воспиталку из детского сада - это нельзя и это нельзя, и то нельзя. Эдакий пастух, который просто пасёт функции. Сам по себе пасёт. И вы этому пастуху не нужны.

Будь в следующем поколении процессоров Intel хоть 100000 новых команд, в Си всё равно останутся только те же 32 ключевых слова. И их опять за глаза хватит, чтобы охватить эти 100000, все до одной. Охватить, значит опять похерить. Си даже набор i386 целиком не нужен. А дурачки из Intel всё изобретают и изобретают. Для кого? Главный лозунг программистов на Си - а нам пофиг. И вы хотите сказать, что на Си пишут операционки. Ну что ж, про Windows 10 я вам уже рассказал. Берите ассемблер. И ручками! Ручками!

Мануал Ритчи лучше всего назвать «Си для идиотов». После того, как человек сам запустил Hello world, попробуйте его разубедить, что printf это не Си, а какая-то левая, консольная функция. Да и сам Ритчи об этом тупо молчит. Типа не догоняет. Иначе, пойдут неудобные вопросы. А что это за функция? А откуда она взялась? А можно ли её заменить? А почему ты об этом молчишь? Библиотека? А почему только стандартная? А что значит подключена по умолчанию? А другие? Можно?! А как подключить? А почему не из текста программы? Не предусмотрел? А почему не предусмотрел? Тю!! Та я смотрю, шо у тебя линкер главнее компилятора! Так мне теперь и линкер учить? И команды управления ассемблированием? Тю!! А ты говорил шо только Си! А где gosub? Забыл? Начальника не разрешила? Ну вот его давай и учи! А зачем мне твой Си, если printf я могу пользовать и из ассемблера, и из бейсика, и откуда угодно? Та ты мурло! Та ты брехло! В общем, учите «Си для идиотов» и вы, по этой книге, не напишите Windows_11 никогда, я вам гарантирую. «Си для идиотов» наилучший пример того, как не надо учить(ся) программированию с ноля.

Серендипность волшебная вещь, как и столь многими желанная. Не правда ли? Короче, идите-ка вы все чай пить с PureBasic >:)

Что в сухом остатке? Хорошего? Не много. Библиотека Си. В Винде - crtdll.dll. Плюс стандартизация. То есть, если вы пишите свои программы в Си только на основании 189-и функций, предоставляемых стандартной библиотекой Си, без дополнительных выкрутасов, ваша программа одинаково хорошо заработает и под Виндой, и под Маком, и под Линух. Но вот, что удивительно. Если вы пишите свои программы в PureBasic только на основании общих функций из 1400-т, предоставляемых PureBasic, ваши программы, тоже, одинаково хорошо заработают и под Виндой, и под Маком, и под Линух. Но в Си вложены миллиарды, а проект PureBasic развивает на свои средства один человек. И хорошо, что он никогда не работал в Bell Labs.

Сегодня, Си такая же религия, не хуже всех остальных. С такими же евангелиями, клиром, духовенством и апостолами. И вот, чему старые обезьяны учат молодых. "Отличительной особенностью структурированных языков является обособление кода и данных (compartmentalization). Оно позволяет выделять и скрывать от остальной части программы данные и инструкции, необходимые для решения конкретной задачи.( Демагогия.) Этого можно достичь с помощью подпрограмм (subroutines), в которых используются локальные (временные) переменные. Используя локальные переменные, можно создавать подпрограммы, не порождающие побочных эффектов в других модулях. Это облегчает координацию модулей между собой. Если программа разделена на обособленные функции, нужно лишь знать, что делает та или иная функция, не интересуясь, как именно она выполняет свою задачу. Помните, что чрезмерное использование глобальных переменных (которые доступны в любом месте программы) повышает вероятность ошибок и нежелательных побочных эффектов. (Каждый программист, работавший на языке BASIC, хорошо знает эту проблему.)" Герберт Шилдт. Полный справочник по C++. 4-е издание.

Что тут сказать? Подпрограммы от функций человек не отличает. Subroutines = functions. Или делает вид, что не отличает. Или намеренно смешивает понятия, для дурачков и вводит нас в заблуждение. Чтобы в дальнейшем не возникала тема с отсутствием gosub. gosub якобы равен вызову функции. Не равен, даже в ассемблере. Но будем следовать тому, что написано.

По мнению Шилдт, хорошо бы в подпрограммах использовать локальные (временные) переменные. Оригинально. А нахера? И само по себе абсурд. Каким образом? Как этого достичь? Покажи. На то они и подпрограммы, а не функции, даже по названию. Разное у них предназначение.

Функция претендует на целостное решение определённой задачи. Подпрограмма же может просто утилизировать повторно используемый код. Для удобства и банальной экономии памяти. Подпрограммы не нуждаются в передаче параметров для вызова. Это главное из отличий.

А если gosub произвести внутри функции? Внутри функции и так все переменные уже временные. «Как сделать временную переменную временной дважды». Хорошая тема для диссертации. Но диссертация провалится. В Си уже давно реализован механизм когда одно и то же имя временной переменной, внутри одной и той же функции, можно использовать повторно для совершенно разных временных переменных, опять же временно и неограниченное число раз. При этом присвоенное значение первой временной переменной с этим именем всегда в точности сохраняется. Поскольку, все последующие живут только до первой скобки }.

extern void printf (const char *_Format,...);

main()

{

int i=25; int n=5;

if (n > 0) {

int i;

for (i = 0; i < n; i++);

printf("%d\n", i);}

printf("%d\n", i);

}

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

"Отличительной особенностью структурированных языков является обособление кода и данных." Ну прям скрижаль Моисея. Но, Герберт, чтобы перемешать код и данные сегодня, надо не хило извратиться. Формат PE не позволит. А антивирь тебя просто прибьёт. Согласно новейшим стандартам, переменные и в Си теперь могут возникать внезапно и где угодно. И это победа. Спустя много лет, программисты Си наконец получили доступ к тому, что было в BASIC с начала времён. Поэтому, зачем поминать "обособление кода и данных", я не понимаю. Правды ради, следует сказать, что с помощью gcc от djgpp можно перемешать код и строковые константы просто на ура.

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

По мнению Шилдт, основа существования любой программы, постоянное сокрытие данных одними её частями от других. И к этому-то и следует стремиться. Очень хорошо. Но каким образом это облегчает координацию работы модулей между собой? Ну, когда все друг от друга всё прячут. И как же все они обходятся без какого-нибудь глобального буфера обмена, например? Даже в Винде есть Клипборд. И этот Клипборд совсем не дефект, а наоборот - прорыв, особенно по отношению к MSDOS. Герберт, ты хочешь отнять у нас Клипборд? Копирасты тебя разорвут.

"Нужно лишь знать, что делает та или иная функция, не интересуясь, как именно она выполняет свою задачу." Мнение профана. А кто придумал MSDN? Всякие там справочники по winAPI. Придётся интересоваться. Иначе, откуда все эти душу раздирающие истории об утечках памяти, сборщиках мусора, переполнениях и эксплойтах.

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

// ТУПО СЧЁТЧИК

extern void printf (const char *_Format,...);

main(){

for (int i = 17; i; i--)

printf("%d\n", i); }

// ТУПО ЕЩЁ СЧЁТЧИК

extern void printf (const char *_Format,...);

main(){

int i = 17;

for (;i;){

i--;

printf("%d\n", i);} }

Трудно поверить, но это два разных счётчика. Так-что интересоваться придётся. Ещё как придётся. И с дисассемблером. Это же Си!!! И чем тупее компилятор, чем более фекальный и загаженный код он генерит, тем лучше это для всякого рода навороченных защит.

Использование глобальных переменных, якобы, приводит к ошибкам и нежелательным эффектам. К каким? Золотце, но как же сам-то ты совсем без них? Покажи. А! Знаю! Знаю! Опять Hello world.

"Каждый программист, работавший на языке BASIC, хорошо знает эту проблему." Видимо, имеется в виду: - Каждый программист, работавший на языке BASIC в 1965-м году, хорошо знал эту проблему. Какую проблему? Эту? Герберт, проснись, протри твои глаза, умойся, выйди, наконец, на улицу и покажи нам хоть один BASIC, не из твоих фантазий и не из твоего чулана, в котором нет функций или процедур в дополнение к подпрограммам. Ты ещё ZX Spectrum вспомни, с его нумерацией строк. Или Форт, в котором якобы на машинном стеке валяются данные вперемешку с адресами возврата. Хотя, для Hello world сойдёт.

Герберт, так на что же ты делить программу? На подпрограммы или на функции? Про процедуры ты, конечно, ещё не слышал. Но не будем умножать сущности. Пожалуйста определись. И что делать тем, кто, в отличие от тебя, хочет и то, и другое, и третье? И спрашивать у тебя не собирается. Видимо, следующая твоя книга, всё-таки - " Функциональное программирование и Ассемблер в примерах".

Хорошие ученики от плохих учителей не отличаются. В этом секрет того, почему отличники не могут стать миллионерами. Нас учит Шилдт, его учил Ритчи, а Ритчи самому пришлось высасывать всё из собственного пальца. Лишь бы начальство от него побыстрей отвязалось. А то, что у Ритчи между функцией и подпрограммой стоял знак равенства, я не сомневаюсь. И то, что Ритчи, будучи якобы аффтором, не отличал язык Си от стандартной библиотеки языка Си, тоже, вполне очевидно. Иначе, он подлец. Ну а Шилдт, ученик подлеца. Рерайтеры. Что тут говорить?

Подводя итог. Уверен, пройдёт ещё 45 лет, но gosub в Си всё равно придёт. Как через 45 лет пришла возможность не возбранно объявлять переменные где угодно. Глаза общественности я открыл и возмущённый глас её будет реветь всё громче и сильнее. Что с того, что Ритчи когда-то так не вовремя перенёс грипп, а Шилдт затуманил всем мозги, да так ловко? Пелена спала, мрак невежества отступил, а на востоке уже брезжит заря освобождения. Вперёд, к новым статьям цикла!

(с) Терешков Олег Евгеньевич. Январь 2019.

+++++++++++++++++++++++++++++++++++++++

И вдогонку. Когда статья была уже написана. Горячие новости. Самым тупым и припиз?енным, из всех, что я видел, оказался Intel(R) C Compiler Version 4.0 99100 . Приведенную выше си-программу он вообще не прожевал. Вот его дефекация. По типу, что знаю - то и пишу! Про что не знаю, про то тихо молчу. Совсем, как Герберт Шилдт. Но стоит ли удивляться? Альтернативы PureBasic нет >:)

; -- Machine type P

; mark_description "Intel(R) C Compiler Version 4.0 99100";

; mark_description "-S";

;ident "Intel(R) C Compiler Version 4.0 99100"

;ident "-S"

.486P

.387

_TEXT SEGMENT PARA PUBLIC USE32 'CODE'

_TEXT ENDS

_DATA SEGMENT PARA PUBLIC USE32 'DATA'

ALIGN 010H

_DATA ENDS

_BSS SEGMENT PARA PUBLIC USE32 'BSS'

ALIGN 010H

_BSS ENDS

_TLS SEGMENT PARA PUBLIC USE32 'TLS'

_TLS ENDS

_TEXT1 SEGMENT PARA PUBLIC USE32 'CODE'

ALIGN 010H

_TEXT1 ENDS

_DATA1 SEGMENT PARA PUBLIC USE32 'DATA'

ALIGN 010H

_DATA1 ENDS

ASSUME CS:FLAT,DS:FLAT,SS:FLAT

_DATA SEGMENT PARA PUBLIC USE32 'DATA'

_DATA ENDS

_TEXT SEGMENT PARA PUBLIC USE32 'CODE'

; COMDAT _main

; -- Begin _main

; mark_begin;

ALIGN 4 ;

PUBLIC _main

_main PROC NEAR

$B1$1: ; Preds $B1$0

push ebp ; 12.17

mov ebp, esp ; 12.17

sub esp, 3 ; 12.17

and esp, -8 ; 12.17

add esp, 4 ; 12.17

push esi ; 12.17

push ebx ; 12.17

sub esp, 12 ; 12.17

mov esi, OFFSET FLAT: __STRING$0 ; 52.25

;

; LOE ebp esi edi esp

$B1$2: ; Preds $B1$4 $B1$1

call _clock ; 29.8

; LOE eax ebp esi edi esp al ah

$B1$3: ; Preds $B1$2

mov ebx, eax ; 50.15

call _clock ; 50.15

; LOE eax ebx ebp esi edi esp al ah bl bh

$B1$4: ; Preds $B1$3

sub eax, ebx ; 51.23

mov DWORD PTR [esp], esi ; 52.25

mov DWORD PTR [esp+4], eax ; 52.25

call _printf ; 52.25

jmp $B1$2 ; Prob 100% ; 52.25

ALIGN 4 ;

ALIGN 4 ;

; LOE ebp esi edi esp

; mark_end;

_main ENDP

;_main ENDS

_TEXT ENDS

_DATA1 SEGMENT PARA PUBLIC USE32 'DATA'

__STRING$0 DB "%d",10,0

_DATA1 ENDS

_DATA SEGMENT PARA PUBLIC USE32 'DATA'

_DATA ENDS

; -- End _main

_DATA SEGMENT PARA PUBLIC USE32 'DATA'

_DATA ENDS

EXTRN _printf:PROC

EXTRN _clock:PROC

; mark_proc_addr_taken _main;

END