Quick question - using Strings in typedef structures...

Hi, this is probably a simple question, hope the answers are simple too !!

I have the following declaration....

typedef struct udt_Settings {
  int16_t PHop_Min;       // 0 to PHop_Max 
  int16_t PHop_Max;       // PHop_Min to 100 
  bool    PHop_Rev;       // 0 = Normal, 1 = Reversed 
  int16_t SHop_Min;       // 0 to SHop_Max 
  int16_t SHop_Max;       // SHop_Min to 100 
  bool    SHop_Rev;       // 0 = Normal, 1 = Reversed 
  int16_t Rud_Rng;        // 0 to Rud_Max 
  int16_t Rud_Trim;       // -Range to +Range
  bool    Rud_Rev;        // 0 = Normal, 1 = Reversed 
  int16_t Thr_Rng;        // 0 to Thr_Max 
  int16_t Thr_Trim;       // +/- 20
  bool    Thr_Rev;        // 0 = Normal, 1 = Reversed 
  int16_t Hook_Rel;       // 1 to 10 S
  float   Batt_Volts;     // 7 - 15 V 
  String  id;             // this is last to catch structure changes
};

The structure "settings" is saved into EPROM

My question is this - How does the compiler know how much memory to allocate for that last String variable ?

I have an earlier declaration SETTINGS_ID = "Version x.y", but it is not until runtime (in Void Setup()) that settings.id is compared to SETTINGS_ID to determine if this is the "first run", or a new run with a modified structure.

