splitting up project in .h and .cpp file with struct variable ?

Hi, this is a general programming question about
splitting up a project in .h and .cpp file when using struct variable ?

my first example sketch is this (all in one ino file):

// type and struct Definitions: --------
struct Settings_T {
  float field1;
  byte field2;
  char name[10];
};


Settings_T settings_default = {
  3.14f,
  65,
  "Working!"
};

// settings stored in EEPROM:
Settings_T settings_eeprom = {
  2.55f,
  44,
  "working_"
};

// actual settings the program works with:
Settings_T settings_actual;

// -----------------------------------


// settings_actual = settings_default; // does not work outside a function ?


void setup() {
  // put your setup code here, to run once:
  SerialUSB.begin(115200);
  while(!SerialUSB);
  
  settings_actual = settings_default;
}

void loop() {
  // put your main code here, to run repeatedly:
  
  SerialUSB.println(settings_actual.field1, 2);
  SerialUSB.println(settings_actual.field2);
  SerialUSB.println(settings_actual.name);
  SerialUSB.println("");
  delay(1000);
  
  settings_actual = settings_eeprom;
  
  SerialUSB.println(settings_actual.field1, 2);
  SerialUSB.println(settings_actual.field2);
  SerialUSB.println(settings_actual.name);
  SerialUSB.println("");
  delay(1000);
  
  settings_actual = settings_default;
}

why does the term settings_actual = settings_default; does not work outside a function ?

then I tried to split up in .h and .cpp files (just for learning):
main sketch *.ino:

#include "settings.h"


extern Settings_T settings_actual;



void setup() {
  // put your setup code here, to run once:
  SerialUSB.begin(115200);
  while(!SerialUSB);
  
  //settings_actual = settings_default;
  load_default_setting();
}

void loop() {
  // put your main code here, to run repeatedly:
  
  SerialUSB.println(settings_actual.field1, 2);
  SerialUSB.println(settings_actual.field2);
  SerialUSB.println(settings_actual.name);
  SerialUSB.println("");
  delay(1000);
  
  //settings_actual = settings_eeprom;
  load_eeprom_setting();
  
  SerialUSB.println(settings_actual.field1, 2);
  SerialUSB.println(settings_actual.field2);
  SerialUSB.println(settings_actual.name);
  SerialUSB.println("");
  delay(1000);
  
  //settings_actual = settings_default;
  load_default_setting();
}

“settings.h”:

// settings.h

#ifndef HEADER_SETTINGS
#define HEADER_SETTINGS
  
#include <Arduino.h>

// type and struct Definitions: --------
struct Settings_T {
  float field1;
  byte field2;
  char name[10];
};

// -----------------------------------

// Function prototypes:
void load_default_setting(void);
void load_eeprom_setting(void);

#endif

“settings.cpp”:

// settings.cpp

#include <Arduino.h>
#include "settings.h"


Settings_T settings_default = {
  3.14f,
  65,
  "Working!"
};

// settings stored in EEPROM:
Settings_T settings_eeprom = {
  2.55f,
  44,
  "working_"
};

// actual settings the program works with:
Settings_T settings_actual;

void load_default_setting(void){
  settings_actual = settings_default;
}

void load_eeprom_setting(void){
  settings_actual = settings_eeprom;
}

This looks a little weird to me (like not a good coding style [?]):

  • why do I have to do the struct definition within the .h-file rather than the .cpp-file ?

  • is it “good style” to declare and/or initialize the three struct variables (settings_default, settings_eeprom, settings_actual) within the .cpp file ?

  • is it “good style” to declare the needed struct variable (settings_actual) as “extern” within the main sketch .ino-file ?
    (shouldn’t it be the opposite way around: declare and/or initialize in main sketch and then mark as “extern” within the .cpp file ?)

  • I think the goal should be to “hide” as much as possible all the “settings” functions and -variables from the main sketch, encapsulating them as much as possible within the .h- and .cpp-files (like a library) → what would be the best coding style to do so ?

(this is a general learning question)

You need to think like the compiler and understand that declare and define are not the same things. When you define a variable, like:

Settings_T settings_actual;

the compiler does two things: 1) create an attribute list (e.g., ID, data type, scope, etc.) and 2) allocates a chunk of memory for that data item.

On the other hand, this:

struct Settings_T {

  • float field1;*
  • byte field2;*
  • char name[10];*
    };

is a data declaration because you are creating an attribute list, but no variable is being created; no memory is allocated here.

Therefore, as statement like:

extern struct Settings_T settings_actual;

is telling the compiler: "I have defined the variable named settings_actual in a different file, but let me use it in this file and let the linker fill in the missing memory address." Now, the compiler can perform type checking on the use of settings_actual while it parses the code.

Therefore, as statement like:

Notice that that statements uses the proper type for the variable, whereas your initial code did not.

The type is struct Settings_T, not Settings_T.