Кто угодно может пнуть мёртвого льва. Мёртвый лев не рыкнет на наглеца. Мёртвый лев не откусит ему ногу «по самое не хочу», хотя стоило бы. Лев мёртв, и теперь его может пнуть каждый ишак, что конечно же не показывает превосходство ишака над львом. Эта статья будет полна негодования и ненависти. Кровь ещё не закончила кипеть от негодования. Но, разумеется, помимо эмоций будут и сухие объективные факты, немножко исследования и расстановка точек над i. В интернете кто-то не прав... опять...
Существует целый ряд инструментов, технологий и вообще вещей, которым по какой-то непонятной вселенской несправедливости не повезло: нашлась масса непонятных людей, которые по какой-то необъяснимой причине начали распускать про эти инструменты/технологии/вещи разные небылицы, идиотские фейки, слухи и прочий порочащий репутацию «компромат». Можно не переживать, если речь идёт о технологии, которая находится «на пике» — у неё будет большое community и правда восторжествует. Совсем другое дело, когда речь идёт о чём-то, что далеко не на пике, чья минута славы в прошлом (возможно даже давно в прошлом) — здесь мёртвый «лев» не может дать сдачи, и что самое обидное, что в какой-то степени «лев» сейчас отчасти потому и мёртв, что ещё при его жизни началось необоснованное распространение всяких бредовых поверий и мифов про него. И сегодня речь пойдёт об одном из таких случаев.
Всё началось со статьи Что было бы, если BASIC развивался вместо C и Python. 06:30 утра, я едва продрал глаза, беру смартфон, чтобы посмотреть, нет ли важных уведомлений, и тут в новостной ленте попадается эта статья. И я просто никогда не могу пройти мимо подобных статей, потому что есть незыблемое правило: если на Хабре появляется статья про какой-нибудь древний Бейсик, то в комментариях к ней обязательно, гарантированно и непременно вспомнят QB и VB, и появятся странные люди со странной мотивацией,которые будут нести свою ша��лонную ахинею про то, что QBasic/QuickBasic и (тут особенно обидно) Visual Basic, дескать, недо-инструменты, потому что не умеют в компилирование, а умеют лишь интерпретировать исходный код.
Тут надо сделать ремарку, что у меня особые отношения с Visual Basic. Ещё примерно с 1998-го года был (и есть по сей день) интернет-ресурс VBStreets, который был одним из самых подробных ресурсов и самых больших сообществ, посвящённых VB/VBA/VBScript/ASP и т.п. В былые времена мы проводили конкурсы совместно с Microsoft, мы издавали бумажные книги совместно с BHV и Ozon. И уже много-много лет я являюсь бессменным администратором этого ресурса. Сейчас в силу положения VB ресурс находится скорее в анабиозе, но речь не об этом. Я не просто администратор этого сайта, я в силу этого обстоятельства потратил какое-то умопомрачительное количество времен на исследование внутреннего устройства VB, на реверс-инжиниринг и тому подобные вещи, так что я знаю внутреннее устройство и внутренний мир VB/VBA как никто другой, и должен вам сказать, этот продукт, эта технология таит массу интересных вещей (если повезёт со свободным временем, я расскажу об этом в отдельных хабра-статьях — рассказы обещают быть очень интересными). И таким образом, досконально зная внутреннее устройство, я не могу спокойно проходить мимо какой-то вопиющей ахинени, которую пишут в частности про VB. На QB я конечно тоже когда то (очень) давно понаписал массу кода, однако QB я никогда досконально не исследовал и его внутренний мир я не знаю так хорошо. Тем не менее, по старой памяти и из ностальгических чувств, когда на QB льют лживые помои, я тоже пройти мимо молча не могу.
Так вот, как вы думаете, обошлось ли в этой статье, ссылку на которую я дал выше, точнее в комментариях к ней без бредовых баек про интерпретаторы? Конечно же нет! Никогда без таких комментариев такие статьи не обходятся.
Вот и сейчас хабра-юзер@Kreyне смог пройти мимо и решил прокомментировать имевшееся в исходной статье утверждение:
>>QuickBASIC/Basic Compiler от Microsoft, который переводил код BASIC в исполняемый .EXE
Ничего он не переводил, а просто упаковывал исходник в виде ресурса и прицеплял его к exe интерпретатора
И вот тут у меня возникает вопрос. Несколько вопросов.
Какая сила или какая мотивация заставляет людей писать подобные комментарии?
Почему когда она приходят с таким утверждением, они не начинают его со слов «Одна бабка сказала» или «Я где-то от каких-то мутных людей слышал, что ...» или «На заборе было написано...»
Если они считают, что эта информация проистекает из достоверного источника, почему не указывают этот достоверный источник, а если у них эта информация в голове на правах предположения или где-то услышанной байки, почему они не перепроверяют вброс, который собираются опубликовать? Вообще-то хорошим тоном считается отвечать за свои слова и проверять достоверность того, что собираешься сказать.
Знаете что я думаю? Я думаю это отголоски холиваров 35-летней давности. Был, допустим, холивар (религиозная война) между сторонниками QuickBasic и Turbo Pascal примерно 35 лет назад. И поскольку это религиозная война, а не аргументированный спор, то ни с одной стороны ни с другой не было знаний и действительной аргументации, а была только слепая вера в превосходство своей любимой игрушки. Нужно было просто сделать вброс, направленный на противную технологию, и чем громче и унизительнее он был звучал, тем лучше. Всё равно никто ничего не будет проверять и исследовать, ведь на то это и религиозная война. «— Я верую, что QuickBasic интерпретируемая какашка, и мне плевать, как там на самом деле!». Холивар давно угас, но отдельные кричалки и посылы словно информационные вирусы путешествуют до сих пор.
Спойлер для самых нетерпеливых
Ничего он [QuickBASIC] не переводил, а просто упаковывал исходник в виде ресурса и прицеплял его к exe интерп��етатора
Достоверность этого утверждения — 0%. Это чушь, миф, байка. Дальше будет очень короткий, поверхностный, но действующий способ проверить, что это не правда.
Так вот: QuickBasic действительно умел порождать на выходе EXE-файлы. Которые могли работать отдельно и самостоятельно от IDE. И очень обидно будет/было бы за QuickBasic, если бы он под видом генерации самостоятельного EXE просто порождал бы копию программы-интерпретатора, в ресурсы которого засовывал исходник программы. Кстати, во времена 16-битных EXE-файлов реального режима не было понятие ресурсов, было понятие оверлеев. Это просто выглядит как какой-то обман, надувательство.
И вот что удивительно: я никогда на самом деле не копал внутрь QuickBasic. Я не смотрел и не проверял, что там содержится внутри сгенерированного (скомпилированного) EXE-файла. Вдруг там реально зашит исходник, который просто интерпретируется? Нет! Всегда хотелось считать и, даже стоит сказать иначе, не просто хотелось считать, а было логичным считать, всё всё устроено не так — что внутри EXE-именно машинный код нашей бейсик-программы, а не просто интерпретатор в связке с пришитым к нему исходником.
Давайте включим логику: это сейчас безумные времена, когда в порядке вещей на веб-странице иметь JS-скрипты, которые являются реализацией интерпретатора какого-то другого языка. В те годы интерпретатор был слишком дорогим удовольствием. Он жрёт много памяти. Он требует тактов на своё исполнение. Кто хоть раз писал интерптератор чего либо, знает, что это просто огромное дерево ветвлений и каскады if-ов для пров��рки всех возможных вариаций синтаксических конструкций на предмет отклонения. Нужно обработать все возможные отклонения от правильного синтаксиса интерпретируемого вами языка и выдать что-то вразумительное в случае ошибки (вы же не будете выдавать Syntax error на всё подряд?)
В таком случае интерпретатор был бы довольно массивным, и, как ни крути, он был бы обязан включать в себя хотя бы «текстовки» ошибок (сообщений об ошибках) для всех возможных вариантов нарушения синтаксиса. И даже за счёт одних только этих текстов сообщений об ошибок он уже получился бы прилично раздутым. А теперь представьте, что кто-то хочет написать 10 абсолютно простых, миниатюрных программок на QB. Тогда получается, что каждый EXE-файл содержал бы вшитую в него логику интерпретирования и ещё пачки строковых последовательностей с сообщениями об ошибках? Выглядит не очень логичным, но умозрительная рациональность или логичность какого-то подхода так себе аргумент «за» или «против» того, насколько такой подход соотносится с реальностью.
Поэтому давайте представим, что я тот самый человек, который хочет сделать вброс и заявить, что скомпилированная QB-программа на самом деле не скомпилированная, а просто склейка заранее заготовленного интерпретатора и исходного кода, который нужно интерпретировать. Или наоборот, я хочу сделать вброс, опровергающий такое общеустоявшееся мнение. Как бы там ни было, прежде чем делать вброс, я бы проверил свои тезисы, чтобы не сесть в лужу.
Я бы запустил QuickBasic и написал простенькую программу в духе Hello world:

