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

Я его слепила. Из того, что было.

- А не пора ли, батенька, замахнуться нам на самого Вильяма, понимаешь, на Шекспира?! - Есть много в этом мире, друг Горацио, что и не снилось нашим мудрецам.

Особо умные называют Си «машинно-ориентированным языком высокого уровня», эдаким универсальным ассемблером. Особенно те, кто сам на Си не пишет. Иначе, они знали бы, что в Си невозможно получить значение метки, вызвать подпрограмму, равно, как и просто так сложить целое с указателем. Хреновенький такой ассемблер получился. Хотели, как лучше. А вышло, как приказали.

Сначала, городим частокол из функций. А потом, пытаемся грызть в нём дыры при помощи указателей. Это так по-нашему. Хитро и оригинально.

Как завещал всем Великий Ритчи, имеется два специальных оператора для работы с указателями - * и &. Оператор & возвращает адрес операнда. Например, p = # помещает адрес переменной num в p. С помощью оператора * присваивают одной переменной значение другой переменной, адрес которой находится в третьей переменной. p=# q=*p; в итоге,q=num.

Зачем? Затем, что такой способ намного затейливее и развлекательнее, чем простая запись q=num;, для дураков. Да и как же без этого, коль скоро в Си нет подпрограмм, а хочется, хоть и черезжопно. Против панацеи локальности и функциональности образца 1970-го года так просто не попрёшь. Вот и приходится извращаться. А костыли выдавать за достижения и гениальные находки.

Всё вроде бы просто. Вы ведь всё поняли?! Ну, кроме того, что указатели + узаконенные метки, типа всяких имён, существуют всегда, а метки - просто метки (для goto) - , только во время компиляции. Но правда состоит в том, что в Си нет просто переменных р, num и q, типа бестипных, как тут процитировано согласно Завета Великого Ритчи. Или просто самих по себе обестипленных указателей на функцию или переменную. И с этого момента начинается секс. При чём, имеют именно вас. Такая простая, вроде бы, программка не прокатит, хоть ты дерись.

//d.c (c) Tereshkov_OE

//Pelle C

int printf();

void bb(void){

_asm {db 'Hello world!',10,0}

}

void aa (void){

int cc; cc=bb; cc=cc+3;

printf(cc);

}

int main(void){

aa();

return 0;

}

.\d.c(8): error #2168: Operands of '=' have incompatible types 'int' and 'void (*)(void)'.

Слышу, слышу стройный хор ведьмаков, затягивающих «А зачема тибе?». А затема! Но не будем о грустном. В конце концов, Ритчи лучше знал, как надо. В смысле, за что ему платят.

Я же, по наивности своей, полагал, что все указатели по природе своей типа int. Ан нет, оказывается, по версии Ритчи, бывают ещё и char, и void. Указатель типа char адресующий адресное пространство в четыре гигабайта. Ну скажите мне, что Ритчи не е?анутый. Вы ведь его понимаете? А вслед за ним и комитет ANSI C. А вслед за ними и всех писателей компиляторов Си с кальки PCC. Копирасты. Если нет, о чём тогда речь? В чём наё?ка? В указательной арифметике? Где вы её видели в этом примере?

А теперь, немного словоблудия. Чем отличается ссылка от указателя? Вы ведь хорошо говорите по-русски? Ритчи этот вопрос не затрагивает. Ибо по?уй. В многотрудном The C programming Language Second Edition слово ССЫЛКА не встречается вообще, за исключением названия 6.5 Структуры со ссылками на себя.

Таким образом отличие ссылки от указателя Ритчи не объяснет. Наверное его нет :). Или малограмотные и недалёкие урусы-переводчики так переводят. Хотя, очевидно, что жизнь программиста на Си делится на две эпохи. После того, как он вкурил разницу и до этого. При случае, раскиньте мозгами.

Господа переводчики, без обид. Вашу цитату "объявление int *daytab[13] определяет массив из 13 указателей на char" знают все. А что определяет объявление char *daytab[13] ? Наверное, по аналогии, массив из 13 указателей на int. Вот, как выглядит предмет в оригинале.

Читать надо в подлиннике. :) Переводы Библии точно такие же. Да пребудет вера твоя крепка!

