我正在为微控制器编写一些代码,我试图通过将大量带有字符串、字符和字节参数的函数调用转换为编译时常量字符串来削减相当多的程序内存,这些字符串是然后解释为控制代码。使用的值在编译时都是已知的,所以我不期待这里有任何魔法。问题是我找不到任何方法来连接带有字节值的字符串,例如如果我有一个“W”的控制字符和一个“32”的值,我想创建一个包含“W\x20\x0”的 3 字节 char * 在字符串中嵌入 NULL 值不是问题将被解析。
我在该主题上找到的大多数问题都提供了连接字节的字符串化值的解决方案,这在理论上可行,但它使解析控制序列更加复杂,因为现在单个字节最多可以包含 3 个字符,这也需要在弦内增加更多空间,这违背了练习的目的。
到目前为止我已经尝试过:
也许我对预处理器的要求太多了,但感觉应该有某种可行的方法来解决这个问题。那么,这可能吗,还是我真的要求太多了?
SET_CURSOR(20,40) 而不是“C\x14\x28”
一个简单的方法在C中就是做一个复合文字:
#define SET_CURSOR(a, b) (const char[]){'C', a, b}
在C++中,上述代码无效,可见语言之间的差异。在 C++ 中,你可以实例化一个模板:
template<int x, int y>
struct SetCursorDetail {
constexpr static const char value[3] = {'C', x, y};
};
#define SET_CURSOR(x, y) SetCursorDetail<x, y>::value
const char *str = SET_CURSOR(20, 40);
我们可以通过创建所有可能值的字典来“真正”用字符串文字替换结果。然后,编译器将连续的字符串文字连接起来。所以我们可以这样做:
/* dictionary integer to character */
#define ITOC_0 "\x0"
#define ITOC_1 "\x1"
#define ITOC_2 "\x2"
#define ITOC_3 "\x3"
/* etc */
#define ITOC_20 "\x14"
/* etc */
#define ITOC_40 "\x28"
/* etc */
#define ITOC(x) ITOC_##x
#define SET_CURSOR(a, b) "C" ITOC(a) ITOC(b)
const char *str = SET_CURSOR(20, 40); // "C" "\x14" "\x28";
如果您正在编写 C++,请记住字符串文字只是静态存储持续时间
const char[N]
。你可以有一个static constexpr std::array<const char, 3> my_string = []{ /*calculate*/ }();
,它应该有同样的效果。当然,与其他静态对象相比,字符串文字可以给予特殊处理,所以这可能不合适。
如果
32
的值是文字 32
,您可以使用预处理器将其转换为 "\x20"
并简单地连接它。使用 Boost.Preprocessor(这是一个 C 和 C++ 库),您可以使用 BOOST_PP_SEQ_ELEM
索引一系列文字:
#include <boost/preprocessor/seq/elem.hpp>
#define BYTE_TO_ONECHAR_STRING(BYTE) \
BOOST_PP_SEQ_ELEM(BYTE, \
("\x00")("\x01")("\x02")("\x03")("\x04")("\x05")("\x06")("\x07") \
(... [snipped for brevity] ...) \
("\xF8")("\xF9")("\xFA")("\xFB")("\xFC")("\xFD")("\xFE")("\xFF") \
)
#define SET_CURSOR(X, Y) "C" BYTE_TO_ONECHAR_STRING(X) BYTE_TO_ONECHAR_STRING(Y)
#define VALUE 32
#define CONTROL_CHARACTER "W"
#define CONCATENATED CONTROL_CHARACTER BYTE_TO_ONECHAR_STRING(VALUE)
#include <stdio.h>
int main() {
printf("\"%s\"; \"%s\"\n", CONCATENATED, SET_CURSOR(120, 121));
}
按预期打印
"W "; "Cxy"
:https://godbolt.org/z/1hKboK3zx