Хотя это не важно в данном случае, выглядит результат работы этой программы вот так:
Но нам интересно вовсе не это, нам интересно скомпилировать эту программу в EXE-файл:
Обратите внимание, что здесь нам предлагают выбор: породить на свет EXE-фа��л, нуждающийся во внешнем BRUN45.EXE, или породить полностью самостоятельный или независимый EXE-файл. Давайте подыграем распространителям фейков и поверим в то, что тот самый BRUN45.EXE и есть интерпретатор, и нам предлагают либо вшить интерпретатор в сам итоговый EXE-файл, либо оставить в выходном EXE-файле маленький кусочек со ссылкой на и подгрузкой внешнего интерпретатор. Выберем вариант с зависимостью от внешнего BRUN45.EXE — в таком случае в нашем EXE-файле должен якобы остаться только исходный код программы и небольшой кусочек машинного кода, подгружающий интерпретатор из внешнего файла.
Компилируем! А теперь берём hex-редактор HIEW и смотрим содержимое только что сгенерированного EXE-файла. Прокрутимся в самый конец, ведь именно там должен быть исходный код, бережливо засунутый туда комп��лятором для последующей интерпретации в момент запуска:
Упс! Где же чёртов исходный код? Где же так милые сердцу ключевые слова DECLARE, SUB, END, FOR? Где же SOUND и PRINT? Где наш милый исходный код? Кажется, им тут и не пахнет! Может он не в конце, а в начале?

Но и в начале его нет! Ни в каком месте полученного EXE-файла исходного кода на языке QuickBasic не наблюдается и нет вообще.
Его здесь нет: ни целиком. Ни в виде отдельных процедур. Ни в виде отдельный statement-ов. Может хотя бы идентификаторы из нашего кода найдутся? Поищем-ка идентификатор HELLO и идентификатор MyCoolVariableI (именно для этого там в цикле не просто каноническое «i», а переменная со столь длинным именем):

Но никаких следов идентификаторов «HELLO» и «MyCoolVariableI» в содержимом EXE-файла не находится даже близко:

Как же так? Ведь нас уверяют, что в EXE-файл просто тупо вшивается исходник, а при запуске EXE-файла встроенный (или не встроенный, а лежащий рядышком?) интерпретатор начинает его интерпретировать? Но на поверку оказывается, что в EXE-файле не обнаруживается исходный код ни в каком виде. Не то, что даже в виде отдельных строк, а даже отдельно взятые идентификаторы в EXE-файл не попадают.
Но подождите, нам могут сурово возразить и обвинить нас в манипуляции. Ведь изначальный посыл звучал так:
Ничего он не переводил, а просто упаковывал исходник в виде ресурса и прицеплял его к exe интерпретатора
Здесь не сказано, что компилятор просто копировал исходный код в EXE-файл как есть, а сказано, что он упаковывал его. Наверное имеется в виду сжатие каким-нибудь PKZIP или LZW. Ведь это конец 80-х, и нам не на что больше тратить драгоценные такты CPU, кроме как на сжатие и разжатие исходного кода. <sarcasm>Тогда абсолютно логично, почему в содержимом файла мы не видим зашитого исходника — он сжат алгоритмом сжатия!</sarcasm>

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

Так что нет, версия, что исходный код при компиляции сжимается, а при запуска готово EXE-файла распаковывается в первозданный вид и передаётся интерпретатору — не оправдывается.
Но подождите вновь! В современном сумасшедшем мире давно есть такая вещь как «минификация»: фронтендеры скармливают свои JS-файлы минификаторам, которые удаляют все пробельные символы, вырезают комментарии, заменяют идентификаторы на однобуквенные (или имеющие минимальное достаточно число букв), но строковые литералы при этом остаются как есть. Может и здесь что-то такое же происходит? Может создатели QB пошли дальше и все ключевые слова и идентификаторы заменили на бинарное представление, а строки остались? Может именно это имелось в виду под упаковкой?
А что если мы пойдём ва-банк? Для этого мы немного изменим наш тестовый код:
Мы модифицируем цикл FOR так, чтобы он пробегал по числам не от 1 до 13 (с дефолтным шагом 1), а от 0xBEEF до 0xCAFE с шагом 0x0123.
Посколько идентификатор «MyCoolVariableI» всё равно в итоговом EXE-файле не обнаруживается, мы заменим его на каноническое «i».
А ещё мы добавим процедуру EatMarker, принимающую 32-битное число и выводящую его:
SUB EatMarker (xxx AS LONG)
PRINT "PASSED MARKER:"; xxx
END SUB
И вызовем её из основного тела программы, передавая примечательное число 0xDEAD4FEE (мёртвый за плату).
В итоге мы получаем вот такую маленькую программку:

