为什么 std::map 让我的代码如此臃肿?

Why is std::map making my code so bloated?

我最近的休闲项目是用 C++ 编写 brainfuck 解释器。它很简单,但今天我决定通过编译步骤添加它。最终目标是能够创建可执行文件,但现在它所做的只是一些基本的优化。例如+++++将5加1命令变成了一个加5等等。

虽然这很好用,但我注意到我的可执行文件在被剥离后的大小从 9k 变成了 12k。经过一些研究,我确定应该归咎于以下功能,特别是地图。我不明白为什么。

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 结构并将其添加到我的解释器将实际执行的指令向量中。

op 结构有两个成员。一个 Opcode,它是一个基于 uint8_t 的作用域枚举,表示 8 个操作之一,一个 int 包含操作数。 (现在是一个操作数。未来更复杂的版本可能会有包含多个操作数的指令。)所以在上面的 +++++ 示例中,结构将包含 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。

map 在内部使用平衡树来存储元素。平衡树速度很快,但需要一些代码开销才能在插入或删除时重新平衡树。所以这个代码的 3k 接缝是合理的。