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

Раздевая Си

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

Абсолютная власть и свобода требуют абсолютных же знаний и умений. Лёгкость бытия, в свою очередь, легка, только пока находится в пределах высокоуровневых лингвистических конструкций. Как водится, истина где-то по середине. Уже давно, со времён Turbo C, предпринимались, не ставшие очень популярными, попытки скрестить первое со вторым. Вот наиболее завершённые. HLA - хай лэвл ассемблер. C--, ассемблер, обличённый в синтаксис языка Си.

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

//(c) Tereshkov_OE

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

int main(void){

unsigned int a = 0;

_asm{ jmp c}

b:

_asm{

cpuid

rdtsc

mov a,eax}

printf(" %u\n", a);

c:

_asm{ jmp b}

}

В мире Windows, из доступных, (мне известны) всего два достойных. MSVC и Pelles C. MSVC не плох, но весьма специфичен. В нём много тайн и скрытых от глаз подстав и заморочек. В том числе и в виде дыр из int 3 между функциями, например:

imul ECX,ECX,02710h

add ECX,_c

mov _c,ECX

L22D: mov ESP,EBP

pop EBP

ret

int 3

int 3

int 3

int 3

int 3

int 3

int 3

int 3

int 3

int 3

int 3

int 3

int 3

int 3

int 3

_GetResults:

push EBP

mov EBP,ESP

L243: cmp dword ptr _life,0

А вот Pelles C, хоть и не имеет ассемблерный output, хороший повод выучить язык Си до конца. До уровня *.obj, Pelles C безусловно лучший. Все последующие примеры основаны именно на нём. Из сторонних утилит, понадобится только obj2asm. Ну, чтобы проверить всё самому.

Если быть точным, Pelles C имеет ассемблерный output. Надо просто поискать :). Но этот output, без допиливания, напрямую ни с чем не совместим, в том числе и с самим Pelles C. Output из obj2asm, тоже ни с чем не совместим, но выглядит на много красивее.

Кстати, в MSVC, тоже, стоит поперёк горла собственный output. Если cl.exe выдаст что-то в роде:

.686P

.XMM

include listing.inc

.model flat

INCLUDELIB LIBCMT

INCLUDELIB OLDNAMES

PUBLIC __real@4000000000000000

PUBLIC __real@4010000000000000

PUBLIC __real@0000000000000000

CONST SEGMENT

__real@4000000000000000 DQ 04000000000000000r ; 2

CONST ENDS

CONST SEGMENT

__real@4010000000000000 DQ 04010000000000000r ; 4

CONST ENDS

CONST SEGMENT

__real@0000000000000000 DQ 00000000000000000r ; 0

CONST ENDS

получить эксэшку из такого асма не удастся. Ибо, в LIBCMT все эти __real@ уже есть. Это не надуманный пример. В пятой статье цикла приведена программка нахождения корней квадратного уравнения sqrt.c. Проведите её по схеме cl.exe > ml.exe > link.exe и убедитесь, что исполняемый файл вам не получить. Это к вопросу о венгерской нотации и тому подобной фигне. Все остальные компиляторы такие же. Рано или поздно, но обязательно упрёшься во что-нибудь. Чаще, буквально сразу.

Здесь и далее я называю Pelles C лучшим. По типу выбора, что лучше - шило или топор? Воздушный шар или презерватив? Смотря для чего. Для ассемблерных вставок и понимания того, как выглядит Си-прога изнутри, Pelles C идеален.

Все слышали о высоком пороге вхождения в Си. Происходит это по причине не очевидных языковых конструкций, разработанных Денисом Ритчи. Зачем и кому понадобилась эта неочевидность, другой вопрос. А вот избавиться от непоняток поможет дисассемблирование. obj2asm, (хоть и не без ошибок в данных, требующих ручной правки), выдаёт вполне красивый, ни с чем не совместимый :) и компактный листинг. В большинстве случаев, идеальный для учебных целей.

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

Глобальные инициализированные

//1.c (c) Tereshkov_OE

int LookAtMe=67;int AtMeToo=45; int AtMeTwo=29;

int num[]={ 112, 12, 1222, 12333, 11, 123, 1 };

void main(void)

{

LookAtMe = LookAtMe+AtMeToo+AtMeTwo;

}

------------------------------------

public _LookAtMe