// initialise start-up settings if not present
if (settings.id != SETTINGS_ID) {
  settings.PHop_Min = 0;      // 0 to PHop_Max 
  settings.PHop_Max = 100;    // PHop_Min to 100 
  settings.PHop_Rev = 0;      // 0 = Normal, 1 = Reversed 
  settings.SHop_Min = 0;      // 0 to SHop_Max 
  settings.SHop_Max = 100;    // SHop_Min to 100 
  settings.SHop_Rev = 0;      // 0 = Normal, 1 = Reversed 
  settings.Rud_Rng = 100;     // +/- 
  settings.Rud_Trim = 0;      // +/- 20
  settings.Rud_Rev = 0;       // 0 = Normal, 1 = Reversed 
  settings.Thr_Rng = 100;     // +/- 
  settings.Thr_Trim = 0;      // +/- 20
  settings.Thr_Rev = 0;       // 0 = Normal, 1 = Reversed 
  settings.Hook_Rel = 3;      // 1 to 10 S
  settings.Batt_Volts = 11.1; // 7 - 15 V 
  settings.id = SETTINGS_ID;

I can't see how the typedef struct can be a variable length, surely it needs to know the length of the string, which it doesn't until it is "set" when a mismatch occurs.

It all compiles OK, but I'm not 100% happy it is 100% correct....

daba:
My question is this - How does the compiler know how much memory to allocate for that last String variable

It is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. Just use cstrings - char arrays terminated with 0.

If you use a cstring you will have to define the size and problem will go away.

...R

Robin2:
It is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. Just use cstrings - char arrays terminated with 0.

If you use a cstring you will have to define the size and problem will go away.

...R

The whole project compiles well within the constrains of the Nano...

Sketch uses 12870 bytes (41%) of program storage space. Maximum is 30720 bytes.
Global variables use 1201 bytes (58%) of dynamic memory, leaving 847 bytes for local variables. Maximum is 2048 bytes.

So if I use char strings, they will be one more than than the actual length of the string to account for the terminating null ?

Is there a "SizeOf" function I can use instead of counting string length manually ?

Try using sizeof on Strings of different lengths, and print the results.
Hopefully you'll see the problem pretty quickly.

My question is this - How does the compiler know how much memory to allocate for that last String variable ?

Probably 6 bytes, consisting of a pointer to a buffer, a length, and a capacity. The actual string data ("test" or whatever) isn't included; that gets dynamically allocated "later."

BillW-MacOSX-2<9994> avr-gdb StringAppendOperator.ino.elf 
GNU gdb (AVR_8_bit_GNU_Toolchain_3.6.1_495) 7.8

(gdb) print sizeof(stringOne)
$1 = 6
(gdb)

Now that is a good answer,

You've said that string data is via pointers, I'm happy with that notion. I can see that the structure will not change, even if the target string changes.

The String class has methods to give you the length of the String and give you the char buffer.

It's all in the reference.

TolpuddleSartre:
The String class has methods to give you the length of the String and give you the char buffer.

It's all in the reference.

And I'm sure it is, but If I asked how to get from A to B would you tell me to buy a map ?

Where's the Karma-- button ?

No, I'd direct you to a map shop.

Usually Stanfords in Covent Garden - it's an amazing resource. Highly recommended.

(There isn't one)

Where's the Karma-- button ?

If you continue on this forum, you will be probably be told many more times to consult available documentation.

So get used to it.

jremington:
If you continue on this forum, you will be probably be told many more times to consult available documentation.

So get used to it.

So if I reply to every OP with terse comments like "It's in the documentation, go read it", or "I know what's wrong, but you'll have to find the answer yourself", that makes me a valuable contributor to this forum ? I think not..

It sort of makes the forum redundant, IMHO

Many people on here have a fraction of the skill-set that others have, but to take the attitude that we should "self-learn".........

I'm sure there are many people who are willing to give constructive advice, even if it is along the lines of "This is all explained in {link to relevant reference documentation}...."

It is definitely not "constructive" to be told "Go and do this, and you will see the problem". In this thread, all I needed was an explanation of something I was concerned with, and thankfully one forum member had the b@lls to actually give me an answer, rather than tell me to go read up on it.

Feel free to report me to moderation.

I don't know where 'A' is.
I don't know where 'B' is.
But I do know where Stanfords is.
(To paraphrase "I don't use the String class, so don't remember what the methods are called, but I know they exist")

But I do know that A and B exist, and that somewhere (Stanfords) there will be a solution that connects A to B.

Whether or not you buy a map, or just use the store as a map library is up to you.

But definitely, go the Stanfords (To paraphrase "RTFM")

one forum member had the b@lls to actually give me an answer, rather than tell me to go read up on it.

And another member told you that methods exist to retrieve the data you need.
Yet another member suggested that the String class is not a great place to be, and certainly not a good starting point to get from A to B.

TolpuddleSartre:
I don't know where 'A' is.
I don't know where 'B' is.
(To paraphrase "I don't use the String class, so don't remember what the methods are called, but I know they exist")

But I do know that A and B exist, and that somewhere (Stanfords) there will be a solution that connects A to B.

Whether or not you buy a map, or just use the store as a map library is up to you.

But definitely, go the Stanfords (To paraphrase "RTFM")

Totally irrelevant to the OP, are you just trying to get your post-count up ?

Feel free to report me to moderation.

daba:
Feel free to report me to moderation.

Why would I do that?

(You were the one who brought up the map metaphor)

daba:
I can't see how the typedef struct can be a variable length, surely it needs to know the length of the string, which it doesn't until it is "set" when a mismatch occurs.

The String object contains a pointer to some dynamically allocated memory. The object itself has a fixed size - it's the dynamically allocated memory that varies.

PaulMurrayCbr:
The String object contains a pointer to some dynamically allocated memory. The object itself has a fixed size - it's the dynamically allocated memory that varies.

One more constructive answer to allay my doubts. Thanks PaulMurrayCbr - karma added

Not tested, but it does show the basics; use of c-string for the (fixed size) id variable.

#include <EEPROM.h>

// address in eeprom where records are stored
#define BASEADDRESS 0

struct UDT_SETTINGS {
  int16_t PHop_Min;       // 0 to PHop_Max
  int16_t PHop_Max;       // PHop_Min to 100
  bool    PHop_Rev;       // 0 = Normal, 1 = Reversed
  int16_t SHop_Min;       // 0 to SHop_Max
  int16_t SHop_Max;       // SHop_Min to 100
  bool    SHop_Rev;       // 0 = Normal, 1 = Reversed
  int16_t Rud_Rng;        // 0 to Rud_Max
  int16_t Rud_Trim;       // -Range to +Range
  bool    Rud_Rev;        // 0 = Normal, 1 = Reversed
  int16_t Thr_Rng;        // 0 to Thr_Max
  int16_t Thr_Trim;       // +/- 20
  bool    Thr_Rev;        // 0 = Normal, 1 = Reversed
  int16_t Hook_Rel;       // 1 to 10 S
  float   Batt_Volts;     // 7 - 15 V
  char    id[5];          // this is last to catch structure changes <-------- 4 characters and nul terminator
};

// for demo purposes, two records (an array would have been better)
UDT_SETTINGS settings1;
UDT_SETTINGS settings2;

void setup()
{
  // save first record to eeprom
  EEPROM.put(BASEADDRESS, settings1);
  // save second record to eeprom
  EEPROM.put(BASEADDRESS + sizeof(UDT_SETTINGS), settings2);
  // read first record from eeprom
  EEPROM.get(BASEADDRESS, settings1);
  // read second record from eeprom
  EEPROM.get(BASEADDRESS + sizeof(UDT_SETTINGS), settings2);

}

void loop()
{

}

From what I can see the OP has gone to a great deal of trouble to find offense where none was intended and I have not seen anything in the advice that has been offered that contradicts what I suggested in Reply #1.

The problem with using the String class is not its initial creation but the process it goes through every time the running program asks it to manipulate Strings - it tramples all over the limited memory in the Arduino. A horse (yes I know it is usually a bull) in a china shop is a good analogy. Using a horse to move things around in the factory yard is fine.

...R

The idea of putting a String into EEPROM as you tried, is a little like The Goons' "What time is it?" sketch.

Just because the code compiles fine on a Nano (or any other platform) does not mean it will perform anything like you expect or desire.
This is a lesson about programming many noobs take a while to learn.

Time for a rethink, and not taking offence at sage advice.

Just because the code compiles fine on a Nano (or any other platform) does not mean it will perform anything like you expect or desire.
This is a lesson about programming many noobs take a while to learn.

Even more insidiously with the storing of a String object in EEPROM is that if you are just doing a simple program/test with EEPROM put and get or AVR eeprom block writes, the sketch will appear to be working. Even if you unplug the Arduino and reconnect it again, or only do a read with the same sketch, the sketch can appear to work. The data in the memory location which the pointer references can still be unchanged.

I think it takes some sort of complete running program with other things going on for the read of the String object from EEPROM to fail. Of course, a simple EEPROM read of the raw bytes stored will show that you have not stored the character message you thought you did.