1

I'm using memcpy_P to copy a row from a PROGMEM 2d array of structs into a buffer. The number of columns is specified by COLS_PER_FRAME.

memcpy_P(buf, FRAMES[i], COLS_PER_FRAME) works as expected.

If I instead specify the source array as (FRAMES + (buffer_offset++)) that works the same, and the code compiles into less space. This is surprising to me because I expected that it should be FRAMES + (buffer_offset++ * COLS_PER_FRAME).

Shouldn't it end up getting the array offset by 1 byte, not one whole row? Any why is the compiled code smaller?

#include <avr/pgmspace.h>

typedef struct {
  const uint8_t anode : 3;
  const uint8_t cathode : 3;
} Led;

const uint8_t NUM_FRAMES = 11;
const uint8_t COLS_PER_FRAME = 6;
const  Led FRAMES[NUM_FRAMES][COLS_PER_FRAME] PROGMEM = {
  { {.anode = 0, .cathode = 1}, {.anode = 1, .cathode = 0}, {.anode = 0, .cathode = 0}, {.anode = 0, .cathode = 0}, {.anode = 0, .cathode = 0}, {.anode = 5, .cathode = 6} },
  { {.anode = 0, .cathode = 2}, {.anode = 2, .cathode = 1}, {.anode = 2, .cathode = 0}, {.anode = 0, .cathode = 0}, {.anode = 0, .cathode = 0}, {.anode = 0, .cathode = 0} },
  { {.anode = 0, .cathode = 3}, {.anode = 1, .cathode = 2}, {.anode = 3, .cathode = 1}, {.anode = 3, .cathode = 0}, {.anode = 0, .cathode = 0}, {.anode = 0, .cathode = 0} },
  { {.anode = 1, .cathode = 3}, {.anode = 3, .cathode = 2}, {.anode = 4, .cathode = 1}, {.anode = 0, .cathode = 0}, {.anode = 0, .cathode = 0}, {.anode = 0, .cathode = 0} },
  { {.anode = 0, .cathode = 4}, {.anode = 2, .cathode = 3}, {.anode = 4, .cathode = 2}, {.anode = 5, .cathode = 1}, {.anode = 5, .cathode = 0}, {.anode = 0, .cathode = 0} },
  { {.anode = 1, .cathode = 4}, {.anode = 4, .cathode = 3}, {.anode = 5, .cathode = 2}, {.anode = 6, .cathode = 1}, {.anode = 6, .cathode = 0}, {.anode = 0, .cathode = 0} },
  { {.anode = 0, .cathode = 5}, {.anode = 2, .cathode = 4}, {.anode = 5, .cathode = 3}, {.anode = 6, .cathode = 2}, {.anode = 7, .cathode = 1}, {.anode = 7, .cathode = 0} },
  { {.anode = 1, .cathode = 5}, {.anode = 3, .cathode = 4}, {.anode = 6, .cathode = 3}, {.anode = 7, .cathode = 2}, {.anode = 0, .cathode = 7}, {.anode = 0, .cathode = 0} },
  { {.anode = 0, .cathode = 6}, {.anode = 2, .cathode = 5}, {.anode = 5, .cathode = 4}, {.anode = 7, .cathode = 3}, {.anode = 2, .cathode = 7}, {.anode = 1, .cathode = 7} },
  { {.anode = 1, .cathode = 6}, {.anode = 3, .cathode = 5}, {.anode = 6, .cathode = 4}, {.anode = 3, .cathode = 7}, {.anode = 4, .cathode = 0}, {.anode = 0, .cathode = 0} },
  { {.anode = 3, .cathode = 6}, {.anode = 2, .cathode = 6}, {.anode = 4, .cathode = 5}, {.anode = 7, .cathode = 4}, {.anode = 4, .cathode = 7}, {.anode = 0, .cathode = 0} },
};

void setup() {
  Serial.begin(9600);

  Serial.println("hello");
}

void loop() {
  uint8_t buffer_offset = 0;

  static Led buf[COLS_PER_FRAME] = {0};

  for (int8_t i = 0; i < NUM_FRAMES; ++i) {
    // 2076 bytes compiled with pointer offset (FRAMES + buffer_offset)
    memcpy_P(buf, FRAMES + buffer_offset, COLS_PER_FRAME);
    buffer_offset += 1;

    // 2082 bytes compiled with array index (FRAMES[i])
    // memcpy_P(buf, FRAMES[i], COLS_PER_FRAME);

    for (uint8_t j = 0; j < COLS_PER_FRAME; ++j) {
      Serial.print('a');
      Serial.print(buf[j].anode);
      Serial.print(" c");
      Serial.print(buf[j].cathode);
      Serial.print(", ");
    }
    Serial.println();
  }
  Serial.println();

  delay(1000);
}

Output (correctly reproduces the input):

  a0 c1, a1 c0, a0 c0, a0 c0, a0 c0, a5 c6,
  a0 c2, a2 c1, a2 c0, a0 c0, a0 c0, a0 c0,
  a0 c3, a1 c2, a3 c1, a3 c0, a0 c0, a0 c0,
  a1 c3, a3 c2, a4 c1, a0 c0, a0 c0, a0 c0,
  a0 c4, a2 c3, a4 c2, a5 c1, a5 c0, a0 c0,
  a1 c4, a4 c3, a5 c2, a6 c1, a6 c0, a0 c0,
  a0 c5, a2 c4, a5 c3, a6 c2, a7 c1, a7 c0,
  a1 c5, a3 c4, a6 c3, a7 c2, a0 c7, a0 c0,
  a0 c6, a2 c5, a5 c4, a7 c3, a2 c7, a1 c7,
  a1 c6, a3 c5, a6 c4, a3 c7, a4 c0, a0 c0,
  a3 c6, a2 c6, a4 c5, a7 c4, a4 c7, a0 c0,
3
  • 1
    look up pointer arithmetic. + adds sizeof type Commented Jan 9, 2022 at 6:50
  • what if I actually do want bytes? Commented Jan 10, 2022 at 8:06
  • then you have to cast the pointer first to uint8_t* Commented Jan 10, 2022 at 8:08

1 Answer 1

2

a[2] is exactly equivalent to *(a+2) (to the point that 2[a] is perfectly valid and equivalent). Since indexing and pointer arithmetic are the same thing, adding to a pointer must advance the pointer by that many elements, not bytes. (Same for subtraction.)

#include <stdio.h>
#include <stdint.h>

int main(void) {
   uint8_t  u8;
   uint32_t u32;
   uint64_t u64;

   printf( "%p %p\n", ( void * )&u8,  ( void * )( &u8  + 1 ) );
   printf( "%p %p\n", ( void * )&u32, ( void * )( &u32 + 1 ) );
   printf( "%p %p\n", ( void * )&u64, ( void * )( &u64 + 1 ) );
}
$ gcc -Wall -Wextra -pedantic a.c -o a && ./a
0x7ffde70ae5eb 0x7ffde70ae5ec  // Delta = 1
0x7ffde70ae5ec 0x7ffde70ae5f0  // Delta = 4
0x7ffde70ae5f0 0x7ffde70ae5f8  // Delta = 8

So all you need is this:

const Led (*p)[COLS_PER_FRAME] = FRAMES;

for (int8_t i = 0; i < NUM_FRAMES; ++i) {
    memcpy_P(buf, p++, sizeof(*p));
    ...
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.