public _AtMeToo

public _AtMeTwo

public _num

public _main

_DATA segment

_LookAtMe:

db 043h,000h,000h,000h ;

_AtMeToo:

db 02dh,000h,000h,000h ;

_AtMeTwo:

db 01dh,000h,000h,000h ;

_num:

db 070h,000h,000h,000h ;

db 00ch,000h,000h,000h ;

db 0c6h,004h,000h,000h ;

db 02dh,030h,000h,000h ;

db 00bh,000h,000h,000h ;

db 07bh,000h,000h,000h ;

db 001h,000h,000h,000h ;

_DATA ends

_TEXT segment

_main:

mov EAX,_LookAtMe

add EAX,_AtMeToo

add EAX,_AtMeTwo

mov _LookAtMe,EAX

ret

_TEXT ends

end

-------------------------------------

Глобальные не инициализированные

//2.c (c) Tereshkov_OE

int LookAtMe, AtMeToo, AtMeTwo;

int num[7];

void main(void)

{

LookAtMe=67; AtMeToo=45; AtMeTwo=29;

num[1]=112; num[2]=12; num[3]=1222;

num[4]=12333; num[5]=11; num[6]=123;

num[7]=1;

LookAtMe = LookAtMe+AtMeToo+AtMeTwo;

}

---------------------------------------

comm _num:byte:01ch

comm _AtMeTwo:byte:04h

comm _AtMeToo:byte:04h

comm _LookAtMe:byte:04h

public _main

_TEXT segment

_main:

mov dword ptr _LookAtMe,043h

mov dword ptr _AtMeToo,02Dh

mov dword ptr _AtMeTwo,01Dh

mov dword ptr _num[04h],070h

mov dword ptr _num[08h],0Ch

mov dword ptr _num[0Ch],04C6h

mov dword ptr _num[010h],0302Dh

mov dword ptr _num[014h],0Bh

mov dword ptr _num[018h],07Bh

mov dword ptr _num[01Ch],1

mov EAX,_LookAtMe

add EAX,_AtMeToo

add EAX,_AtMeTwo

mov _LookAtMe,EAX

ret

_TEXT ends

_BSS segment

_BSS ends

end

----------------------------------------

Локальные

//3.c (c) Tereshkov_OE

void main(void)

{

int LookAtMe, AtMeToo, AtMeTwo;

int num[7];

LookAtMe=67; AtMeToo=45; AtMeTwo=29;

num[1]=112; num[2]=12; num[3]=1222;

num[4]=12333; num[5]=11; num[6]=123;

_asm{

mov num[7],1}

LookAtMe = LookAtMe+AtMeToo+AtMeTwo;

}

-----------------------------------------

public _main

_TEXT segment

_main:

push EBP

mov EBP,ESP

sub ESP,02Ch

mov dword ptr -4[EBP],043h

mov dword ptr -8[EBP],02Dh

mov dword ptr -0Ch[EBP],01Dh

mov dword ptr -024h[EBP],070h

mov dword ptr -020h[EBP],0Ch

mov dword ptr -01Ch[EBP],04C6h

mov dword ptr -018h[EBP],0302Dh

mov dword ptr -014h[EBP],0Bh

mov dword ptr -010h[EBP],07Bh

mov dword ptr -0Ch[EBP],1

mov EAX,-4[EBP]

add EAX,-8[EBP]

mov -02Ch[EBP],EAX

add EAX,-0Ch[EBP]

mov -4[EBP],EAX

mov ESP,EBP

pop EBP

ret

_TEXT ends

end

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

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

//4.c (c) Tereshkov_OE

void one(void)

{

int LookAtMe, AtMeToo, AtMeTwo;

int num[7];

LookAtMe=67; AtMeToo=45; AtMeTwo=29;

num[1]=112; num[2]=12; num[3]=1222;

num[4]=12333; num[5]=11; num[6]=123;

_asm{

mov num[7],1}

LookAtMe = LookAtMe+AtMeToo+AtMeTwo;

}

void main(void)

{

int LookAtMe, AtMeToo, AtMeTwo;

int num[7];

LookAtMe=67; AtMeToo=45; AtMeTwo=29;

num[1]=112; num[2]=12; num[3]=1222;

num[4]=12333; num[5]=11; num[6]=123;

_asm{

mov num[7],1}

LookAtMe = LookAtMe+AtMeToo+AtMeTwo;

one();

}