Смысл использования примечательных чисел (BEEF, CAFE, 0123, DEAD4FEE) в возможности поискать их в хекс-редакторе и посмотреть, как они вплетены в окружающие их бинарные данные. Компилируем этот код и опять открываем его в hex-редакторе HIEW.
Мы зайдём с конца. С числа 0xDEAD4FEE. Это 32-битное число, а между тем, QB генерировал 16-битный машинный код для 16-битного реального режима работы процессора. Если компилятор генерирует не машинный код, а какое-то промежуточное его представление, которое затем интерпретируется (насколько вообще может быть применён термин интерпретация к сильно переваренному коду, значительно отличающемуся от исходного) — то мы увидим в содержимом EXE-файла это 32-битное число как есть, с той лишь оговоркой, что это будет little-endian представление, то есть байты EE 4F AD DE. Если же я прав, и компилятор компилирует самый обыкновенный машинный код (как делал бы это компилятор C++), то мы увидим упаковку числа 0xDEAD4FEE в стек за два приёма (поскольку режим работы процессора — 16-битный).
Итак, компиируем, открываем в HIEW и пытаемся найти следы 0xDEAD4FEE:

И находим! Но находим не как 4 смежных байта, а сначала младшую часть (0x4FEE => байты EE 4F), а затем спустя 4 байта и старшую часть (0xDEADxxxx => байты AD DE). Давайте-ка не будем тянуть интригу, нажмём F4 (Mode) и выберем режим Disasm.
У машинного кода x86 нет битов самосинхронизации (в отличие от UTF-8, например), поэтому вывод дизассемблера зависит от того, откуда будем начинать дизассемблировать. Вообще-то я ожидал, что там будет использоваться инструкция push, но реальность оказалось чуть иной:

Что же мы тут такое видим? А мы видим, что эти байты EE 4F и AD DE является частью обычного, рядового, простого и привычного машинного кода x86! Никакой это не исходный код. Ни в сыром виде. Ни в упакованном/сжатом виде. Ни в минифицированном виде. Ни в промежуточном представлении.
Это самый настоящий машинный код, исполняемый код x86:
mov word ds:[0dc8], 4FEEh ; помещаем мл. часть числового литерала во вр. перем.
mov word ds:[0dca], DEADh ; помещаем ст. часть числового литерала во вр. перем.
mov ax, 0dc8 ; пушаем адрес временной переменной
push ax ; пушаем адрес временной переменной
call 0000:0109 ; и вызываем процедуру EatMarker
Я чуть-чуть ошибся, ожидая увидеть два push-а, а разгадка проста: на самом деле в QuickBasic аргументы процедур/функций всегда передаются ByRef (по ссылке), а не по значению (ByVal), то есть на физическом уровне передаётся не значение переменной, а адрес этой переменной, у��азатель на неё. Если же на уровне исходного кода в процедуру передаётся не переменная, а непосредственное значение (числовой литерал), то создаётся временная переменная, куда сохраняется числовой литерал, и указатель на эту временную невидимую переменную передаётся в вызываемую процедуру.
Но может это только для вызова процедур генерируется настоящий машинный код? А для control structures типа ветвлений и циклов используется интерпретация? Вспомним про наш FOR-цикл и вернёмся к нему: у нас там были примечательные числа 0xBEEF, 0xCADE, 0x0123. Поищем-ка их.
И конечно же мы их найдём. Далеко идти не надо, достаточно чуть-чуть прокрутиться наверх:

На этом экране в этом дизасм-листинге виден весь код основного тела нашей QB-программы. Видно, что и для цикла FOR тоже используется машинный код. У нас цикл FOR от значения 0xBEEF до 0xCAFE с шагом 0x0123. И мы здесь видим, что переменная «i» у нас живёт по адресу DS:[0DC6].
В начале цикла FOR инициализируется начальное значение: mov ax, 0BEEF. Сразу же идёт джамп на код проверки условия (продолжать ли цикл) — значение переменной i должно быть не больше 0xCAFE. И действительно, переменная i сравнивается с 0xCAFE (cmp ax, 0CAFE). Если условие выполняется, идёт условный джамп (jle) на тело for-цикла, если нет — выполнение переходит к следующей за циклом FOR инструкции. В теле цикла у нас в исходном коде строка «HELLO i%». Переменная i% передаётся в процедуру HELLO — и в машинном коде мы это видим (вместо значения переменной i% на стек кладётся её адрес), после чего i% инкрементируется на 0x123. За циклом мы видим вызов PRINT "We are done with..., а вслед за ним — вызов процедуры EatMarker, куда передаётся число DEAD4FEE (через временную переменную).
То есть буквально вот этот исходный код:

Превратился в 73 байта машинного кода. Исполняемого кода x86-процессора. Можно прямо сопоставить группы машинных команд строкам исходного кода:

