Search code examples
arrayscmacrosmemcpy

Set buffer contents as defined in header file macros


I have various array content "templates" of type uint8_t that I'd like to define in a special header file. Those content templates also have different lengths:

#define CONTENT_VARIANT_A { 5, 3, 8, 1, 4, 23 }
#define CONTENT_VARIANT_B { 1, 10, 2 }
#define CONTENT_VARIANT_C { 4, 39, 2, 39 }
// '0' is not a valid element value (=> can be used for loop termination)

#define CONTENT_MAX_SIZE = 20;

In my code, I'd like to have a method to set the content of an array buffer to one of those pre-defined values. This is my code so far, using switch and memcpy:

Method to set the content:

void SetBuffer(uint8_t *my_buffer, uint8_t chosen_content) {
  memset(my_buffer, 0, CONTENT_MAX_SIZE);
  switch (chosen_content) {
    case CHOICE_VARIANT_A: {
      uint8_t new_content[] = CONTENT_VARIANT_A;
      memcpy(my_buffer, new_content, sizeof(new_content));
      break;
    }
    case CHOICE_VARIANT_B: {
      uint8_t new_content[] = CONTENT_VARIANT_B;
      memcpy(my_buffer, new_content, sizeof(new_content));
      break;
    }
    case CHOICE_VARIANT_C: {
      uint8_t new_content[] = CONTENT_VARIANT_C;
      memcpy(my_buffer, new_content, sizeof(new_content));
      break;
    }
  }
}

Usage:

// Buffer declaration (done once)
uint8_t my_buffer[CONTENT_MAX_SIZE] = { 0 };

// Buffer population + usage (executed multiple times, with varying values for 'chosen_content')
SetBuffer(my_buffer, chosen_content);
uint8_t i = 0;
while (i < CONTENT_MAX_SIZE && my_buffer[i] > 0) {
  // ...
  ++i;
}

I'm a C# programmer, and new to C; the code in SetBuffer seems overly complicated to me, but is the only thing my mind could come up with that should work (with regards to what I think I know about C), and that also compiles. Is it the correct way of doing what I want, or is it pell-mell and should be done completely different?


Solution

  • In case the zero-out isn't necessary, you can shave the function down to something like this:

    void SetBuffer(uint8_t *my_buffer, uint8_t chosen_content) {
      
      switch (chosen_content) {
        case CHOICE_VARIANT_A: memcpy(my_buffer, (uint8_t[])CONTENT_VARIANT_A, sizeof((uint8_t[])CONTENT_VARIANT_A)); break;
        case CHOICE_VARIANT_B: memcpy(my_buffer, (uint8_t[])CONTENT_VARIANT_B, sizeof((uint8_t[])CONTENT_VARIANT_B)); break;
        case CHOICE_VARIANT_C: memcpy(my_buffer, (uint8_t[])CONTENT_VARIANT_C, sizeof((uint8_t[])CONTENT_VARIANT_C)); break;
      }
    }
    

    Where (uint8_t[])CONTENT_VARIANT_A is not a cast but together with the macro forms a compound literal. Essentially a local, anonymous temporary array. The sizeof expression is similar and calculated at compile-time.

    If you must zero-out non-used cells, then replace (uint8_t[]) with (uint8_t[CONTENT_MAX_SIZE]). C guarantees that items not contained in the initializer list gets set to zero.

    Yet another alternative for speed over readability is an evil macro:

    #define SetBuffer(my_buffer, content)         \
    memcpy(my_buffer,                             \ 
           (uint8_t[])CONTENT_VARIANT_##content,  \
           (uint8_t[])CONTENT_VARIANT_##content)
    

    Call as SetBuffer(buf, A); etc. It's fairly type safe since unknown letter prefixes will result in compiler errors. You might also want to ask yourself why you aren't simply using memcpy on the caller side.