------------------------------------

public _one

public _main

_TEXT segment

_one:

push EBP

mov EBP,ESP

sub ESP,02Ch

mov dword ptr -4[EBP],043h

mov dword ptr -8[EBP],02Dh

mov dword ptr -0Ch[EBP],01Dh

mov dword ptr -024h[EBP],070h

mov dword ptr -020h[EBP],0Ch

mov dword ptr -01Ch[EBP],04C6h

mov dword ptr -018h[EBP],0302Dh

mov dword ptr -014h[EBP],0Bh

mov dword ptr -010h[EBP],07Bh

mov dword ptr -021h[EBP],1

mov EAX,-4[EBP]

add EAX,-8[EBP]

mov -02Ch[EBP],EAX

add EAX,-0Ch[EBP]

mov -4[EBP],EAX

mov ESP,EBP

pop EBP

ret

_main:

push EBP

mov EBP,ESP

sub ESP,02Ch

mov dword ptr -4[EBP],043h

mov dword ptr -8[EBP],02Dh

mov dword ptr -0Ch[EBP],01Dh

mov dword ptr -024h[EBP],070h

mov dword ptr -020h[EBP],0Ch

mov dword ptr -01Ch[EBP],04C6h

mov dword ptr -018h[EBP],0302Dh

mov dword ptr -014h[EBP],0Bh

mov dword ptr -010h[EBP],07Bh

mov dword ptr -021h[EBP],1

mov EAX,-4[EBP]

add EAX,-8[EBP]

mov -02Ch[EBP],EAX

add EAX,-0Ch[EBP]

mov -4[EBP],EAX

call _one

mov ESP,EBP

pop EBP

ret

_TEXT ends

end

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

Те, кто прочёл предыдущую статью цикла, уже знают, что в Си нет call и gosub. В Pelles C это легко исправить.

//5.c (c) Tereshkov_OE

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

int main(void){

unsigned int a = 0;

b:

_asm{

cpuid

rdtsc

mov a,eax

call c

jmp b}

c:

printf(" %u\n", a);

_asm{ ret}

}

--------------------------------

extrn _printf

public _main

_TEXT segment

_main:

push EBP

mov EBP,ESP

sub ESP,4

mov dword ptr -4[EBP],0

LD: cpuid

rdtsc

mov -4[EBP],EAX

call L1B

jmp LD

L1B: push dword ptr -4[EBP]

push offset FLAT:@10

call _printf

add ESP,8

ret

mov EAX,0

mov ESP,EBP

pop EBP

ret

_TEXT ends

_DATA segment

@10:

db 020h,025h,075h,00ah,000h ;

_DATA ends

end

Для закрепления, разберём ещё пару примеров из предыдущей статьи.

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

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

int main(void)

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

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

---------------------------------------------

extrn _printf

public _main

_TEXT segment

_main:

push EBP

mov EBP,ESP

sub ESP,4

mov dword ptr -4[EBP],011h

jmp L22

LF: push dword ptr -4[EBP]

push offset FLAT:@12

call _printf

add ESP,8

dec dword ptr -4[EBP]

L22: cmp dword ptr -4[EBP],0

jne LF

mov EAX,0

mov ESP,EBP

pop EBP

ret

_TEXT ends

_DATA segment

@12:

db 025h,064h,00ah,000h ;

_DATA ends

end

--------------------------------------

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

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

int main(void)