В общем, что мы здесь видим? Исходный код на языке QuckBasic скомпиловался сразу и непосредственно в машинный код, в инструкции x86-процессора для 16-битного реального режима. В такие же инструкции и в такой же код, в каком бы скомпилировался for-цикл, будь он написан на Си. Никакой упаковки исходного кода здесь нет, никакой интерпретации кода здесь нет — интерпретировать эти инструкции будет процессор, его декодер команд.
А теперь вспомним изначальную цитату:
Ничего он не переводил, а просто упаковывал исходник в виде ресурса и прицеплял его к exe интерпретатора
Немая пауза... Мы открыли исполняемый файл и нашли там, чёрт его возьми, исполняемый код! Кто бы мог подумать, что внутри исполняемого файла будет исполняемый код? Никогда такого не было и вот опять... И обратите внимание, что вместо использования какого-нибудь отладчика я специально выбрал простейший hex-редактор HIEW, чтобы ни у кого не было соблазна сказать, что найденный код и найденные инструкции образовались в памяти процесса в результате р��спаковки/интерпретации/компиляции кода в процессе запуска EXE. Я показываю то, что уже есть в EXE-файле, не допуская никаких самораспаковок.
И это далеко не единственный комментарии такого толка в той статье. Вот «прекрасный» спор между @checkpointи @PerroSalchicha:
Каким бы хорошим не был Basic, это всё равно интерпретируемый язык. Взрослые парни пишут на компилируемых языках. В этом смысле все питонисты - дети. ;)
На что @PerroSalchichaсправедливо возмущается:
Бейсик стал компилируемым ещё в 1980-х, с появлением Турбо Бейсика :)
Но @checkpoint не успокаивается:
AFAIK, он не был компилируемым как таковым, просто среда упаковывала интерпретатор с кодом в один .EXE файл. Нормальных компиляторов с "Васика" я не встречал.
В этот момент воин света @PerroSalchichaпереходит на сторону тьму и тоже начинает писать ахинею:
По-моему, TB был как раз честным компилятором. Исполняющую среду с кодом в один EXE паковал Visual Basic
И @checkpointподытоживает:
Скорее всего Вы правы, мне на глаза попадались только QBASIC и Visual Basic. Оба умели генерировать .EXE, но не настоящий. :)
Изумительно...