Вот маленький концепт от меня. Ссылка, это метка. Метка, это адрес. Технически, указатель, это ссылка на ссылку, адрес по которому находится значение другого адреса. Копирайт мой :).

И манипулируя с указателем по типу while (b=*a++), совершаются действия с данными, находящимися по адресу равному значению переменной a. Использовать прямую ссылку на данные при этом нельзя. Нет механизмов. Сначала этой ссылке надо присвоить статус указателя. При этом не возможно отобразить в программе, что именно содержит в данный момент конкретный указатель. Равно, как и присвоить ему произвольное значение. К этому надо просто привыкнуть.

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

Сам Ритчи уточнений не даёт. Вот его определение: Указатель - это переменная, содержащая адрес переменной. Слишком убого, как для автора Си. А как же такой случай: a = GetCommandLineA(); while (b=*a++).

Пусть я наивный, но другие знают про указатели всё. Вот загадка от одного широко известного в узких кругах чувачка. «Куда указывает выражение (m+1) в массиве int m[10][20][30]; и как этого простыми звёздочками добиться?» Чего этого? А пока, следующая загадка от Microsoft.

Вот эта программка

int main(void) {

int m[3][3][3];

int *mm;

int n,a,b,c;n=a=b=c=0;

return 0; }

в MSVC из командной строки компилируется, а эта уже нет. :)

int main(void) {

int n,a,b,c;n=a=b=c=0;

int m[3][3][3];

int *mm;

return 0; }

При этом, как я слышал, MSVC это стандарт. :)

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

Особо просвещённые называют такое положение вещей «концептуальной близостью к архитектуре компьютеров». Не имею возражений. Если Sound Blaster 16, при желании, мог запрограммировать каждый чудак, то сегодня, современная «архитектура компьютеров» существует только для того, чтобы продавать и не пускать чужих на свой рынок. Проблема в том, что нельзя ничего продать насильно без последствий. Купил и выбросил. А если не покупать? Совсем. Радио уже давно есть, а счастья-то до сих пор нет. Кто я есть, если отнять от меня Си? Да я и сам отдам. Не жалко:)

Си великий язык. Особенно, если не задумываться. И особенно для тех, кто на нём не программирует. Универсальный супер ассемблер в 32 ключевых слова и даже в 27, в оригинале. Ох. И как же хотелось кому-то перегнать всех рабочих обезьян на функциональное программирование в семидесятом. И поскорей.

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

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

Есть два списанных с одной кальки компилятора Borland C Builder и Paradigm. Оба ещё можно скачать. Первый для народа. Второй специализированный. Отличий, кроме названий файлов bcc32/pcc32, никаких. Оба компилятора прекратили своё развитие одновременно двадцать лет назад на версии 6. И загнулись обе фирмы, тоже, одновременно, в один и тот же тот самый день. Как так? Волшебство. Повсюду «честная» конкуренция, «прозрачный» бизнес, а тут такой синхронизм. Не хорошо.

Ещё отвлечённый пример. Кое-кто до сих пор помнит группу Битлз - идеал Дитера Болена: сдыбались, записались, разосрались, разбежались. Пока ребят пасли вместе, получалось неплохо. Их песенки даже подписывали именами Пола и Джона. Правда, без предоставления авторских прав упомянутым субъектам, конечно и почему-то.

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

Многие скажут - налицо благотворное влияние коллектива. А я скажу, если вам приглянулся программный пакет, но его дебильность сводит вас с ума, не тратьте силу и время на препирательства с его типа-аффторами. Удалите не исправимый полуфабрикат с вашего диска сразу и забудьте. Или посмотрите, что сможете сделать для себя сами.

