Делаю свой домашний проект - виртуальную машину наподобие jvm. Есть текстовый файл с опкодами по типу: store 200 0 i //кладет в ячейку памяти 0 значение 200 и помечает ячейку как integer store string 1 s //кладет в ячейку памяти 1 значение string и помечает ячейку как string store a 2 c //кладет в ячейку памяти 2 значение c и помечает ячейку как char store 300 3 i //кладет в ячейку памяти 3 значение 300 и помечает ячейку как integer load 0 //загружает из ячейки памяти 0 значение в стек, тип стека задается типом первого загружаемого значение, в данном случае int load 3 //загружает из ячейки памяти 3 значение в стек add //складывает значения в стеке store 4 //результат который остался от сложения берет из стека и кладет в ячейку 4 Код store 200 0 i //кладет в ячейку памяти 0 значение 200 и помечает ячейку как integer store string 1 s //кладет в ячейку памяти 1 значение string и помечает ячейку как string store a 2 c //кладет в ячейку памяти 2 значение c и помечает ячейку как char store 300 3 i //кладет в ячейку памяти 3 значение 300 и помечает ячейку как integer load 0 //загружает из ячейки памяти 0 значение в стек, тип стека задается типом первого загружаемого значение, в данном случае int load 3 //загружает из ячейки памяти 3 значение в стек add //складывает значения в стеке store 4 //результат который остался от сложения берет из стека и кладет в ячейку 4 И я столкнулся с проблемой как хранить эти ячейки памяти. Если создавать ячейки через дженерики < T >, то такие ячейки будут разными и хранить их в одном массиве не получится. Если бы так работало, то можно было бы получать доступ к ячейке просто по индексу и вызывать в ней метод get который возвращал тот тип данных который хранит ячейка. Потом я подумал хранить ячейки в массиве object, но это плохая идея так как все равно придется приводить к типу ячейки, приведение типов не безопасно. Да и конвертация в object и обратно занимает время просто так, неэффективно. Далее я подумал сделать ячейку универсальной и дать ей поля всех нужных типов данных: int float char string bool, но тогда получается когда в ячейку помещается значение оно хранится в одном из 5 полей, остальные 4 поля пустые и неинициализированные, что не есть хорошо. Еще была идея создать 5 массивов, каждый для своего типа ячеек, например массив MemArrInt[] хранит ячейки MemCellInt которые имеют поле int для хранения данных, но мне эта идея показалась неправильной и костыльной и что будто есть нормальный путь сделать задуманное. Если тут есть хорошо разбирающиеся в C# и в целом архитектуре языков программирования, подскажите пожалуйста идею как это все можно было бы реализовать по вашему. Или если вы знаете источники где это хорошо описано прошу поделиться. Заранее спасибо. Если есть какие либо идеи или предложения и вам удобнее показать/рассказать это в тг, то пожалуйста, буду ждать вас там.
vtlstolyarov, Еще один вопрос появился) Вот я углубился в тему байтов-битов и как вы посоветовали решил оперировать байтами. Почитал много мнений на всяких форумах о том как складывать на байтовом уровне и так далее. Единственное что у меня сейчас получилось это складывать два числа int32 путем сложения их байтов, это то что мне подсказала gpt. А вот как быть с float я не знаю так как там есть мантисса. Еще у меня проблемы возникли при вычитании и делении, умножении и при делении с остатком. Я облазил все форумы что мне выдал гугл на эту тему но так и не нашел источника где было бы нормально изложено как складывать/вычитать/делить/умножать на байтовом уровне. Еще на каком-то форуме было сказано что вот есть байты float и с байтами ничего не сделать - либо переходить в операции с битами, либо переводить байты в типы .NET производить что нужно и переводить обратно в байты. Второй вариант не подходит так как я хотел бы реализовать так как это приблизительно реализовано в жизни в вм других языков в частности jvm. И отсюда у меня появляется еще один вопрос, вот есть байты int, long, short, для них опрерации с байтами одинаковы или у каждого свои особенности? Например способ складывать\делить\умножать\вычитать используя байты одинаков для всех этих типов? Одинаков ли он также и для float или там совсем другая механика?
Host1, Молодец, глубоко зашёл :) у каждого свои особенности, и всё сильно завязано на набор команд подерживаемых процессором. Думаю что тебе это всё не нужно - превращай свои биты в тип .net призводи операцию и результат конвертируй обратно в биты, иначе у тебя уйдёт пару лет на то что бы написать всё самому :) Считай что .net - это твой "машинный ассемблер" а ты надо ним пишешь виртуальную машину. Операции над числами в .net реализованы как функции в классе соответсвующего типа, почитай по теме "таблица виртуальных методов" (https://ru.wikipedia.org/wiki/Таблица_виртуальных_методов) - тебе виртуальные методы не нужны если ты не собираешься поддерживать наследование, но сама идея того что у класса есть список функций которые можно вызывать передавая им экземпляр класса вроде там должен быть описан. У тебя есть GitHub? Было бы проще что-то поконкретнее посоветовать если бы я видел что ты пишешь
vtlstolyarov, Столкнулся я с еще одной проблемой. Это связано с нем что я не настолько хорошо пока знаю c# и его возможности чтобы ими жонглировать, поэтому решил спросить вас. Пока у меня вырисовывается такая архитектура байт-код, у байта инструкции 6 бит под команду, 2 бита под mode команды(аргумент1: регистр, аргумент2: адрес ИЛИ регистр и регистр), 1 бит под разрядность адреса(32 или 64 бита). И вот с чем у меня проблема. Вот есть у меня несколько вариантов функции Add: private static void Add(Memory register, uint index) { } private static void Add(Memory registerLhs, Memory registerRhs) { } CSHARP private static void Add(Memory register, uint index) { } private static void Add(Memory registerLhs, Memory registerRhs) { } И словарь который хранит ассоциацию биты команды - функция: public delegate void CommandDelegate(byte mode); public static readonly Dictionary<byte, CommandDelegate> CommandsDictionary = new() { {0b00000000, Add}, }; CSHARP public delegate void CommandDelegate(byte mode); public static readonly Dictionary<byte, CommandDelegate> CommandsDictionary = new() { {0b00000000, Add}, }; Тут код словаря неверен, но в целом моя идея прослеживается. Я хочу запускать нужную функцию например так: dict[commandBits](аргумент1, аргумент2) CSHARP dict[commandBits](аргумент1, аргумент2) И чтобы перегрузка переходила автоматически. То есть словарь понимает что я хочу запустить метод Add, но их много, и выбирается тот который подходит по типам аргументов. Не знаю возможно это или нет, поэтому спрашиваю вас. GPT и Google ответов на эту идею не дают, там совсем другое. Делать смесь словаря команд и mode как-то не очень мне кажется. Это получается что в словаре будет много ассоциаций байт-команда только для Add, и это очень сильно помешает дальнейшей масштабируемости. Например с моей идеей если это реализуемо нужно будет лишь указать команду и написать их функции, а тут придется еще делать одинаковые биты команд в словаре с разными битами режимов каждый раз если команда будет не в единственном экземпляре. P.S.: Add это сложение.