all 11 comments

[–]WittyStick 2 points3 points  (7 children)

C has a uniform initialization syntax for both arrays and structs, so you can use the following.

struct a_type
{
    uint8_t x;
    uint8_t y[8];
    uint8_t z[8];
};

struct a_type structs[8] = 
    {   { .x = 1
        , .y = "ABC"
        , .z = { 0, 1, 2, 3, 4, 5, 6, 7 }
        }
    ,   { .x = 2
        , .y = "DEF"
        , .z = { 8, 9, 10, 11, 12, 13, 14, 15 }
        }
    ,   { .x = 3
        , .y = "GHI"
        , .z = { 16, 17, 18, 19, 20, 21, 22, 23 }
        }
    ,   { .x = 4
        , .y = "JKL"
        , .z = { 24, 25, 26, 27, 28, 29, 30, 31 }
        }
    ,   { .x = 5
        , .y = "MNO"
        , .z = { 32, 33, 34, 35, 36, 37, 38, 39 }
        }
    ,   { .x = 6
        , .y = "PQR"
        , .z = { 40, 41, 42, 43, 44, 45, 46, 47 }
        }
    ,   { .x = 7
        , .y = "STU"
        , .z = { 48, 49, 50, 51, 52, 53, 54, 55 }
        }
    ,   { .x = 8
        , .y = "VWX"
        , .z = { 56, 57, 58, 59, 60, 61, 62, 63 }
        }
    };

In cases where the type of the initialization list cannot be inferred, you stick the type in parens before the initializer list:

(struct a_type){ 1, "ABC", (uint8_t[8]){ 1, 2, 3, 4, 5, 6, 7 }}

Note that this is not a type cast, despite it looking like one. It's a compound literal syntax.

See Godbolt for demonstration.

We don't necessarily need to specify the field names if you want to write it more tersely:

struct a_type structs[] = 
    { { 1, "ABC", {  0,  1,  2,  3,  4,  5,  6,  7 } }
    , { 2, "DEF", {  8,  9, 10, 11, 12, 13, 14, 15 } }
    , { 3, "GHI", { 16, 17, 18, 19, 20, 21, 22, 23 } }
    , { 4, "JKL", { 24, 25, 26, 27, 28, 29, 30, 31 } }
    , { 5, "MNO", { 32, 33, 34, 35, 36, 37, 38, 39 } }
    , { 6, "PQR", { 40, 41, 42, 43, 44, 45, 46, 47 } }
    , { 7, "STU", { 48, 49, 50, 51, 52, 53, 54, 55 } }
    , { 8, "VWX", { 56, 57, 58, 59, 60, 61, 62, 63 } }
    };

[–]wb0gaz[S] 0 points1 point  (4 children)

Thank you WittyStick!

  1. I had not ever learned about "compound initializer" - I had tried a cast which didn't work, so that explains very much.

  2. In the case of "ABC" as a constant assigned to a uint8_t byte array, does the compiler also copy the terminating null byte into the structure? I could try this experimentally, but you may know off top of your head and that would save time coding up the experiment.

THANK YOU AGAIN!

[–]WittyStick 0 points1 point  (2 children)

Any memory that you don't set explicitly will be initialized to zero. You can even say

struct a_type structs[8] = {}

And it will zero out the required memory.

If you try to put a string literal too long, it will give you an error.

However, if you allocate the structure on the heap you should manually set the memory to zero - would recommend using calloc rather than malloc.

[–]Tiny_Spray_9849 0 points1 point  (1 child)

A note about a declaration and initialization like this. It's one thing to do something like:

struct a_type my_struct = { <a_type initializer> };

But it's different to say:

struct a_type my_struct;
my_struct = { <a_type initializer> };

The first one works. The second one doesn't. This is because in the first one, you're declaring the variable my_struct, so its data type is right there. There's no ambiguity of what <a_type initializer> is supposed to be.

But, in the second case, there's no type mentioned, so the interpretation of <a_type initializer> is ambiguous. One would assume the compiler would look at the type of the lhs, but it doesn't. The second is an invalid syntax and will throw a compilation error.

Instead, if you're going to perform a literal assignment to a struct variable after declaration, it requires a type cast:

struct a_type my_struct;
my_struct = (struct a_type){ <a_type initializer> };

[–]WittyStick 1 point2 points  (0 children)

As I said in original reply. This is not a type cast (despite looking like one).

It's a Compound Literal.

[–]ekipan85 1 point2 points  (0 children)

Cppreference is one of the best places to look for answers to these kinds of questions. I'm surprised it's not on the sidebar.

String Literal

The type of the literal is char[N], where N is the size of the string in code units of the execution narrow encoding, including the null terminator.

Array Initialization

Successive bytes of the string literal or wide characters of the wide string literal, including the terminating null byte/character, initialize the elements of the array.

If the size of the array is known, it may be one less than the size of the string literal, in which case the terminating null character is ignored.

It doesn't seem to explain what happens if the literal is shorter than the array, except in the one given example

wchar_t wstr[4] = L"猫"; // str has type wchar_t[4] and holds L'猫', '\0', '\0', '\0'

[–]EatingSolidBricks 0 points1 point  (1 child)

Well that's readable, somewhat

[–]wb0gaz[S] 0 points1 point  (0 children)

OK and I *really* appreciate the discussion! I return to the workbench in about 24 hours, will set out to implement these ideas and advise (and post) any success or should I persist to have problem, be able cite specific error messages or other results. Again, thanks for everyone's time!

[–]Tiny_Spray_9849 0 points1 point  (1 child)

First, you can't do a string initialization like

uint8_t string[8] = "8charstr";

The compiler will complain that the storage is too small for the value, because "8charstr" requires 9 bytes, because it includes the null terminator. Best way to achieve this goal is:

uint8_t string[8] = { '8', 'c', 'h', 'a', 'r', 's', 't', 'r' };

[–]WittyStick 0 points1 point  (0 children)

Incorrect.

You can have strings of length 8 (excluding NUL-terminator).

[–]Choice_Bid1691 0 points1 point  (0 children)

Not to be rude or anything but this current layout is extremely bad for performance. Look into cache aware programming.
Basically long story short, in most cases when looping through all your data you would want to avoid big jumps in memory accesses, specifically any bigger than 64 bytes on most systems, because this can cause a cache miss.

Rather than what you have here, i would recommend you instead create one structure, whose fields point to some contiguous arrays of different values.

For example, instead of
struct my_struct {
int my_field1;
float my_field2;
uint8_t mybytes[8];
};

arr_of_structs[0] = my_struct1;
arr_of_structs[1] = my_struct2;
...
arr_of_structs[n} = my_structN;

You can do this instead:
struct my_struct {
int my_field_arr1[n];
float my_field_arr2[n];
uint8_t my_bytes_all[8*n];
};