Каким должен быть идеальный компилятор? Имеется в виду именно компилятор, а не среда программирования в целом. Во первых, компилятор не должен знать лучше, что для вас лучше. Это значит, что командная строка и объектный файл на выходе обязательны. Крайне желателен выбор между coff, elf, и чистым ассемблером. Ассемблер на выходе должен быть совместим хотя бы с самим этим же компилятором. И наряду с объектным файлом, по умолчанию, должен в точности соответствовать тому, что написано в вашей программе и в нём не должно быть ничего от большого ума аффтора компилятора, имеющего своей целью привязать вас к его продукту раз и навсегда, как вяжут в PureBasic, Digital Mars, Borland, Orange C и многих других. Т.е. объектный файл, для автономных процедур, должен быть вполне автономным. Крайне желателен выбор между внутренней CRT и барахлом из папки system32. Этот объектный файл должен быть совместим со стандартом и другими широко используемыми линкерами не этого аффтора. Полноценный инлайн ассемблер обязателен. В целом, компилятор должен быть ныне здравствующим, простым, понятным, обозримым, иметь исчерпывающую документацию с примерами и быть поддержанным широкими массами, желательно до уровня стандарта де факто или хотя бы действующего фан-клуба не по принуждению. С тем, чтобы время, потраченное на его освоение, не пропало даром.

Чтобы легче было определиться, проведём ещё один тест на вшивость. Ничего запредельного:

//(c) Tereshkov_OE

int main (void)

{

_asm{ db 2,3,4,5,6

mov eax, 2}

return 0; }

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

public _main

_TEXT segment

_main:

push EBP

mov EBP,ESP

add AL,[EBX]

add AL,5

push ES

mov EAX,2

mov EAX,0

mov ESP,EBP

pop EBP

ret

_TEXT ends

end

В четвертьфинал выходят Pelles C, Digital Mars и Builder 6. Но Digital Mars трудно назвать прозрачным. Builder 6 великолепен, развесист, раскидист и ветвист. Реальный претендент. Но знает лучше, что для вас лучше. Всё время и беззапретно оптимизирует. Даже в обличии Paradigm. Например, по своему усмотрению, пытается хранить переменные в регистрах общего назначения. Кто просил? Эта хитрожопость раздражает и ставит под сомнение целесообразность предварительной компиляции, как и использования, вообще. Если ты такой умный, то расскажи сегодня, кто и что будет делать с твоими *.obj завтра или через год. Специфический формат объектных файлов. Путаный процесс линковки. В этом отношении, Builder 6 ещё хуже, чем Paradigm 3. Хоть между ними и 7 лет. Таким образом, чемпионом Вселенной, в номинации «Лучший и простой», заслуженно становится компилятор Pelles C32 v.8 .

Вы спросите, а как же компиляторы от Интел, АМД, нью Борланд? Не знаю, не пробовал. Слова: «IA-32 architecture hosts is no longer supported. On Windows*, the Intel C++ Compiler require a Microsoft Visual Studio installation. The installer may need up to 12 GB of additional temporary disk storage to manage the intermediate installation files», конечно, греют душу. Но у меня вопрос. А почему только 12? Почему не 120 GB? Наденька, не мелочись! Заменить диск, приобрести новую архитектуру, установить Microsoft Visual Studio. И всё это только ради того, чтобы потом в течении 30-и дней просто пробовать не известно что, разочаровываться и стараться поскорее забыть. Ребята из Интел явно о себе много воображают. Наверное, их компилятор читает мысли. Не меньше.

Давайте посчитаем. 12 GB. Нормальная страничка 60х90. Нормальная книжка 100 листов. 12 GB разделим на одну нормальную книжку. Равно 11111 книг. За сколько вы их прочитаете? Я понимаю, что фарш от Intel на много интереснее Трёх мушкетёров. Но всё-таки.

Вернёмся к названию статьи. Я взял компилятор Pelles C. Добавил к нему в качестве линкера TCC. Избавил их от родного содержимого папки Include. Оно в архиве, для любопытных, можете распаковать. Приправил всё это демо версией Universal Student IDE. И получил нечто феерическое для изучения Си и Асм start level одновременно, превосходящее даже HLA. То же можно проделать и с Builder, и с MSVC, и с GCC и с остальным.

Из огорчений. Не устранимо не поддерживаются не инициализированные глобальные переменные и массивы. Челобитные слать сюда. Но они, видимо, глобально нигде и не поддерживаются. Кнопочки в Universal Student IDE можно нажать только 16 раз. Печально. Но это же демка.

Пару слов для новичка. Начнём с азов.

#include <stdio.h>

int main(){

printf("Hello World\n");

return 0;}

