使用来自 #define 的字符代码与编译时常量字符串连接

3
我想组装一个 char 数组,其中包含一个编译时常量值和一个字符串,也是编译时常量。解决方案如下:
char packet[] = "\x42" __DATE__;

这个方法虽然可以运行,但其可读性和可维护性较差,因为0x42是一个消息操作码,在其他地方也被使用,这样就变成了魔法数字。现在,我可以向字符串中添加一个虚拟的“x”,并在定义后面跟随以下赋值语句:

#define OPCODE 0x42
char packet[] = "x" __DATE__;
packet[0] = OPCODE;

但我感觉这个可以在纯常量字符串字面量中完成,只是我不知道如何做。有什么想法吗?


1
请注意,#define OPCODE 42十进制的42(八进制为52,十六进制为2a,ASCII码为"),而字符串转义序列"\42"八进制的42(十进制为34,十六进制为22,ASCII码为*),因此问题本身就是不一致的。 - ndim
好的,谢谢,我把“42”改为“0x42”。这应该会使它保持一致。 - Stefan Bormann
2个回答

4

__DATE__ 通常应该采用 Mmm dd yyyy 的格式,长度为11个字符。您可以这样做:

char packet[] = {
  OPCODE,
  __DATE__[0],
  __DATE__[1],
  __DATE__[2],
  __DATE__[3],
  __DATE__[4],
  __DATE__[5],
  __DATE__[6],
  __DATE__[7],
  __DATE__[8],
  __DATE__[9],
  __DATE__[10],
  __DATE__[11],
  0,
};
   

4
考虑在 __DATE__ 的大小上添加静态断言。 - tstanisl
2
虽然ISO C规定预定义宏__DATE__的格式为“mmm dd yyyy”(11个字符加上NUL),但是分配一个_Static_assert(sizeof(__DATE__)==12, "unexpected size of __DATE__");仍然显得很明智。 - ndim
看起来在初始化列表中将最后一个字符常量设为0'\0'并不是必要的。无论是clang还是gcc都会自动产生一个尾随的NUL字符。我找不到这个规定在哪里或是否存在。 - ndim
请注意,如果在任何数据包中使用了 OPCODE 值为 0,则对该数据包进行运行时 strlen() 调用将无法按预期工作。但是,静态编译时的 sizeof 将可以正常工作。 - ndim

1

由于C预处理器无法在此处发挥作用,这可能是需要向构建系统添加额外预处理器的情况。

您可以选择第二阶段的C预处理器、m4、shell或Python脚本或其他任何工具。

或者您可以选择另一种方式,将数据包类型从char数组更改为具有灵活数组成员的struct,类似于以下内容:

/* foo.c
 * Compile with something like
 *    avr-gcc -mmcu=atmega328 -Os -Wall -Wextra -Werror \
 *        -save-temps=obj -Wa,-adhlns=foo.lst,-gstabs -c foo.c
 */

#include <stddef.h>
#include <stdint.h>

#include <avr/pgmspace.h>

#define OPCODE_DATE 0x42

struct packet_descr {
  uint8_t  opcode;
  uint16_t size;
};

struct string_packet {
  struct packet_descr descr;
  char                string[];
};

#define STRING_PACKET_P(IDENTIFIER, OPCODE, STRING)   \
  const struct string_packet IDENTIFIER PROGMEM = {   \
    { (OPCODE),                                       \
      sizeof(STRING)-1                                \
    },                                                \
    (STRING)                                          \
  }

STRING_PACKET_P(packet_date_P, OPCODE_DATE, __DATE__);

void uart_send_char(const char ch);
void uart_send_char(const char ch)
{
  UDR0 = ch;
}

/* Send string packet in the following format:
 *      uint8_t  opcode;
 *      uint16_t len;         // in AVR endianness
 *      char     string[len]; // string of "len" characters, unterminated
 */
extern
void string_packet_send_P(const struct string_packet *string_packet_P);
void string_packet_send_P(const struct string_packet *string_packet_P)
{
  const uint8_t opcode = pgm_read_byte(&string_packet_P->descr.opcode);
  uart_send_char(opcode);

  size_t        len    = pgm_read_word(&string_packet_P->descr.size);
  for (PGM_P byte_P = (PGM_P)&string_packet_P->string; len > 0; len--, byte_P++) {
    uart_send_char(pgm_read_byte(byte_P));
  }
}

int main(void)
{
  string_packet_send_P(&packet_date_P);
  return 0;
}

struct中使用柔性数组成员的一个明显缺点是sizeof(packet)仅返回非数组部分的大小。但是,根据实际数据包格式(接收方也需要知道数据包何时开始和结束,对吧?),可以考虑单独记录大小。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接