С тем, что QuickBasic всё-таки умеет генерировать .EXE и при этом это настоящий .EXE, самый настоящий, более настоящего не придумать — мы вроде бы разобрались. Но QuickBasic никогда меня особо не интересовал. Совсем другое дело — Visual Basic — внутреннюю кухню которого я изучил вдоль и поперёк.
Эти дурацкие байки,я клянусь, всплывают в абсолютно любой статье на Хабре, где хоть как-то затрагиваются всевозможные бейсикоподобные языки. Эта паста постоянно пережёвывается кем-нибудь и передаётся из уст в уста.
Мне хочется воззвать хотя бы к логике людей, которые тиражируют подобные байки. Если люди в Microsoft в 80-х годах сумели сделать и осилили такую вещь как генерацию настоящих полноценных EXE-файлов с машинным кодом в таком в общем-то несерьёзном и игрушечном продукте, как QuickBasic, неужели вы хоть на минуту допускаете, что в таком монструозном продукте как Visual Basic кто-то сделал бы такую дичь как засовывание в EXE-файл исходника в склейке с интерпретатором? Разве хоть немного правдоподобным это выглядит?
Чтобы понимать, что за зверь такой VB, нужно понимать, как он родился и из каких проектов у него растут ноги. Здесь всё не так просто. Есть два пути разобраться в этом вопросе: короткий и более углубленный. Предполагая, что у вас не так много времени, расскажу об этом коротко, а затем, для тех, кто хочет узнать поглубже, дам несколько ссылок на свои переводы и статьи, проливающие свет на этот вопрос. Так вот, если говорить коротко:
На рубеже десятилетий (80-е/90-е) в Microsoft существовало такое подразделение, как DABU — Data Applications Business Unit. В ведении DABU тогда находилась разработка СУБД под название Omega. Кроме того, именно DABU занималась разработкой и поддержкой QuickBasic. В составе СУБД Omega должен был быть бейсикоподобный язык, и он тогда назывался EB, что расшифровывалось как Embedded Basic. Речь не идёт о бейсике для электроники типа Raspberry Pi (если бы оно в то время существовало), речь идёт о возможности встраивать что-то бейсикоподобное в своим приложения. Не трудно догадаться, что проект СУБД Omega со своим EB развился потом в MS Access/JET/MS SQL. Помимо этого, подразделению DABU было поручена разработка инновационной среды для объектно-ориентированной разработки приложений под Windows с бейсико-подобным языком программирования под кодовым названием Silver.
В то же время небезызвестный Joel Sploslky в одной из своих статей рассказывал, как в своё время Билл Гейтс решил, что в Excel должен был встроенный язык программирование, и что это должно быть нечто бейсикоподобное. Именно Джоэлю было поручено написать спецификацию для будущего языка программирования, который на начальном этапе тоже назывался EB — только команда расшифровывала это как Excel Basic, то есть бейсик для Excel. Джоэль, в частности, гордится, что принёс в спецификацию 4 инновационных момента: новый тип переменных Variant, который мог бы хранить что угодно (потому что ячейка Excel может хранить что угодно), поддержку позднего связывания наряду с ранним связыванием в ООП-вызовах, конструкцию For Each, позаимствованную из csh и конструкцию With...End With, позаимствованную из Паскаля.
Одновременно с этим в те же года некто Алан Купер — IT-предприниматель — разработал концепцию инструмента под названием Ruby (никакого отношения это не имеет к языку программирования Ruby). Ruby у купера — концепция (и не только концепция, но и рабочий прототип) менеджера рабочего стола, где пользователю даровали возможность самому себе в режиме конструктора делать то окружение, которое ему будет удобно.В Ruby была концепция так называемых «штуковин» (gizmos), эти штуковины можно было легко рисовать в любом месте на desk-ах, связывать друг с другом, привязывать к разным событиям и свойствам штуковин различные действия, например, при щелчке на пункт в списке могла запускаться какая-нибудь команда ОС/программа. В Ruby не было никакого своего языка программирования: ни бейсикоподобного, ни Си-подобного, ни какого либо вообще. Алану Куперу удалось выгодно продать эту концепцию и свой рабочий прототип Биллу Гейтсу в Microsoft.
Таким образом, сначала EB из проекта Omega попал и начал свою жизнь в рамках проекта Silver (IDE для Windows с ООП и бейсикоподобным языком). Потом проект Silver стал частью проекта Excel Basic (снова EB). Далее этот EB развился в то, что теперь известно как VBA.
Не отказываясь от первоначальной идеи проекта Silver (бейсикоподобная IDE с ООП для разработки Windows-приложений), Microsoft решило подружить проект EB с проектом Ruby — результат слияния и сращения двух изначально независимых продуктов сперва назывался Thunder, но позже отдел маркетинга решил иначе: EB стал называться Visual Basic for Applications, а Thunder, который представлял собой результат слияния EB и Ruby, стал называться просто Visual Basic.
Для тех, кто хочет детальнее изучить эти аспекты истории становления продуктов Microsoft, информация под спойлером:
Расширенная информация
Я давно написал статью о слиянии EB и Ruby, но прежде чем приступать к этой статье, рекомендуется подготовить свой мозг и прочитать две других, которые являются переводами статьей, написанных непосредственно теми людьми, кто работал в Microsoft и работал над Silver/Omega/EB/VBA/Ruby/VB. Читать рекомендую именно в таком порядке:
Почему меня называются «отцом Visual Basic'а — перевод одноимённой статьи Алана Купера, придумавшего систему Ruby.
Thunder... рождение Visual Basic — перевод статьи Скотта Фергюсона, человека из DABU, кто имел отношение к разработке Silver/Thunder/EB. Это буквально взгляд на VB с другой стороны тоннеля, потому что к уже имеющемуся ядру бейсикоподобного языка поставили задачу присоединить чужеродную систему — Ruby.
Ruby + EB = Visual Basic — моя статья, полагающаяся на материалы предыдущих двух, а также статьи Joel Spolsky и собственную аналитику и результаты глубоких исследований внутреннего устройства VB/VBA.
В итоге получается, что генеалогическое дерево VB очень сложное и переплетённое, и вообще, скорее даже не дерево, а просто граф — VB является побочным продуктом слияния VBA (EB) с другой независимой и купленной ранее у Алана Купера концепцией/идеей/технологией — Ruby.
А теперь важные вещи, касающиеся VBA/EB и VB в плане генерации кода:
EB/VBA никогда исторически не был интерпретируемым. Собственно, даже QuickBasic не интерпретирует исходный код в момент запуска программы под отладчиком — код интерпретируется в момент его вводы в редакторе кода и в дальнейшем внутри QB представлен не как код, а как нечто более абстрактное и предобработанное (но не как AST). EB унаследовал эту концепцию, и тоже не интерпретировал код (как это делал, скажем VBScript).
Поскольку EB должен был быть кроссплатформенным, поскольку, Excel, например, поставлялся так же и под Mac, а под Mac была своя аппаратная архитектура, то EB исторически никогда не компилировался в машинный код. Вместо этого авторы EB разработали свою виртуальную машину (почти как в Java) со своей собственной системой высокоуровневых команд. Байт-код для этой виртуальной машиной назывался P-код. Весь код, написанный на EB/VBA, в конечном счёте компилировался в P-код и в рабочем режиме (а также в режиме отладки) этот P-код исполнялся собственной P-кодной виртуальной машиной. Это был единственный и основной режим компиляции EB/VBA, что, в общем-то и логично. Реализация же самой виртуальной машины на разных аппаратных архитектурах и под разными ОС могла быть совершенно разной, а вот система команд была одной и той же, что теоретически означало бы, что единожды скомпилированный VB-код в P-код мог выполняться без перекомпиляции под сильно разными платформами. Отдельно стоит сказать, что EB/VBA не перекомпилирует P-код процедур и методов классов (и других объектных сущностей) всякий раз при запуске. Код компилируется по принципу JIT (если каких-то процедур не коснулись — они вообще не компилируется), и один раз скомпилировавшись, продолжает существовать между запусками, если процедуры не модифицировались. Более того, этот подход с P-кодом позволяет VBA давать разработчику уникальную возможность: поставив выполнение кода на паузу и словив его на паузу брекпоинтом или выполняя пошаговое выполнение, разработчик может очень существенно по живому переписывать код, менять строки кода местами, удалять что-то добавлять, дописывать, редактировать выражения. И это всё не требует перекомпиляции проекта, перезапуска, за исключением редких случаев (когда, например, был объявлен массив одного типа, а после правки он остался массивом, но уже другого типа, либо поменялась размерность).
Однако то, что было хорошо для EB/VBA, для Thunder/VB могло оказаться не самым оптимальным. Поэтому в VB, который умел (в отличие от VBA) порождать самостоятельные независимые EXE-файлы (включим сюда и DLL/OCX и т.п.), сделали целых два режима компиляции кода проекта.
Первый механизм, используемый VB — это компиляция кода во всё тот же P-код, как в VBA. Все процедуры компилируются в P-код и помещаются в EXE-файл вместе во вспомогательными структурами данных, чем-то похожими на RTTI в C++. Виртуальная же машина, исполняющая этот P-код, жила в отдельном файле, например, для Visual Basic 6 этот файл назывался MSVBVM60.DLL, Весь этот принцип исполнения P-кода на виртуальной машине был устроен так, что в любой момент из P-кода можно было вызвать Native-код процедуры (например системное API или какие-то функции из сторонних прикладных библиотек), а с другой стороны из любой native-кодной среды (например сишного кода) можно было вызвать процедуру, реализованную как P-код — для этого EB генерирует для такой процедуры крохотный переходничок, который передаёт управление виртуальной машине, заставляя её выполнять P-код процедуре, а затем выполнить возврат обратно в переходничок и в вызывающую сторону.
Второй механизм это то, чем не может похвастаться VBA (EB), но VBA ( = Thunder = EB+Ruby) похвастаться может — это генерация из VB-кода полноценного EXE-файла с трансляцией кода в машинный код x86, исполняемый непосредственно процессором. Без каких-либо интерпретации даже при очень поверхностном взгляде.
В обоих случаях никакой интерпретацией не пахнет. В одном случае виртуальная машина исполняет свой проприетарный байт-код, ровно так же, как это делает Java. В другом случае вообще генерируется машинный код, который исполняется процессором, как будто скомпилировали программу на Си или Си++.
Выбор, какой режим компиляции использовать, лежал на программисте. Оба варианта имели свои преимущества и недостатки. По умолчанию действовал вариант с генерацией машинного кода. Вариант с генерацией P-кода давал очень компактный код, потому что P-код инструкции были весьма высокоуровневыми: одна P-code инструкция могла делать то, что делает 50 машинных инструкций процессора. Зачастую P-кодный вариант был медленнее, чем исполняемый файл, сгенерированный в Native-код. Но не всегда: если куски машинного кода, составляющие реализацию виртуальной машины, удачно попадают в кеш инструкций процессора, выполнение P-code варианта могло (и может) наоборот обогнать Native-код.
Ну и по аналогии аналогии с QuickBasic, давайте напишем какой-нибудь бессмысленный в реальной жизни код на Visual Basic, скомпилируем и посмотрим под дизаессемблером, что же там генерируется. Но на этот раз мы не просто будем смотреть на результат компилции VB-кода, а рядом напишем аналогичный код на C++ и сравним оба варианта.
В качестве демонстрации напишем две процедуру/функции:
Первая — GetMinMax — принимает два числа (a и b) и возвращает минимальное из них и максимальное из них. При этом, если так оказалось, что a>b — вызывается WinAPI-функция MessageBeep с параметром MB_OK.
Вторая — Fact — принимает число n и подсчитывает факториал (n!) самым наивным образом, не заботясь о переполнениях при больших n.
Использовать будем два продукта одного года выпуска: Visual Basic 6 и Visual C++ 6 (более нового VB просто не существует):