{ int i = 17;

for (;i;){

i--;

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

-------------------------------------

extrn _printf

public _main

_TEXT segment

_main:

push EBP

mov EBP,ESP

sub ESP,4

mov dword ptr -4[EBP],011h

jmp L22

LF: dec dword ptr -4[EBP]

push dword ptr -4[EBP]

push offset FLAT:@12

call _printf

add ESP,8

L22: cmp dword ptr -4[EBP],0

jne LF

mov EAX,0

mov ESP,EBP

pop EBP

ret

_TEXT ends

_DATA segment

@12:

db 025h,064h,00ah,000h ;%d..

_DATA ends

end

------------------------------------

Оба счётчика являются аналогом цикла while i!=0. Разница в месте уменьшения аргумента, расположении команды dec dword ptr -4[EBP]. До тела основной функции или после него. do-while не случится даже, если вычеркнуть jmp L22.

Счастье будет не полным, если не увидеть, как передаются параметры функции

//6.c (c) Tereshkov_OE

int one(int c, int d)

{

return c+d;

}

int main(void)

{

int a, b;

a=3; b=4;

b=one( a, b);

}

---------------------------------------

public _one

public _main

_TEXT segment

_one:

push EBP

mov EBP,ESP

mov EAX,8[EBP]

add EAX,0Ch[EBP]

pop EBP

ret

_main:

push EBP

mov EBP,ESP

sub ESP,8

mov dword ptr -4[EBP],3

mov dword ptr -8[EBP],4

push dword ptr -8[EBP]

push dword ptr -4[EBP]

call _one

add ESP,8

mov -8[EBP],EAX

mov EAX,0

mov ESP,EBP

pop EBP

ret

_TEXT ends

end

Как видно, адресация опять ведётся относительно EBP. Параметры находятся с адреса EBP+8 и по четыре в сторону увеличения адресов. Локальные переменные с адреса EBP-4 и по четыре в сторону уменьшения адресов. Почему +8 и -4? Так оно устроено. Просто примем к сведению.

Ну вот. Методика и методология ясны. Если в программе нет явных ошибок, но она упорно демонстрирует свой несгибаемый характер, незаурядный интеллект, настаивает на собственном мнении, просто берётся функция printf, дисассемблер, отладчик или obj2asm и многое выясняется само.

На фоне визуальных сред программирования, изложенное здесь выглядит не слишком футуристично, даже приземлённо, но тем не менее. Ещё, желательно выяснить, для себя, чем отличаются cdecl от stdcall, while от do-while, case от if и т.д. Но это уже сами, :).

В нетах принято поливать фекалиями Си и растекаться мыслью по древу о преимуществах ассемблера по скорости и компактности. Вы сами всё увидели. Кто-нибудь пишет на асме лучше? Pelles C идеален. Будучи слинкованы с crtdll.dll в TCC, программки имеют размер от 1.5к. Что ещё надо?

Кстати, и обычный "Hello, World!\n" далеко не исчерпал себя. Как вам такой вариант?

//7.c (c) Tereshkov_OE

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

void aa (void){

char *s[] = {"Hello, World!\n"};

printf(*s);}

int main(void)

{

aa();

return 0;

}

И что он доказывает? А то, что ты хоть дерись, но символьный массив "Hello, World!\n", будь он хоть 255 раз локальным, всё равно будет лежать не в области стека, а в разделе глобальных переменных. Локальной будет только ссылка на него, что есть не одно и тоже. А в MSVC 2003, запись "Hello, World!\n" будет даже ещё и продублирована. Для надёжности. Вдруг, одна потеряется. И при этом, совершенно бесплатно :) Очень, очень надёжно. Стандарт.

Герберт Шилдт явно всего не знал. Хороший учитель должен уметь и пердеть, и сморкаться. Я так считаю. Иначе, это продажный пропагандист. Не даром, ракеты на взлёте в океан падают.

Вот, как должен выглядеть канонически верный функциональный Hello, World!, сейчас.

//8.c (c) Tereshkov_OE

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

int aa (void){

int s[] = {0x6c6c6548,0x57202c6f,0x646c726f,0x00000a21};

printf(s);

_asm {sub ESP,4

pop eax}}

int main(void){

int s = 0x000a6425;

printf(&s,aa());

return 0;}

Но каноникам, типа Петцольда, Шилдта и компании, нет до этого дела. Хороших учителей из них не получилось. Книжки проданы, деньги получены, тупые ритуалы расписаны, а скользкие темы лежат в стороне. Не было дела до "функциональности" и в 1973-м, нет и сегодня, не будет и завтра. Смысл и цель введения функционального программирования, отобрать у обезьян call и gosub. Всё получилось.

Предусмотрительность идиотов убивает. Мне так и не удалось объявить printf, как stdcall. Ни в Pelles C, ни в MSVC. И если первый честно об этом предупреждает, то MSVC 2003 тупо молчит и тихо делает своё чёрное дело. Зато бесплатно, от щедрот барских. Не только очень надёжный, но и скромный, даже стеснительный. Короче, альтернативы ассемблеру нет. Кстати, на этом идиотическом фоне, TCC, хоть и с недоразвитым инлайн ассемблером, тоже, очень не плох.

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

