So I have display_buffer[4][20] (rows, chars) and display_pages[7][4][20] (pages, rows, chars). I have tried with my very basic skills of pointers and then tried memcpy(). This works only if I drop the page dimension from the source. Later I would of had languages too but clearly I may be barking up the wrong tree.
memcpy(display_buffer, display_pages[page], sizeof display_buffer);
If you have languages, and sufficient memory, then
#define NUM_LANGUAGES 3
#define NUM_PAGES 7
#define NUM_ROWS 4
#define NUM_COLS 20
unsigned char display_buffer[NUM_ROWS][NUM_COLS];
const unsigned char display_pages[NUM_LANGUAGES][NUM_PAGES][NUM_ROWS][NUM_COLS];
void set_display(int lang, int page)
{
if (lang >= 0 && lang < NUM_LANGUAGES && page >= 0 && page < NUM_PAGES)
memcpy(display_buffer, display_pages[lang][page], sizeof display_buffer);
}
Right. So, in addition to each page, you'll have some kind of list of variables displayed and how the values are formatted.
In any case, the core point is that if you have an array
TYPE name[N3][N2][N1];
then
name[i3]
refers to the i3'th sub-array, TYPE [N2][N1]. Also,
(void *)(name[i3]) == (void *)&(name[i3][0][0])
Similarly,
name[i3][i2]
refers to the [i3][i2]'th sub-array, TYPE[N1], and
(void *)(name[i3][i2]) == (void *)&(name[i3][i2][0])
So, if you have say
const unsigned char display_page[PAGES][4][20];
then
display_page[0]
refers to a two-dimensional array of const unsigned char with dimensions [4][20]. Because memcpy() takes void *, referring to the sub-array is converted to a void pointer to its first element. Thus,
memcpy(destination, display_page[pagenum], sizeof display_page[0]);
copies the 80 bytes of page pagenum to destination.
The sizeof display_page[0] works, because display_page[0] refers to a [4][20] array of const unsigned char, and the sizeof operator only examines its type; no memory is ever accessed.
Indeed, if you have say struct somestruct *pointer; then (sizeof *pointer == sizeof (struct somestruct)), because the sizeof operator does not evaluate its argument, it only examines it for the type of the argument. There is no problem even if pointer == NULL, because the operator does not cause pointer to be dereferenced. More importantly,
int x = 4;
printf("sizeof ++x = %zu\n", sizeof ++x);
printf("x = %d\n", x);
will print x = 4 (and NOT x = 5), simply because sizeof is the special size-examining operator, and not a function-like thing.
I'm sort of aware that using the name of an array with no index is a pointer to the first element so it sounds like you are talking about this same principle where leaving out sub array indexes means I am pointing to the start of the sub array that is listed like pages[page0] is a pointer to the start of pages[page0][row0][col0].
Yes, I think you got it.
Consider the following example C program:
#include <stdio.h>
#define XSIZE 4
#define YSIZE 4
#define ZSIZE 4
void show_3d(const char *title, int map[ZSIZE][YSIZE][XSIZE])
{
printf("%s:\n", title);
for (int z = 0; z < ZSIZE; z++) {
printf(" z = %d:\n", z);
for (int y = 0; y < YSIZE; y++) {
printf(" ");
for (int x = 0; x < XSIZE; x++) {
printf(" %3d", map[z][y][x]);
}
printf("\n");
}
}
printf("\n");
}
void show_2d(const char *title, int map[YSIZE][XSIZE])
{
printf("%s:\n", title);
for (int y = 0; y < YSIZE; y++) {
printf(" ");
for (int x = 0; x < XSIZE; x++) {
printf(" %3d", map[y][x]);
}
printf("\n");
}
printf("\n");
}
void show_1d(const char *title, int map[XSIZE])
{
printf("%s:\n ", title);
for (int x = 0; x < XSIZE; x++) {
printf(" %3d", map[x]);
}
printf("\n\n");
}
int main(void)
{
int map[ZSIZE][YSIZE][XSIZE];
for (int z = 0; z < ZSIZE; z++) {
for (int y = 0; y < YSIZE; y++) {
for (int x = 0; x < XSIZE; x++) {
map[z][y][x] = z*100 + y*10 + x;
}
}
}
show_3d("3D", map);
show_2d("2D with z=0", map[0]);
show_2d("2D with z=1", map[1]);
show_1d("1D with z=2 and y=3", map[2][3]);
show_1d("1D with z=3 and y=0", map[3][0]);
return 0;
}
Writing and running tests like this with all error checking enabled (-Wall -Wextra -pedantic -std=c99) is an useful way to explore these things.
The output is
3D:
z = 0:
0 1 2 3
10 11 12 13
20 21 22 23
30 31 32 33
z = 1:
100 101 102 103
110 111 112 113
120 121 122 123
130 131 132 133
z = 2:
200 201 202 203
210 211 212 213
220 221 222 223
230 231 232 233
z = 3:
300 301 302 303
310 311 312 313
320 321 322 323
330 331 332 333
2D with z=0:
0 1 2 3
10 11 12 13
20 21 22 23
30 31 32 33
2D with z=1:
100 101 102 103
110 111 112 113
120 121 122 123
130 131 132 133
1D with z=2 and y=3:
230 231 232 233
1D with z=3 and y=0:
300 301 302 303
Sorry for the refloat, but wouldn't this do the job straight away?
I don't think it's UB, the compiler knows both src and dst are the same type, I use this approach often, never had any problem.
typedef struct{
char d[4][20];
}dispbf_t;
dispbf_t display_buffer;
dispbf_t display_pages[7];
void store_page (){
display_pages[0] = display_buffer;
display_pages[1] = display_buffer;
}
void access_array(){
display_buffer.d[0][0] = 0;
display_pages[0].d[0][0] = 0;
}
AFAIK, the compiler runs a memcpy under the hood when copying structs.
I guess it'll made inline optimizations for small data.
Yes, just like memcpy itself.
Of course, that's not a simple int copy, but you must know what you're doing.
I like it over memcpy or other methods, gives very compact and neat code, also you don't have to think about addressing, size, etc...
Yes. It makes manipulating your data higher-level instead of a mix of high- and low-level that is pretty common in C.
Likewise, for zero'ing out a whole struct (which can contain anything you want including arrays) outside of initialization, you can use C99+ compound literals.
Like in your example:
display_buffer = (dispbf_t){ 0 };
It's higher-level than using memset().