Компилируем VB-код. Оставляем активной опцию «Compile to Native Code», которая и так выбрана по умолчанию для всех новых проектов. Compile to Native Code означает компиляцию в машинный код x86, который выполняется на виртуальной машиной, а непосредственно процессором. Ставим галочку Create Symbolic Debug Info — чтобы потом легко можно было найти наши процедуры в дизасм-листинге. Компилируем и получаем EXE-файл.

Теперь компилируемый сишный код:сишный код компилируем bat-ником со следующим содержимым:
cl test.cpp /O2 /link /dll /noentry /debugtype:coff user32.lib
pause
Здесь мы передаёт ключ /O2 компилятору, потому что это соответствует VB-шной оптимизации «Optimize for Fast Code» (см. скриншот выше), линкеру же мы передаём ключи /dll и /noentry (чтобы не писать функцию main ни в каком виде, ибо она в данном эксперименте не нужна), а также /debugtype:coff, чтобы нужные функции были как-то подписаны в дизассемблере.
И теперь сравниваем результат дизассемблирования обоих бинарных файлов:

Что мы здесь видим? Главным образом мы видим то, что внутри EXE-файла, порождённого на свет силами VB, содержится, чёрт его возьми, машинный код. Не исходный код, как говорят нам бредовые байки, не какое-то там промежуточное представление, а нормальный 32-битный машинный код.
Во вторую очередь мы видим, что на уровне машинного кода результат компиляции GetMinAndMax вообще абсолютно идентичен для VB6 и MSVC++6. И это в то же самое время, когда кто-то пишет глупость в духе:
Исполняющую среду с кодом в один EXE паковал Visual Basic
...
Оба умели генерировать .EXE, но не настоящий. :)
Я просто ума не приложу, чем EXE-файл, генерируемый силами VB, не является настоящим, если он, зараза, байт-в-байт, инструкция-в-инструкцию идентичен результату компиляции эквивалентного кода, написанного на C++? Но, конечно, я знаю ответ: авторам не интересно докапываться до истины или хотя бы проверять правомочность своих слов. Куда прикольнее просто пнуть тушу мёртвого льва, ведь лев уже не даст сдачи.
В случае с функцией Fact() варианты, которые выдали компиляторы VB и C++ отличаются: VB-шный выхлоп более многословен. Но это ни в коем случае не означает, что VB-шный компилятор в каком-то смысле менее полноценный или оптимальный.
Просто VB — это не Си (и не C++), а C++ — не VB. В Си язык никогда не будет сам заботиться о том, что у вас может произойти переполнение при выполнении целочисленной арифметики. VB — совсем другое дело. VB гарантирует вам, что случайное целочисленное переполнение не останется назамеченным: на каждую операцию, потенциально грозящую переполнением, VB сгенерирует код, выполняющий проверку, не произошло ли оно, и выбрасывающий ошибк�� (путём выкидывания SEH-исключения), позволяющую программисту отреагировать каким-либо образом.
Таким образом, в случае функции Fact() выхлоп от VB получился длиннее просто потому, что VB предполагает дополнительные телодвижения и автоматически делает их. Но вы, если вам вдруг это не надо и вы точно уверены, что переполнения не будет (или вам всё равно на результат, если оно всё-таки случится), можете деактивировать это поведение:

После этого (я также дал компилятору обещание, что в коде нет aliasing-а — ситуации, когда на одно местоположение в памяти ссылаются две различные переменные), если перекомпилировать код, то и для функции Fact() выхлоп на выходе VB-компилятора станет таким же, как у компилятора C++:

Машинный код идентичен инструкция-в-инструкцию, байт-в-байт, бит-в-бит.
Но мы с вами помним — VB просто вшивает исходный код в EXE-шник, наряду с интерпретатором. VB умеет делать EXE, но эти EXE априори ненастоящие, неполноценные, недоделанные какие-то. Ну, по крайней мере, так искренне считают люди, которые распространяют эти бредовые байки и поверия.
Вообще же, сравнение результата работы компилятора VB, работающего в режиме «Compile to Native Code», и компилятора C/C++ от Microsoft — тупейшее и бессмысленнейшее занятие. И дело здесь в следующем. (Речь, разумеется, идёт о компиляторах одной эпохии, одного поколения).
Нужно знать, как устроен компилятор (транслятор) C/C++, являющийся частью Microsoft Visual C++ 6.0 (для более ранних версий это актуально в той же степени — не только для шестой). Компилятор (транслятор) оформлен в виде исполняемого файла CL.EXE. На самом деле, внутри CL.EXE почти нет ничего интересного. Архитектура компилятора (транслятор) C/C++ предполагает двухкомпонентный подход: фронтенд и бэкенд (рассматривайте эти термины просто в контексте двухкомпонентной архитектуры, забудьте о веб-разработке и дополнительных коннотациях, связанных с веб-разработкой). CL.EXE разбивает работу по компиляции (трансляции) исходного файла на два этапа:
Первый этап выполняет фронтенд, который зависит от языка исходного кода. У Си свой фронтенд (C1.DLL), у Си++ — свой (C1XX.DLL). Задача фронтенда — выполнить первые, языко-специфичные шаги трансляции. Это обработка директив препроцессора, токенизация, построение AST-деревьев, построение таблиц имён, объектов, олицетворяющих процедуры, генерация графов хода выполнения, разворачивание циклов и тому подобное.
Второй этап выполняет бэкенд, который получает от фронтенда частично транслированную программу в максимально абстрагированном от конкретики ЯП формате — IL (Intermediate Language, ничего общего не имеет с MSIL в .NET). Задача бэкенда абстрактное представление программы низвести до уровня машинных команд, а точнее не только машинных команд, а объектного файла с его специями кода, данных и т.п. В случае компилятора CL.EXE из MSVC++6 бэкенд один — C2.DLL — которому абсолютно безразлично, компилировался ли сейчас исходник на C или на C++.
Таким образом, CL.EXE просто создаёт конвейер/пайплайн между фронтендом (выбирая его в зависимости от языка) и бэкендом (выбирая его в зависимости от аппаратной архитектуры, правда в случае с MSVC6 выбора нет — поддерживается только x86, 32-битный режим).
В случае же Visual Basic для генерации EXE-файлов в режиме «Compile into Native Code» Microsoft позаимствовали бэкенд (C2) компилятора C/C++ у команды Visual C++ и включили его в состав продукта VB. С единственной оговоркой: что теперь это не C2.DLL, а C2.EXE, который в процессе компиляции вызывается средой (VB IDE) и выполняет всю грязную работу.
Значительная часть оптимизаций уровня того, какие оптимальные инструкции выбрать, какой порядок чередования инструкций, какой план использования регистров и т.п. — всё это удел бэкенда C2. И абсолютно глупо сравнивать или задаваться целью сравнить, какой компилятор сгенерирует более хороший, либо быстрый, либо компактный машинный код для одной и той же задачи — VB или C/C++ — просто потому, что генерацией машинного кода и в том и в другом случае занимается один и тот же компилятор (а точнее его половинка) — C2.
Но вопрос не в том, что оптимальнее и насколько оптимальнее. Вопрос в том, что генерация машинного кода в случае с VB не уступает таковой у C++ (того же поколения, версии) просто по той причине, что делается силами и средствами того же самого механизма кодогенерации. И на фоне этого в комментариях циркулируют байки про интерпретацию или какие-то неполноценные EXE-файлы... Потому что мёртвый лев не даст сдачи; и про него можно писать любые гадости: достоверность никто не пойдёт проверять (не интересно же, когда есть модные молодёжные языки а-ля Rust или Zig), зато с радостью кто-нибудь перескажет вашу дурацкую небылицу.
Компиляция в P-code же — совсем другая история. Да, в этом случае код пользовательских процедур не превратится в машинный код. Он превратится в P-код и будет исполнен виртуальноый машиной VB. Но это ни коим образом не делает эти EXE каким-то неполноценными и не даёт право называть их интерпретируемыми, так же как выполнение Java-приложения на JVM не делает Java-код интерпретируемым.
Виртуальная машина VB — стековая. Все манипуляции операции делаются на собственном стеке ВМ. Подход чем-то похож на работы инструкциями FPU и регистрами FPU,которе организованы как стек.
Код процедуры GetMinAndMax:
Public Sub GetMinAndMax(ByVal a As Long, _
ByVal b As Long, _
ByRef Min As Long, _
ByRef Max As Long)
If a > b Then
Min = b
Max = a
MessageBeep MB_OK
Else
Min = a
Max = b
End If
End Sub
Например, превращается в такой P-код:
loc_401AF8: ILdI4 arg_C ' Поместить на стек VM аргумент #1 (он же [a])
loc_401AFB: ILdI4 arg_10 ' Поместить на стек VM аргумент #2 (он же [b])
loc_401AFE: GtI4 ' Сравнить два лежащих на стеке I4-числа (Long)
loc_401AFF: BranchF loc_401B1B ' Перейти туда-то, если первое не было больше второго
loc_401B02: ILdI4 arg_10 ' Поместить на стек VM аргумент #2 (он же [b])
loc_401B05: IStRf arg_14 ' Убрать его из стека в память в аргумент #3 (он же [Min])
loc_401B08: ILdI4 arg_C ' Поместить на стек VM аргумент #1 (он же [a])
loc_401B0B: IStRf arg_18 ' Убрать его из стека в память в аргумент #4 (он же [Max])
loc_401B0E: LitI4 0 ' Помустить на стек VM число-литерал 0 в формате I4 (Long)
loc_401B13: ImpAdCallFPR4 MessageBeep() ' Вызвать импортируемую функцию MessageBeep
loc_401B18: Branch loc_401B27 ' Безусловный переход на эпилог процедуры (чтобы перепрыгнуть Else-ветку)
loc_401B1B: ' else-часть условия
loc_401B1B: ILdI4 arg_C ' Загрузить (push) в стек значение аргумента #1 (он же [a])
loc_401B1E: IStRf arg_14 ' Извлечь (pop)) его из стека и поместить в аргумент #3 (он же Min)
loc_401B21: ILdI4 arg_10 ' Загрузить в стек значение аргумента #2 (он же [b])
loc_401B24: IStRf arg_18 ' Извлечь его из стека и поместить в аргумент #4 (он же Max)
loc_401B27: ExitProc ' Выход из процедуры
Теперь, когда вы встретите людей, либо утверждающих, что QuickBasic является интерпретируемым языком (особенно, если они говорят, что EXE-файл скомпилированной программы на поверку оказывается интерпретатором, с пришитым к нему исходным кодом), либо утверждающих, что Visual Basic является интерпретируемым языком (с той же чушью относительного неполноценности EXE-файлов), вы знаете, что делать. Позорьте их, бросайте в них соответствующими тряпками, минусуйте им карму. Вежливо и конструктивно объясните им, что они мягко говоря заблуждаются. Пришлите им ссылку на эту статью. И обязательно скажите, что делать громкие, но непроверенные заявления нужно обязательно с припиской «одна бабка сказала».
Фух, а теперь можно выдохнуть...
Комментарии (19)

Krey
05.12.2025 16:07Я видел сам неоднократно бейсиковые блобы в бинарнике в школьные времена, до того как перейти на паскаль и буквально месяц два назад натыкался на каком-то ретро канале на ютубе на тоже самое. Будет время, проверю в досбоксе.
Единственное в чем может быть заквоздка, это в том что я имел ввиду qbasic из состава DOS, возможно это другой продукт

firehacker Автор
05.12.2025 16:07QBasic вообще не делал EXE-файлы.
QuickBasic делал, и это были настоящие полноценные полноправные EXE, как показано в статье.
Krey
05.12.2025 16:07Да, только что проверил. Ну то что у меня было - делало и именно так, как я описал. Что-то наверное из школы притащил. Уж извините, мне сейчас 45, тогда было 17, имею право что-то забыть. Попадётся ещё на просторах ютуба, допишу.

NickDoom
05.12.2025 16:07Я тоже отчётливо помню фейковые «ЕХЕ» с явно видимым исходным васичным кодом внутри. Причём они работали с точно такой же скоростью, что и просто васик.
Смутно помню, что при каких-то обстоятельствах получил код, летающий птичкой. Что-то ещё в голове промелькнуло, типа «о, начали реально компилировать, а не просто цеплять в хвост интерпретатору бейсичный код». Не был уверен в том, что правильно запомнил — но тут автор, похоже, подтвердил в своей статье то, что мне это не померещилось и память не переврала.
Ну, теперь подожду подтверждения первой части воспоминаний с Вашей стороны :) Будем искать, в какой момент произошла эта разница :)