Кто как хочет, так и дрочит

Кому всё это надо, если есть PureBasic? Хороший вопрос. PureBasic, это тёплый и мягкий старт, простота использования. Особенно, для новичка. Всё, что написано о нём во второй статье цикла, чистейшая правда. Я и сам на ём пишу :)

Но PureBasic закрытый продукт. Процесс компиляции полностью скрыт и имеет это своей целью. Обычный пользователь всегда будет иметь дело только с тем, что торчит наружу. А чему торчать, решает только Фред, автор и владелец PureBasic.

Главная же забота Фреда, сохранить права собственности и привязанную к себе паству. Как можно дольше. Фред обидчив, молчалив и заносчив. С ним трудно разговаривать. Таким образом, PureBasic не изменится никогда. Компилируется кое-что, ну и ладно.

Главное отличие между PureBasic и Си в том, что стандартной библиотекой Си может пользоваться каждый и всегда. В Windows это crtdll.dll. Стандартной же библиотекой PureBasic не воспользуется никто и никогда. Даже сам Фред. Её просто нет и не будет.

Приобретя начальный опыт в PureBasic и TCC, рано или поздно, кроме простоты, захочется ясности, понимания и той самой свободы и абсолютной власти. Захочется, чтобы написанные утилиты работали и в Pico_XP. Чтобы ассемблерные вставки были естественным продолжением основной программы, а не шли параллельным курсом не известно куда и никогда не пересекались. Захочется приобрести навыки, которые помогут в трудоустройстве и учёбе в ВУЗе. Написать собственную операционку, наконец.

Всё это возможно в Pelles C. Высококлассный продукт. Почти стандарт. Который, на уровне компиляции, в большинстве случаев, не станет обламывать и сдерживать инициативу. Вперёд!

Однако.

Отгремели фанфары, стихли аплодисменты. И вот, моё новое, шедевральное творение в одну секунду превращает Pelles C v8 в полуфабрикат. Равно, как и Digital Mars C++, и Orange C 386. Кстати, Digital Mars и Orange C 386, тоже, проходят предложенную выше проверку на вшивость.

//^Z F6.c (c) Tereshkov_OE

int LookAtMe=0;

void main(void)

{

a:

LookAtMe = getchar();

putchar(LookAtMe);

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

goto a;

}

---------------------------------------

extrn _printf

extrn _putchar

extrn _getchar

public _LookAtMe

public _main

_DATA segment

_LookAtMe:

db 000h,000h,000h,000h ;....

_DATA ends

_TEXT segment

_main:

push EBP

mov EBP,ESP

L3: call _getchar

mov _LookAtMe,EAX

push dword ptr _LookAtMe

call _putchar

add ESP,4

push dword ptr _LookAtMe

push offset FLAT:@10

call _printf

add ESP,8

jmp short L3

pop EBP

ret

_TEXT ends

_DATA segment

@10:

db 020h,025h,064h,00ah,000h ; %d..

_DATA ends

end

Гениально! Не правда ли? Отбилдите это в Pelles C, Orange C или Digital Mars C++. Запустите. Введите "0123456789", энтер, чтобы убедиться, что всё работает. А теперь, нажмите кнопочку F6. Должен появиться ^Z. Теперь, энтер. Сюрприз-сюрприз. Рерайтеры.

Умоляет ли этот дефект достоинства Pelles C, Orange C и Digital Mars C++? Нисколько! И то не очень серьёзно и это. Просто, теперь их легкомыслие доказано. Как компиляторы, они всё равно хороши, наверное. Но только до уровня *.obj.

А далее, кому как повезёт. Начинается уровень функций и библиотек, которых вы не писали, не знаете, как они устроены, как работают, не понимаете их природу, а следовательно, ничего не гарантируете. Ну прямо мечта Герберта Шилдта.

И если *.obj от Pelles C и TCC, на примитивном уровне, можно использовать где угодно, то чудак из Digital Mars зачем-то тыкает везде __acrtused, __acrtused_winc, __acrtused_dll, __acrtused_con, __wacrtused, __wacrtused_con. Превращая *.obj от Digital Mars в никому не нужное, унылое г?вно. В добавок, в Digital Mars ещё и линкер не отвязно висит на snn.lib, как ведьма под перекладиной. А Digital Mars, это бывший Symantec C++. Макинтошцы 90-х, принимают поздравления. Компилятор языка D, видимо, такой же жлобский. Orange C 386 version 6.0.40, на уровне объектных файлов, вообще ни с чем не совместим. Возможно, что и с самим собой. Отдельная планета.

