0

介绍

我的 Cortex-M4 设备上的 Flash 快用完了。我分析了代码,减少代码大小的最大机会就是在预定义的常量中。

- 例子

const Struct option364[] = {
   { "String1",  0x4523, "String2" },
   { "Str3",     0x1123, "S4" },
   { "String 5", 0xAAFC, "S6" }
};

问题

问题是我有(大量)(短)字符串要存储,但它们中的大多数用于表中 - const structs 数组具有指向const与数字数据混合的字符串的指针。每个字符串的大小都是可变的,但是我仍然考虑更改struct指针以保存一个简单的(最大)char数组而不是指针 - 并没有太大区别。编译器希望在 4 字节边界上开始每个新字符串并没有帮助。这让我想到...

主意

如果我可以将 4 字节char指针替换index为字符串表中的 2 字节 - 一个预定义的链接器部分,它index是一个偏移量 - 我会在那里为每条记录节省 2 个字节,但会以轻微的代码碰撞为代价。我也会避免内部填充,因为每个字符串都可以在前一个字符串的NUL字节之后立即开始。如果我能聪明点,我可以为索引重用字符串——甚至是部分字符串。

但此外,我会将4 + 2 + 4 (+ 2)对齐方式更改为2 + 2 + 2- 节省更多空间!

- 考虑

当然,在源代码中,所有这些字符串以及字符串表本身的内务管理将是一场噩梦……除非我能让编译器帮忙?我想改变实际源代码的语法:如果我希望字符串出现在字符串表中,我会将其写为#"String",其中#前缀会将其标记为字符串表候选者。一个普通的字符串不会有那个前缀,编译器会把它当作普通的。

执行

因此,要实现这一点,我必须编写一个预编译器。只处理#""字符串,用“神奇”的 16 位偏移量替换它们,然后将其他所有内容输出到真正的(预)编译器以进行实际编译。预编译器还必须编写一个C包含完整字符串表的新文件(尽管有一个技巧 - 见下文),以便编译器解析并提供给链接器的专用部分。使用开关调用它会很容易-no-integrated-cpp,调用我自己的预处理器,然后调用真正的预处理器。

- 问题

不要误会我的意思;我知道有问题。例如,它必须能够处理部分构建。我的解决方案是,对于每个修改过的C文件,它会(如有必要)编写一个并行字符串表文件。“主”C字符串表文件只不过是一系列s,如果其中一个s 已更改,或者实际上,如果添加了新#include的,则构建将意识到需要重新编译。#include#include

结果

结果将是一个可执行文件,它将所有(常量)字符串打包到一个不大于 64K 的内存块中(这不是问题!)。代码会知道这index将是该 blob 的偏移量,因此会在正常使用它之前将索引添加到字符串表指针的开头。

问题

我的问题是:值得吗?

- 优点:

  • 它将节省一空间。我没有在上面量化它,但假设节省了总 Flash 的 5%(!)。

- 缺点:

  • 这将需要修改构建过程以包含定制的预处理器;
  • 该预处理器必须作为工具链而不是项目的一部分来构建;
  • 预处理器可能有错误或限制;
  • 真正的源代码不会“开箱即用”编译。

现在...

我已经穿上了我的石棉套装,所以... GO!

4

1 回答 1

0

这种“项目自定义预处理器”在内存非常有限的日子里曾经很常见。make如果您将其用作构建系统,这很容易做到——只需一个自定义模式或后缀规则来运行您的预处理器。

主要问题是您是否想在所有源文件或只是一些源文件上运行它。如果只有几个需要它,您可以为需要预处理的源文件定义一个新的文件扩展名(例如,.cx以及.cx.c:运行预处理器的规则)。如果所有人都需要它,您可以重新定义隐式.c.o:规则。

正如您所指出的,主要缺点是,如果有任何类型的全局协调(例如像您尝试做的那样汇集所有字符串),更改任何需要预处理器的源文件可能需要重建所有这些,这可能是很慢。

于 2020-07-27T19:55:31.140 回答