Oangai
05.12.2025 16:07в начале девяностых как-то попалась в руки демо версия довольно крутого по тем временам SPICE симулятора электронных схем, написанная на каком-то тогдашнем компилируещем бейсике. Все модели, символы и формулы были в комплекте, а демонстрационность реализовывалась тем что они вырезали логику ввода от пользователя и заменили на массив записанных комманд симуляции - движения мышью, нажатия клавиш итд. Помнится, я наверное пару недель пытался сломать этот код и переделать в более-менее употребимое состояние, но так и не смог, что-то он там слишком сложное для понимания накомпилировал, так обидно было. И 640кб памяти катастрофически не хватало, не влазило это всё туда вместе с дебаггером, но это всетаки удалось решить дополнительной EGA карточкой, подключал её память в дос как расширенние, всетаки влезло, хотя и не помогло в итоге. Своеобразные впечатления...

Krey
05.12.2025 16:07В те годы интерпретатор был слишком дорогим удовольствием.
Да, включите логику и объясните каким образом это дорогое удовольствие было в каждой 8ми битке, например в ZX80 с 4КБ памяти и сколько там, 16КБ ПЗУ ?!

firehacker Автор
05.12.2025 16:07Не жалко занять всю имеющуюся память интерпретатором, если по изначальной задумке это единственное, чем должно быть занято ПЗУ вашего изделия.

Krey
05.12.2025 16:07Пзу занимается много чем, речь ведь идёт о компьютере. Там куча системных функций зашита, особенно в случае ZX, в котором не было ни одного спец контроллера. Системный шрифт и прочие ресурсы.

artptr86
05.12.2025 16:07Кстати в случае с ZX Spectrum, в нём исходник фактически уже был частично токенизирован: в памяти вместо ключевых слов лежали однобайтные коды этих слов.
Поэтому, например, вместо
FOR a=1 TO 10 STEP 2в памяти лежалоEB 61 3D 31 CC 31 30 CD 32— интерпретатору требовалось распарсить только имена переменных и литералы. Это сильно упрощало интерпретацию и требовало существенно меньше памяти, чем иные промежуточные структуры.

checkpoint
05.12.2025 16:07Спасибо, что включили мой username в свою статью. Прочтя Вашу статью я сильно озадачился - не выжил ли я из ума под старость лет, ведь в начале 90-х я имел дело QBasic и отчетливо помню, что никакой он не компилятор, но в Вашей статье приводятся ясные доказательства обратного. Чтож, надо разобраться. Я пошел гуглить и выяснил следующее:
Сущетсвуют целых три версии BASIC-а с похожими названиями от компании Microsoft: GW-Basic, QBasic и QuickBasic.
В состав MS-DOS сначала входил GW-Basic, с версии 5.0 его заменили на QBasic.
QuickBasic продвигался как отдельный продукт и действительно является компилирующим: содержит среду, компилятор, линкер и отладчик.
Ни GW-Basic, ни QBasic не являются компилятором.
Ниже цитата с сайте Wikipedia: https://en.wikipedia.org/wiki/QuickBASIC
A subset of QuickBASIC 4.5, named QBasic, was included with MS-DOS 5 and later versions, replacing the GW-BASIC included with previous versions of MS-DOS. Compared to QuickBASIC, QBasic is limited to an interpreter only, lacks a few functions, can only handle programs of a limited size, and lacks support for separate program modules. Since it lacks a compiler, it cannot be used to produce executable files, although its program source code can still be compiled by a QuickBASIC 4.5, PDS 7.x or VBDOS 1.0 compiler, if available.
Моя ошибка в том, что QBasic вообще не мог генерировать .EXE файл.

NickDoom
05.12.2025 16:07Страшная мысль: а вдруг это была неофициальная, но очень популярная тулза? И она нам всем запомнилась как вот это вот «ха-ха, оно не компилирует, просто цепляет васик в хвост файла»?
Надо б древние диски глянуть, вдруг прочитаются…

checkpoint
05.12.2025 16:07Был еще такой MS Visual Basic for MS-DOS (VBDOS), я скачал его инсталляшку и запустил в DosBox.

Microsoft, Visual Basic for DOS / 1992 В меню есть опция для генерации .EXE:

Я написал тестовую погу и заглянул в результирующий EXE. Если генерировать по второму варианту "EXE Requiring Run-Time Module", то получается коротенький файл похожий на "шитый код" в заголовке которого находится код подгрузки
интерпретаторавиртуальной машны из рядом лежащей библиотеки. Если выбрать первую опцию "Stand-Alone", то содержимое этой библиотеки включается в результирующий EXE, за которым следует все тот же "шитый код".
PerroSalchicha
Спасибо за исследование, но я всё-таки был прав. Компиляция в нативный код в Visual Basic появилась только в VB5, до этого он EXE собирал из исполняющей среды и исходников, точнее, не исходников, а байт-кода, в который он их преобразовывал.
DancingOnWater
Тогда любой современный компилятор можно назвать интерпретатором.
А если вспомнить, что в любом x86 современном процессоре встроен преобразователь cisc команд во внутреннее risc, то можно дойти до того что ни один язык не является компилируемым.
На самом деле у компилятора и интерпретатора есть коренное различие. У второго ошибка синтаксиса языка - ошибка времени исполнения программы. При компиляции анализ на корректность синтаксиса происходит на этапе сборки программы.
firehacker Автор
Обычно говорят «точнее», когда хотят уточнить и без того достаточно точное первое утверждение. Например «она была зелёного цвета, точнее фисташкового». Его странно видеть в предложениях типа «он был чёрным, точнее белым» и т.п.
Исходники и байт-код — на двух диаметрально противоположенных концах жизненного цикла программного продукта. Это руда и экскаваторы. Мука и булочки.
Если вы понимаете, что там не исходники, а байт-код, то почему называете это «интерпретируемым языком»? Точно так же работает дотнет, точно так же работает Java. Никто не говорит, что Java интерпретируемая, что C# интерпретируемый, что VB.NET интерпретируемый.
Python, PHP и современный JS тоже генерирует байт-код и выполняют его на своей виртуальной машине. Но они это делают в момент запуска и на конечном устройстве. В случае VB это было делалось один раз, при компиляции и происходило на машине разработчика.
Даже если бы до появления VB5 там реально вшивался бы исходный код (что not the case, как мы выяснили, исходный код не вшивался никогда), почему-то никто никогда не делает эту ремарку с указанием версии, хотя, грубо говоря, 10 лет генерации нативного машинного кода не было, после чего 20 лет эта генерация была — то есть она была бОльшую часть времени, но этого факта как будто бы не существует, зато положение дел, которое существовало меньшую часть времени, акцентируется и извращается до непомерных масштабов (готовый оптимизированный байт-код для виртуальной машины выдаётся за исходный код).