Мораль той басни такова. MSDN: «If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.» Если Microsoft, во всей её красе, великолепии и могуществе, при наличии всех исходников и всей полноты информации, расписываясь в собственной не состоятельности, не может написать адекватные CRT и CreateThread и поэтому, may=позволяет, даёт разрешение CRT самой делать то, что той вздумается, то куда уже нашим доморощенным писакам, изобретающим намеренно ни с чем не совместимые велосипеды. И первый ответ которых на любой вопрос - А зачема тибе? Отъ?бись!

Действительно, а зачема? Pelles C не может открыть файл с именем «^ZF6.c». Notepad может, а Pelles C и MSVC - нет. И я, даже, не буду спрашивать у авторов, почему. И так знаю. По причине большого ума господ аффторов. Отвяжусь сразу.

Лучше рассмотрим простую программку за авторством Дениса Ритчи. Чувствуете неизъяснимый трепет? Рука мастера! Я её немного подправил, вы уж извините и вот почему. Ни для кого не секрет, кто такие копирасты. И если сказать, что все программисты на Си и вообще все программисты копирасты, многие обидятся. Но, когда человек бездумно помещает в программу строку #include <stdio.h> или использует чужую библиотеку, что он, по вашему, на самом деле делает?

#define EOF (-1)

main()

{

int c;

c = getchar();

while (c != EOF) {

putchar(c);

c = getchar();

}

}

На этот монументальный шедевр натыкаешься сразу, как только начинается обучение. Как утверждает Ритчи, «при наличии функций getchar и putchar, ничего больше не зная о вводе-выводе, можно писать удивительно много полезных программ.» А через 20 лет ему вторит Герберт Шилдт, «нужно лишь знать, что делает та или иная функция, не интересуясь,как именно она выполняет свою задачу.» Какая правильная и удивительно верная мысль у обоих, «ничего не знать»! Файлы типа stdio.h, как раз для этого и предназначены. Особенно, на начальном этапе обучения. Меньше знаешь, крепче спишь.

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

Может быть, ввод с клавиатуры не является файлом? Или у такого файла не бывает конца? Самое время вспомнить о найденной мной волшебной кнопочке F6, которая как раз этот самый конец фала EOF и формирует. Запускаем, тыкаем F6, ентер. Программка благополучно завершается. Выход найден? - нет! Набираем 777, F6, ентер. Программка не завершается, строка не переводится. Набираем 777, F6, F6, F6, ентер. Выглядит, как «777^Z^Z^Z». Программка не завершается, строка не переводится, на выходе«777_», как и в прошлый раз.

Поздравим себя. Только что выявлен очевидный баг getchar() из msvcrt.dll. Если в буфере более одного символа и символ EOF не первый на выход, EOF и всё, что после него, включая ентер, становится не доступным и на выходе не появляется. Чтение из буфера прекращается и getchar() переходит в режим ожидания нового ввода с клавиатуры. Этот дефект не возможно обойти и программки, типа написанной Денисом Ритчи, становятся совершенно бесполезными. Обнуляет ли при этом свой буфер getchar() не известно. Возможно, что нет.

У вежливых людей, дефекты принято называть особенностями и сопровождать их радостными воплями - «надо же, как интересно получилось!», наполнять бокалы и праздновать, праздновать! А как известно из второй статьи цикла, весь официальный фарш в мире Си пишется под одну копирку, присланную ЦРУ и Пентагон т.е. централизованно. Стало быть, остальные библиотеки CRT такие же. Все до одной. Без обид, но копирасты.

Если предположить, что getchar() из msvcrt.dll работает правильно, в соответствии со спецификациями, то значит Денис Ритчи был плохим программистом, не тестировал своих программ, ничего не знал и знать не хотел, и не скрывал этого, на пару с Гербертом Шилдтом. И вас тому же и учил.

