3

I want to assemble a char array that contains one byte with a compile time constant value and a string, which is also compile time constant. Solution would be:

char packet[] = "\x42" __DATE__;

That works but is not very readable and maintainable, as that 0x42 is a message opcode that is used elsewhere, making this a magic number. Now, I could put a dummy x into the string and follow this definition with an assignment like this:

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

But I have the feeling that could be done in a purely constant string literal, I just can't find how to do it. Any idea?

ndim
  • 35,870
  • 12
  • 47
  • 57
Stefan Bormann
  • 643
  • 7
  • 25
  • 1
    Note that `#define OPCODE 42` is **decimal** 42 (octal `52`, hex `2a`, ascii `"`), while the string escape `"\42"` is **octal** 42 (decimal 34, hex `22`, ascii `*`), so the question is inconsitent in itself. – ndim Dec 19 '21 at 11:31
  • Okay, thanks, I changed `42` to `0x42`. That should make it consistent. – Stefan Bormann Dec 23 '21 at 18:01

2 Answers2

4

__DATE__ should mostly have exactly Mmm dd yyyy format, so 11 characters. You can do this:

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,
};
   
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 4
    Consider adding a static assert on size of `__DATE__` – tstanisl Nov 25 '21 at 07:14
  • 2
    While ISO C mandates the 11 character plus NUL format "mmm dd yyyy" for the predefined macro `__DATE__`, assing a `_Static_assert(sizeof(__DATE__)==12, "unexpected size of __DATE__");` still appears prudent. – ndim Dec 19 '21 at 11:48
  • It appears having the last char constant in the initializer list be `0` or `'\0'` is not necessary. Both clang and gcc generate a trailing NUL character automatically. I could not find where or whether this is specified, though. – ndim Dec 19 '21 at 12:11
  • Note that a runtime `strlen()` call on any packet will only work as intended if `OPCODE` value `0` is not used anywhere. A static compile time `sizeof` will work however. – ndim Dec 25 '21 at 19:17
1

As the C preprocessor cannot help here, this might be a case for an additional preprocessor to be added to the build system.

Whether you choose a second stage of C preprocessor, m4, a shell or python script or whatever else is up to you.

Or you go another route and change the packet type from char array to a struct with a flexible array member, something like the following:

/* 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;
}

One obvious disadvantage of a struct with a flexible array member is that sizeof(packet) will only yield the size of the non-array part. However, depending on your actual packet format (the receiver of the packet also needs to know when a packet starts and finishes, right?), recording the size separately might be feasible.

ndim
  • 35,870
  • 12
  • 47
  • 57