Мой последний рекреационный проект — написать Brainfuck интерпретатор в C ++. Это было достаточно просто, но сегодня я решил добавить к нему шаг компиляции. Конечная цель состоит в том, чтобы иметь возможность создавать исполняемые файлы, но сейчас все, что он делает, — это некоторая базовая оптимизация. Например, +++++ превращается для 5 команд добавления 1 в одно добавление 5 и так далее.
Хотя это работает хорошо, я заметил, что размер моего исполняемого файла после удаления увеличился с 9 до 12 тысяч. После некоторых исследований я определил, что виновата приведенная ниже функция, а именно карта. Я не понимаю почему.
void Brainfuck::compile(const string& input) {
map<char, pair<Opcode, int>> instructions {
{ '<', make_pair(Opcode::MOVL, 1) },
{ '>', make_pair(Opcode::MOVR, 1) },
{ '+', make_pair(Opcode::INCR, 1) },
{ '-', make_pair(Opcode::DECR, 1) },
{ '[', make_pair(Opcode::WHILE, 0) },
{ ']', make_pair(Opcode::WEND, 0) },
{ ',', make_pair(Opcode::INP, 0) },
{ '.', make_pair(Opcode::OUTP, 0) },
};
string::const_iterator c = begin(input);
while (c != end(input)) {
if (instructions.find(*c) != end(instructions)) {
auto instruction = instructions[*c];
makeOp(c, instruction.first, instruction.second);
} else {
c++;
}
}
}
Ключ на карте — одна из 8 действительных операций Brainfuck. Функция просматривает строку ввода и ищет эти допустимые символы. Недопустимый персонаж просто пропущен согласно спецификации Brainfuck. Если он находит его, он передает значение карты для этого ключа в функцию makeop, которая выполняет оптимизацию, превращает ее в структуру op и добавляет ее в вектор инструкций, которые мой интерпретатор фактически выполнит.
Операционная структура имеет два члена. Код операции, представляющий собой перечисление с областью видимости на основе uint8_t, представляющего одну из 8 операций, и одного целого, содержащего операнд. (пока один операнд. В будущей более сложной версии могут быть инструкции с несколькими операндами.) Таким образом, в приведенном выше примере +++++ структура будет содержать Opcode :: INCR и 5.
Таким образом, значением каждой записи карты является std :: pair, состоящая из кода операции и количества операндов. Я понимаю, что некоторые издержки неизбежны при общей структуре данных, но, учитывая простоту приведенного выше описания, не думаете ли вы, что 3k — это немного излишне?
Или, может быть, это не правильный подход для эффективного достижения моей цели? Но если не std :: map, то какую структуру данных мне следует использовать?
Обновить:
Спасибо всем, кто откликнулся. Сначала несколько слов о моих мотивах. Я не часто использую C ++ в своей повседневной работе. Так что я делаю несколько развлекательных проектов, чтобы держать свои знания острыми. Наличие абсолютно наименьшего размера кода не так важно, как, например, ясность, но мне интересно узнать, как происходит раздувание и подобные вещи.
Следуя данному совету, моя функция теперь выглядит так:
static const int MAXOPS = 8;
void Brainfuck::compile(const string& input) {
array<tuple<char, Opcode, int>, MAXOPS> instructions {
make_tuple('<', Opcode::MOVL, 1),
make_tuple('>', Opcode::MOVR, 1),
make_tuple('+', Opcode::INCR, 1),
make_tuple('-', Opcode::DECR, 1),
make_tuple('[', Opcode::WHILE, 0),
make_tuple(']', Opcode::WEND, 0),
make_tuple(',', Opcode::INP, 0),
make_tuple('.', Opcode::OUTP, 0),
};
string::const_iterator c = begin(input);
while (c != end(input)) {
auto instruction = find_if(begin(instructions), end(instructions),
[&instructions, &c](auto i) {
return *c == get<0>(i);
});
if (instruction != end(instructions)) {
makeOp(c, get<1>(*instruction), get<2>(*instruction));
} else {
c++;
}
}
}
Я перекомпилировал и … ничего не случилось. Я вспомнил, что у меня был другой метод, который содержал карту, и ответ (теперь удаленный?) Предполагал, что простого добавления карты будет достаточно, чтобы добавить код. Поэтому я изменил эту карту на массив и перекомпилировал снова. На этот раз лишенный размер
мой исполняемый файл пошел с 12280 до 9152.
Карта внутренне использует сбалансированное дерево для хранения элементов. Сбалансированные деревья бывают быстрыми, но требуют дополнительных затрат кода для перебалансировки дерева при вставках или удалениях. Так что 3к для этого кода выглядит разумно.
Других решений пока нет …