В этой зловещей и пугающей программе «Hello World» есть строка #include <stdio.h>, мечта копираста. Продвинутые и наивные люди, как я, могут полагать, что встретив её, компилятор откроет файл stdio.h, найдёт в нём определение int printf(const char* format, ...); и просто добавит его в эту программу. Или, даже не добавит, а где-нибудь там для себя учтёт.

Но мы ведь договорились, что "хороший" компилятор не обладает интеллектом. Поэтому, файл stdio.h будет добавлен к этой программе полностью, весь, целиком, как текст не отличающийся от только что написанного. Если stdio.h потребуется подключить ещё файлы, как в GCC, они тоже будут добавлены, как исходный код. Но не как только определение функции printf. Так, что в эту однострочную программу окажутся приплетены ещё сотни строк постороннего кода, которые теперь будут являть собой единое целое с этой программой в отдельном временном файле, спрятанном от посторонних глаз и уже все вместе, как единое однородное целое, будут проверяться компилятором на наличие ошибок и трансформироваться в ассемблер или объектный файл. Этот процесс, слияния всех фекалий в одной бочке, называют препроцессом. Если общий временный файл зримо может и не создаваться, то сути процесса это не меняет.

Нет проблем, если это родной stdio.h. Другое дело, если это какой-нибудь noname.h от Вайсмана Вайсмановича. С ашиппками. Компиляции не получится. Но если в файле noname.h полезно только определение константы VERY_NEED, то файл noname.h можно и не упоминать, а определение константы записать в тексте основной программы самому, собственными ручками - VERY_NEED 22. И всё заработает. Суть понятна.

Остерегайтесь бездумной копипасты, неизбежно ведущей к копирастии и ашиппкам. А #include <stdio.h> и есть эта самая бездумная копипаста. Но только хуже, на много хуже. Вы её не только не читаете, но даже и не видите. «Hello World» переписывается следующим образом.

int printf(const char* format, ...);

int main(){

printf("Hello World\n");

return 0;}

И ещё, *.h нужны и используются поставщиками компиляторов, для постоянного переименовывания и переопределения уже многократно переименованного и переопределённого. Вроде, как и так всё не достаточно сложно. Иначе, кому будет нужен windows.h? Вы же не сможете догадаться сами или где-нибудь прочитать, что WINDOWS = WIN = WIN32 = PASCAL = STDCALL = stdcall = _stdcall = __stdcall. А в windows.h это всё уже есть. Очень удобно. Надо только немного поковыряться. windows.h не справочник же. Иначе, кому бы все эти бесконечные гигабайты были бы и нужны? Пользуйтесь :).

А как? Скачайте архив. Распакуйте его на диск D... продолжение следует. Архив пока не готов. В смысле, я не решил пока, что туда положить. Но скорее всего, это будут Pelles C и TCC в одном флаконе. С возможностью выбора между ними в одной программе без перезагрузок. Открываете файл и компилируете любым компилятором и в любой последовательности, и не однократно. Плюс ассемблерный листинг в придачу. Круто? Всё уместилось в пару мегабайт. Для обучения самое то.

Маленькое обоснование. К сожалению, не существует компилятора Си, генерирующего полностью автономные, не привязанные к прародителю, логически обоснованные и предсказуемые объектные файлы. Ну может быть, кроме Fasm, который не Си. Чем больше вы будете углубляться, тем больше подводных камней встретите. Досада и раздражение неизбежны. Равно, как и изучение ассемблера. Даже беглого взгляда на crt.lib от Pelles C достаточно, чтобы понять, что написана она далеко не на Си. Таким образом, Си никакая не панацея, а ещё один вид хорошо продуманных палок в колёса.

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

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

А пока, заслуженный бонус. Как похерить Великого Ритчи и ANSI C комитет вместе с их указателями. Увы, братия, враг силён и матёр. Без ассемблера никак. Незабвенный и вечно живой 'Hello world!'.

//Hw.c (c) Tereshkov_OE

//Pelle C

int printf();

void bb (void){

_asm{

db 'Long live Hello world!',10,0}

}

int aa (void){

int cc;

_asm{

mov eax, bb

add eax, 3

mov cc, eax}

printf(cc);

_asm {sub ESP,4

pop eax}

}

int main(void){

int s = 0x000a6425;

printf(&s,aa());

return 0;

}

