How can I use the C/C++ preprocessor to concatenate string literals and byte values

huangapple go评论60阅读模式
英文:

How can I use the C/C++ preprocessor to concatenate string literals and byte values

问题

I understand that you're working on optimizing your code for a microcontroller and trying to create compile-time constant strings that represent control codes. You've explored various approaches, including using macros, character arrays, and string literals. While it can be challenging, it's possible to achieve your goal with some compromises and creative solutions. If you have specific questions or need assistance with a particular aspect of your code, please feel free to ask.

英文:

I'm writing some code for a microcontroller, and I'm trying to shave off quite a bit of the program memory required by converting a whole slew of function calls with string, char, and byte parameters into compile time constant strings that are then interpreted as control codes. The values used are all known at compile time, so I'm not expecting any magic here. The problem is I can't find any way to join a string with a byte value, e.g. if I have a control character of "W" and a value of "32" I want to create a 3 byte char * which contains "W\x20\x0" Having NULL values embedded within the string is not an issue with the way it's going to be parsed.

Most questions I find on the subject offer solutions for concatenating the stringified value of the byte, which in theory would work, but it makes parsing the control sequence more complex since now a single byte can be up to 3 characters, and this also takes up more space within the string which rather defeats the purpose of the exercise.

So far I've tried:

  • Treat everything as a character to create a comma delimited char array. Works great for the char values and bytes (obviously), but there seems to be no way to add a string literal to a char[] using macros and manually coverting all string literals to char arrays isn't great for readability
  • Treat everything as a string, which maintains a good amount of readability for strings and characters, but now the byte values are problematic. I can find no way of getting the preprocessor to turn 32 into "\x20" (or even "\x32"). The best workaround I've found is to actually convert all byte values into strings as an input to the macro, but DO_SOME_COMMAND("\x20") is a fair bit worse than being able to do DO_SOME_COMMAND(32). (So far this seems to be the best option available)
  • Forget macros and concatenate character arrays and strings using memcpy or printf, and rely on the compiler to optimize it down to a compile time constant. This seems to work for simple cases but I'm unsure how crazy I can get before the compiler chokes on it. I'm finding a lot of down sides to this method as well, namely that it requires nesting macros instead of just chaining them one after another and/or creating a bunch of variations for handling different mixes of char * and byte values. Additionally this makes it impossible to have it be compiled into program memory directly, even if the compiler is able to reduce it to a simple string assignment, which somewhat limits my options.
  • And of course there's always just process the inputs externally and paste them back in the source code as a single string literal, but that almost seems like cheating and would be impossible to maintain. I'm hoping to keep it as a macro so somebody can look at it and see e.g. SET_CURSOR(20,40) rather than "C\x14\x28"

Maybe I'm asking too much from the preprocessor here, but it feels like there should be some kind of workable approach to this. So, is this possible, or am I actually asking too much?

答案1

得分: 4

设置光标位置(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);

我们可以进行“真正的”替换,通过使用所有可能值的字典,将数字替换为字符串文字以获得字符串文字。然后,编译器会将连续的字符串文字连接起来。因此,我们可以这样做:

/* 字典整数到字符 */
#define ITOC_0  "\x0"
#define ITOC_1  "\x1"
#define ITOC_2  "\x2"
#define ITOC_3  "\x3"
/* 等等 */
#define ITOC_20  "\x14"
/* 等等 */
#define ITOC_40  "\x28"
/* 等等 */
#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";
英文:

> SET_CURSOR(20,40) rather than "C\x14\x28"

A simple way in C would be to just do a compound literal:

#define SET_CURSOR(a, b)  (const char[]){&#39;C&#39;, a, b}

In C++, the above code is invalid, which shows the differences between languages. In C++ you can instantiate a template:

template&lt;int x, int y&gt;
struct SetCursorDetail {
    constexpr static const char value[3] = {&#39;C&#39;, x, y};
};
#define SET_CURSOR(x, y)  SetCursorDetail&lt;x, y&gt;::value
const char *str = SET_CURSOR(20, 40);

We can do "real" replacing to get a string literal by replacing the numbers with a string literal using a dictionary of all possible values. Then, consecutive string literals are concatenated by the compiler. So we can do this:

/* dictionary integer to character */
#define ITOC_0  &quot;\x0&quot;
#define ITOC_1  &quot;\x1&quot;
#define ITOC_2  &quot;\x2&quot;
#define ITOC_3  &quot;\x3&quot;
/* etc */
#define ITOC_20  &quot;\x14&quot;
/* etc */
#define ITOC_40  &quot;\x28&quot;
/* etc */
#define ITOC(x)  ITOC_##x

#define SET_CURSOR(a, b)  &quot;C&quot; ITOC(a) ITOC(b)

const char *str = SET_CURSOR(20, 40);  // &quot;C&quot; &quot;\x14&quot; &quot;\x28&quot;;

huangapple
  • 本文由 发表于 2023年5月6日 16:08:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/76187813.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定