MicroPython使用字符串驻留可以同时保存在RAM和ROM,这就避免了存储相同字符串的重复副本,这主要适用于代码中的标识符,因为函数或变量名很可能会在代码中的多个地方出现。在MicroPython中,内部字符串被称为QSTR(uniQue STRing唯一字符串)。
QSTR值(类型为qstr
)是QSTR池链接表的索引。QSTR会存储其长度及其内容的哈希值,以便在去重过程中进行快速比较。所有处理字符串的字节码操作都使用QSTR参数。
编译时生成QSTR
在 MicroPython C 代码中,任何应在最终固件中内嵌的字符串都被写成 MP_QSTR_Foo
。在编译时,这将评估指向QSTR池中"Foo"
索引的qstr
值。
Makefile 中的一个多步骤过程实现了这一功能。总的来说,这个过程包括三个部分:
- 查找代码中的所有
MP_QSTR_Foo
标记。 - 生成一个静态QSTR池,其中包含所有字符串数据(包括长度和哈希值)。
- 用相应的索引替换所有
MP_QSTR_Foo
(通过预处理器)。
MP_QSTR_Foo
标记从两个来源中搜索:
-
$(SRC_QSTR)
中引用的所有文件。这是所有的C代码(即py
、extmod
、ports/stm32
)但不包括第三方代码,如lib
。 -
附加的
$(QSTR_GLOBAL_DEPENDENCIES)
(包括mpconfig*.h
)。
注意:frozen_mpy.c
(由mpy-tool.py
生成)有自己的QSTR
生成和池。
在 qstrdefs.h
和 qstrdefsport.h
中,通过 $(QSTR_DEFS)
变量明确提供了一些无法用 MP_QSTR_Foo
语法表示的附加字符串(例如,它们包含非字母字符)。
处理过程分为以下几个阶段:
qstr.i.last
是将每个输入文件通过C预处理器处理后的合并结果。这意味着任何有条件禁用的代码都会被删除,宏也会被扩展,这意味着不会向字符串池中添加固件中不会使用的字符串。由于在这一阶段(QSTR_GEN_CFLAGS
添加了NO_QSTR
宏)没有定义MP_QSTR_Foo
,因此它不受影响地通过了这一阶段。该文件还包括来自预处理器的注释,其中包含行号信息。请注意,这一步只使用已更改的文件,这意味着qstr.i.last
只包含自上次编译以来已更改的文件中的数据。qstr.split
是在qstr.i.last
上运行makeqstrdefs.py split
后创建的空文件。它只是作为一个依赖项,表示该步骤已运行。该脚本为每个输入 C 文件输出一个文件genhdr/qstr/...file.c.qstr
,其中只包含匹配的QSTR。每个QSTR 都打印为Q(Foo)
。要将现有文件与qstr.i.last
中增量更新生成的新数据结合起来,这一步骤必不可少。qstrdefs.collected.h
是使用makeqstrdefs.py cat
将genhdr/qstr/*
连接起来的输出结果。现在这是代码中MP_QSTR_Foo
的全集,格式为Q(Foo)
,每行一个,有重复。只有当QSTR数据集发生变化时,才会更新该文件。QSTR数据的哈希值会写入另一个文件(qstrdefs.collect.h.hash
),这样就能跟踪不同版本中的变化。- 生成一个枚举,其中每个条目都将
MP_QSTR_Foo
映射到相应的索引。它将qstrdefs.collected.h
与qstrdefs*.h
连接起来,然后将Q(Foo)
的每一行转换为"Q(Foo)"
,这样它们就能原封不动地通过预处理器。然后使用预处理器处理qstrdefs*.h
中的任何条件编译。然后撤销转换,返回Q(Foo)
,并保存为qstrdefs.preprocessed.h
。 qstrdefs.generated.h
是makeqstrdata.py
的输出结果。对于qstrdefs.preprocessed.h
中的每个Q(Foo)
(加上一些额外的硬编码),它都会输出QDEF(MP_QSTR_Foo,(const byte*) "hash" "Foo")
。
然后,在主编译程序中,qstrdefs.generated.h
会发生两件事:
- 在
qstr.h
中,每个QDEF
都成为枚举中的一个条目,这使得MP_QSTR_Foo
可用于编写代码,并等于该字符串在QSTR
表中的索引。 - 在
qstr.c
中,实际的QSTR
数据表是作为mp_qstr_const_pool->qstrs
的元素生成的。
运行时生成QSTR
可以在运行时创建额外的QSTR
池,以便向其中添加字符串。例如,代码:
foo[x] = 3
需要为x
的值创建一个QSTR
,以便"load attr"
字节码可以使用它。
此外,在编译Python代码时,标识符和字面量需要创建QSTR
。注意:只有短于10个字符的字面量才会成为QSTR
。这是因为堆上的普通字符串总是至少占用16
个字节(一个 GC 块),而QSTR
可以更有效地将它们打包到堆中。
QSTR
池(以及存储字符串数据的底层 “块”)在堆上按需分配,并有最小大小限制。