如何使用 C/C++ 预处理器连接字符串文字和字节值

问题描述 投票:0回答:2

我正在为微控制器编写一些代码,我试图通过将大量带有字符串、字符和字节参数的函数调用转换为编译时常量字符串来削减相当多的程序内存,这些字符串是然后解释为控制代码。使用的值在编译时都是已知的,所以我不期待这里有任何魔法。问题是我找不到任何方法来连接带有字节值的字符串,例如如果我有一个“W”的控制字符和一个“32”的值,我想创建一个包含“W\x20\x0”的 3 字节 char * 在字符串中嵌入 NULL 值不是问题将被解析。

我在该主题上找到的大多数问题都提供了连接字节的字符串化值的解决方案,这在理论上可行,但它使解析控制序列更加复杂,因为现在单个字节最多可以包含 3 个字符,这也需要在弦内增加更多空间,这违背了练习的目的。

到目前为止我已经尝试过:

  • 将所有内容都视为字符以创建逗号分隔的字符数组。非常适合 char 值和字节(很明显),但似乎没有办法使用宏将字符串文字添加到 char[] 并且手动将所有字符串文字转换为 char 数组不利于可读性
  • 把所有东西都当成字符串,这对字符串和字符保持了很好的可读性,但是现在字节值是有问题的。我找不到让预处理器将 32 变成“\x20”(甚至“\x32”)的方法。我发现的最佳解决方法是将所有字节值实际转换为字符串作为宏的输入,但 DO_SOME_COMMAND("\x20") 比能够执行 DO_SOME_COMMAND(32) 要差一些。 (到目前为止,这似乎是可用的最佳选择)
  • 忘掉宏并使用 memcpy 或 printf 连接字符数组和字符串,并依靠编译器将其优化为编译时间常数。这似乎适用于简单的情况,但我不确定在编译器阻塞之前我能变得多疯狂。我也发现这种方法有很多缺点,即它需要嵌套宏,而不是仅仅将它们一个接一个地链接起来和/或创建一堆变体来处理 char * 和字节值的不同混合。此外,这使得它无法直接编译到程序内存中,即使编译器能够将其简化为简单的字符串赋值,这在某种程度上限制了我的选择。
  • 当然,总是只是在外部处理输入,然后将它们作为单个字符串文字粘贴回源代码中,但这几乎像是作弊,而且无法维护。我希望将它保留为宏,以便有人可以查看它并查看例如SET_CURSOR(20,40) 而不是“C\x14\x28”

也许我对预处理器的要求太多了,但感觉应该有某种可行的方法来解决这个问题。那么,这可能吗,还是我真的要求太多了?

c++ c-preprocessor
2个回答
1
投票

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";

0
投票

如果您正在编写 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

© www.soinside.com 2019 - 2024. All rights reserved.