4

I defined myself a struct:

typedef struct {
  unsigned char current;
  unsigned char start;
  unsigned char target;
  unsigned long startTime;
  unsigned int duration;
} led;

I was able to initialize an instance of that like this:

led h_red = {0,0,255,0,300};

However, when I try to have them in an array:

led leds[LEDS];
leds[0] = {0,0,0,0,0};
leds[1] = {0,0,0,0,0};
leds[2] = {0,0,255,0,300};

I get this error

signal:28:1: error: 'leds' does not name a type
 leds[0] = {0,0,0,0,0};
 ^~~~
signal:29:1: error: 'leds' does not name a type
 leds[1] = {0,0,0,0,0};
 ^~~~
signal:30:1: error: 'leds' does not name a type
 leds[2] = {0,0,255,0,300};
 ^~~~

And I have no idea what I'm doing wrong here. You can see the full change here on GitHub

2 Answers 2

10
led h_red = {0,0,255,0,300};

Here, you are defining a variable, and at the same time giving it an initial value. This is called an initialization.

led leds[LEDS];

Here you are defining an array. Since it is in global scope, and not explicitly initialized, it is implicitly initialized to all bytes zero.

leds[0] = {0,0,0,0,0};

Here, you are trying to give a value to an element of the array that has already been defined previously. This is thus not an initialization, but an assignment. Unlike initializations, assignments cannot exist outside a function. If you want to assign initial values, you can do it in setup().

Alternatively, you can initialize the array while you define it:

led leds[LEDS] = {
    {0, 0,   0, 0,   0},
    {0, 0,   0, 0,   0},
    {0, 0, 255, 0, 300}
};
1
  • 1
    This is the key part to notice: Unlike initializations, assignments cannot exist outside a function. If you want to assign initial values, you can do it in setup(). OP's call to leds[0] = {0,0,0,0,0}; is perfectly valid within another function, such as setup(), but is NOT allowed outside of all function scopes, where only initialization can take place. The OP was accidentally trying to do this assignment in the global scope, where new assignments and regular logic code are not allowed. Commented Dec 20, 2020 at 22:12
1

I've upvoted @Edgar Bonet's answer, because it is correct.

I want to add some more examples and explanations though.

Key summary:

In C and C++, you can NOT have code logic outside of functions. The global context outside of functions is intended for declarations, definitions, and some initializations/constructors only. All code logic must be inside of a function. Code logic includes post-construction variable assignments, if statements, etc.

Examples:

1. NOT allowed:

Therefore, you can NOT do the following, since it attempts to do non-initializing assignments outside of all function scopes.

Notice also that in C++ (which Arduino is), you don't need to use typedef for structs either, so I've removed that from the struct definition. I'm also using the stdint.h types, which are considered safer and "best practice" for data types, as they specify the exact type size in bits. Use uint8_t in place of unsigned char, uint32_t instead of the Arduino Uno's unsigned long, and uint16_t instead of the Arduino Uno's unsigned int. Also, in C++ in particular, constexpr or enums are preferred over #define to set variable constants.

constexpr uint8_t NUM_LEDS = 3;

struct Led {
  uint8_t current;
  uint8_t start;
  uint8_t target;
  uint32_t startTime;
  uint16_t duration;
};

Led leds[NUM_LEDS];
// NOT OK: Variable (re)assignments are NOT allowed outside of functions in 
// C and C++.
leds[0] = {0, 0,   0, 0,   0};
leds[1] = {0, 0,   0, 0,   0};
leds[2] = {0, 0, 255, 0, 300};

void setup()
{
}

void loop()
{
}

2. IS allowed:

You CAN, however, have variable initializations outside of functions, so long as they occur at the same time as construction, so this is perfectly valid:

Led leds[NUM_LEDS];
// This form of aggregate initialization during construction is just fine in the 
// global scope outside of all functions. 
Led leds[NUM_LEDS] = {
    {0, 0,   0, 0,   0},
    {0, 0,   0, 0,   0},
    {0, 0, 255, 0, 300},
};

void setup()
{
}

void loop()
{
}

3. Also IS allowed:

Or, you can just move your reassignments into a function, such as setup():

Led leds[NUM_LEDS];

void setup()
{
    // This variable (re)assignment is just fine since it's inside 
    // of a function.
    leds[0] = {0, 0,   0, 0,   0};
    leds[1] = {0, 0,   0, 0,   0};
    leds[2] = {0, 0, 255, 0, 300};
}

void loop()
{
}

4. Full, runnable example on a PC:

Here's a full, runnable example on a PC, with printing to verify the contents of the struct were changed:

Run it online yourself on OnlineGDB here, or from my eRCaGuy_hello_world repo here: struct_array_initialization.cpp:

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

// Get the number of elements in a C-style array 
#define ARRAY_LEN(array) (sizeof(array)/sizeof(array[0]))

constexpr uint8_t NUM_LEDS = 3;

struct Led {
  uint8_t current;
  uint8_t start;
  uint8_t target;
  uint32_t startTime;
  uint16_t duration;
};

// To initialize once at construction. This form of aggregate initialization 
// can be used anywhere: both inside and outside functions. 
Led leds[NUM_LEDS] = {
    { 1,  2,  3,  4,  5},
    { 6,  7,  8,  9, 10},
    {11, 12, 13, 14, 15},
};

// Print the full contents of an array of `Led` structs
void printLeds(const Led ledArrayIn[], size_t ledArrayLen)
{
    for (size_t i = 0; i < ledArrayLen; i++)
    {
        printf("ledArrayIn[%lu]\n"
               "current   = %u\n"
               "start     = %u\n"
               "target    = %u\n"
               "startTime = %u\n" 
               "duration  = %u\n\n",
               i,
               ledArrayIn[i].current,
               ledArrayIn[i].start,
               ledArrayIn[i].target,
               ledArrayIn[i].startTime,
               ledArrayIn[i].duration);
    }
}

int main()
{
    printf("Hello World\n\n");

    printLeds(leds, ARRAY_LEN(leds));
    
    printf("==============\n\n");
    
    // Do this to set or change the structs at any time AFTER construction!
    // This variable (re)assignment is only allowed inside of a function, NOT 
    // in the global scope outside of all functions!
    leds[0] = {10, 20,  30,  40,  50};
    leds[1] = {60, 70,  80,  90, 100};
    leds[2] = { 0,  0, 255,   0, 300};
    
    printLeds(leds, ARRAY_LEN(leds));

    return 0;
}

Sample output:

Hello World

ledArrayIn[0]
current   = 1
start     = 2
target    = 3
startTime = 4
duration  = 5

ledArrayIn[1]
current   = 6
start     = 7
target    = 8
startTime = 9
duration  = 10

ledArrayIn[2]
current   = 11
start     = 12
target    = 13
startTime = 14
duration  = 15

==============

ledArrayIn[0]
current   = 10
start     = 20
target    = 30
startTime = 40
duration  = 50

ledArrayIn[1]
current   = 60
start     = 70
target    = 80
startTime = 90
duration  = 100

ledArrayIn[2]
current   = 0
start     = 0
target    = 255
startTime = 0
duration  = 300

Other references:

  1. https://en.cppreference.com/w/cpp/language/aggregate_initialization
  2. [my answer] https://stackoverflow.com/questions/61240589/how-to-initialize-a-struct-to-0-in-c/61240590#61240590
1
  • Regarding the first paragraph of your answer; What does "I've upvoted @Edgar Bonet's answer, because it is correct." have to do with the question?
    – VE7JRO
    Commented Dec 23, 2020 at 3:03

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.