Разумеется, int aa(void) можно переписать и так:

int aa (void){

_asm{

mov eax,bb

add eax,3

push eax}

printf();

_asm {pop eax}

}

но сейчас у нас другие цели. Хороший компилятор должен не бить по рукам на каждом шаге по причине того, что у господ аффторов не хватает воображения, как выбраться из изобретённого ими же фекалярия, а являть собой удобную и простую надстройку над обычным ассемблером. Удобную для всех, а не только для господ аффторов, которые выкладывают свои бесплатные творения единственно из желания зарисоваться. Таких хороших компиляторов нет и не предвидится. А Pelle C, среди прочих, наиболее адекватный. Сейчас, речь идёт только о файле pocc.exe. GCC гораздо мене строгий, но и на много более тупой. Такая изящная программка ему не по зубам.

Подобная программка не возможна и в PureBasic. Там int printf() означает именно int printf(), но не любые параметры. Одной возможностью меньше и многими затруднениями больше. От большого ума и всеведения господина Фреда, разумеется. Ибо нефиг, как он считает. А раз нефиг, то и нафиг. Я никогда не буду пользоваться тем, чем не возможно воспользоваться по определению.

Ассемблер, ассемблер и ещё раз ассемблер, товарисчи!(с)Я:) Ассемблером пользуются не из-за какой-то мифической крутости, быстроты или компактности кода. А из-за вполне реальной и ощутимой ограниченности и кривотупости Си, Бейсика, Паскаля и прочей ?уеты. Флажок локального выключения/включения проверки соответствия типов сильно бы помог, но до этого пока не додумался никто. Хотя, в GCC вольница уже реализована в виде простых предупреждений по всем фатальным в Pelle C поводам.

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

Нечем? - пользуйся TCC, Pelle C или MSVC. Джон Коннор, я жду тебя! :)

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

PS. Многое из того, что вы уже прочитали в этом цикле, может показаться невероятным. Не может быть! Но многие видели фильм посвящённый взлому немецкой машинки Enigma Аланом Тьюрингом. Тогда, в фильме, всё получилось буквально за пару недель.

Но всю войну Тьюринг вкалывал не покладая рук, день и ночь. Все немецкие радиограммы были расшифрованы им лично. Кроме трёх. Самых главных. Про бабло. А денег-то и сегодня хочется.

Годы подготовки. И вот, в 2007 году был запущен проект распределённых вычислений «Enigma@Home», целью которого являлся взлом трех зашифрованных сообщений «Энигмы», перехваченных в северной Атлантике в 1942 году. Тех самых, про золото партии. Два сообщения были успешно расшифрованы в 2009 году, третье (изначально неполное и повреждённое) — в 2013.

И это хорошо. Но вот, что удивительно. При наличии всех современных супер компьютеров и огромного парка домашних, ушло два года на два сообщения + 2007-1942=65 лет на подготовку. Алан Тьюринг в 1944-м тратил на то же самое жалких 24часа/3000отгаданных_ключа =28.8х2=57.6 секунды. Верь.

Естественно, что в реале никакого оборудования, "созданного" Аланом в Блетчли-парке в количестве 210 штук весом 2,5 тонны каждая, до наших дней не дошло. Хоть и выпускалась оно серийно до сентября 1944 года. В Блетчли-парке всегда было полно ворья. Алан Тьюринг вынужден был даже свою кружку пристёгивать на цепь к батарее, чтобы не украли. А тут такая ценная аппаратура - «Bombe» в количестве 210х2,5=525 тонн. Сама к пальцам липнет.

В 40-х одну «Bombe» делали за 6 дней, по одной в неделю. В наше время одну копию «Bombe» делали 10 лет.

И всё это не считая того, что каждое сообщение можно просто прокатывать последовательно через 2-3-4-5 разных настроек подряд. Немецким криптографам такое очевидное и лежащее на поверхности решение и в голову, якобы, не приходило. Но ведь именно поэтому-то, все немцы в кино про войну и дураки, а Алан Тьюринг - один в фильме умный. Бывает.

С компиляторами языка С примерно та же история. :)

- Есть много в этом мире, друг Горацио, что и не снилось нашим мудрецам.