Но если и Ритчи молодец. И getchar() из msvcrt.dll работает верно, в соответствии со спецификациями, то значит в ANSI Си комитете заседают одни дураки, которые эти спецификации пишут. И гнать их оттуда давно пора взашей и грязной метлой, вместе с их спецификациями. Что верно, решать вам. Тем более, что внутренних протестов, ни по одному из трёх вариантов, как-то не возникает.

Уже давно, проели мозги необходимостью спецификаций, комментариев и хорошего стиля. Так почему же Ритчи сам не описал правила использования его чудесной программки. Мол тыкаешь, тыкаешь, тыкаешь, а в конце, значит, ентер-F6-ентер и good buy. Ну, видимо, потому, что Великий Ритчи никогда свою программку сам и не запускал. Учить, это одно. А вот делать всё самому, это совсем другое.

И это простейшая getchar(), которой уже почти 50 лет. В Виндовс и Линух таких функций миллионы. И все они тщательно протестированы. Очень тщательно. Все до одной. Так же, как и getchar(). Вот и ракеты падают, в доказательство.

Ну всё есть. Вот только пользоваться нечем. В смысле, не возможно. И всё вместе, это называется высоким порогом вхождения. А не пошли бы вы?! Вместе с вашими порогами. Ну какие Spectre, Meltdown, Prime95 или PortSmash CVE-2018-5407, если даже простейшая getchar() толком не работает. Придуркам, работающим за колбасу, хакеры не страшны. Такие придурки сами, хуже хакеров.

Можно работать через любой прокси, но Google Chrome всегда заботливо отошлёт в Google.com ваш реальный IP. И если провайдер подвесил весь микрорайон на один адрес, развлечение вида

гарантировано через каждые 10 минут.

У меня два варианта. Либо провайдер тупой, либо Google. Но не стоит заморачиваться. Оба варианта подходят. Google повзрослел. Или захерел. Пора менять смартфон обратно на велосипед и удочки :)

GCC, пройдёт лишь кающийся:

.comm _LookAtMe, 4, 2

.comm _AtMeToo, 4, 2

.comm _AtMeTwo, 4, 2

.comm _num, 200, 5 <<<<<<<<<<<<

.text

.globl _main

.def _main;.scl 2;.type 32; .endef

_main:

pushl %ebp

movl %esp, %ebp

andl $-16, %esp

subl $16, %esp

call ___main <<<<<<<<<<<<<<<<<

L39:

movl $0, _you

movl $0, me

subl $12, %esp<<<<<<<<<<<<<<<<<<

pushl $LC1

call printf

addl $16, %esp

subl $12, %esp<<<<<<<<<<<<<<<<<<

pushl $LC10

call printf

addl $16, %esp

subl $12, %esp<<<<<<<<<<<<<<<<<<

pushl $LC11

call printf

addl $16, %esp

Безупречно хороших и идеально сговорчивых компиляторов нет и не существует. Не предвидится даже для обучающего применения. Ну, может быть, HLA или Fasm. Но HLA, имея широкие возможности для собственной настройки и изучения вами английского:), труден для освоения и нигде не используется. А Fasm, будучи в том числе и одной из утилит HLA, не шьёт объектные файлы, хоть и знает формат elf.obj.

За 20 лет существования, под Fasm не написано ни одного совместимого дисассемблера или парсера с других дисассемблеров и не будет. Видимо, чтобы красноглазики развивали свой кругозор и не зацикливались на одном единственном. Good reason.

Главный признак хорошего компилятора наличие трёх главных опций. Первая -do_what_i_told. Вторая -exactly_what_i_told. И третья -close_your_mouth_right_now. Хотя, опция -w, как правило, везде уже есть.

Не нравится? - не пользуйся! Yes? А я и не пользуюсь. Точнее, пользуюсь. Но только тем, что воспринимаю. Но странно, что другие пишут их компиляторы и операционки только за тем, чтобы я ими не пользовался. Не понимал, не покупал и никому не советовал.

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

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

Линкуйте свои *.obj только с проверенными *.dll и библиотеками. Работайте только в проверенных вами компиляторах. Дисассемблируйте. Скомпануйте свой компилятор сами. И будете работать спокойно, сбережёте силы, нервы и здоровье. Будущее начального обучения, всё равно, за PureBasic и TCC >:). Жаль только потраченное впустую время. По совокупности признаков, размер, возможности и удобство, лично я, двумя руками голосую за Pelles C в формате pocc.exe. Продолжение следует.

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