An array of pointers- invalid conversion error

My goal is to update a bunch of variables at once through a user interface; save those variables to eeprom, and retrieve them to ‘load default values’. In order to do this, I think I need 1) some variables, 2) an array, and 3) something to keep track of the location of the variable- a pointer. Unfortunately, I get lost at the pointer part. The compiler reports: “invalid conversion from ‘long unsigned int*’ to ‘int’ [-fpermissive]”. Here is a sketch that demonstrates my problem:

// initialize some variables
unsigned long Setpoint1 = 750;//
unsigned long Setpoint2 = 250;//
unsigned long Setpoint3 = 1530;//
unsigned long Setpoint4 = 60000;//
byte state1 = HIGH;
byte state2 = LOW;

char * VariableNames[] = {"Setpoint1", "Setpoint2", "Setpoint3", "Setpoint4", "state1", "state2"
                         };
                         
// Create an array    // use pointers to keep track of the location of variables
int VariableValues[] = { &Setpoint1, &Setpoint2, &Setpoint3, &Setpoint4, &state1, &state2
                         };
byte VariableCount = 6;

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < VariableCount ; i++)
  {
    Serial.print (VariableNames[i]);
    Serial.print(F(": "));
    Serial.println(VariableValues[i]); //print the variable values
  } 
}
void loop() {}

What am I doing wrong? Why is it an invalid conversion? Why is it trying to convert anything at all?

If you want to store a pointer into an ‘int’ variable you will have to cast it.
Note: You should make your array of string pointers ‘const’.

// initialize some variables
unsigned long Setpoint1 = 750;//
unsigned long Setpoint2 = 250;//
unsigned long Setpoint3 = 1530;//
unsigned long Setpoint4 = 60000;//
byte state1 = HIGH;
byte state2 = LOW;


const char * VariableNames[] = {"Setpoint1", "Setpoint2", "Setpoint3", "Setpoint4", "state1", "state2"
                         };


// Create an array    // use pointers to keep track of the location of variables
int VariableValues[] = {(int) &Setpoint1, (int) &Setpoint2, (int) &Setpoint3, (int) &Setpoint4,
                         (int) &state1, (int) &state2};
byte VariableCount = 6;


void setup()
{
  Serial.begin(9600);
  for (int i = 0; i < VariableCount ; i++)
  {
    Serial.print (VariableNames[i]);
    Serial.print(F(": "));
    Serial.println(VariableValues[i]); //print the variable values
  }
}
void loop() {}

It will now compile but since you changed the pointers to ‘int’ you will just get the numeric value of the address printed out. To get the value of the variable you will have to re-cast the ‘int’ back to the appropriate pointer type… which means different casts for different types.

Would probably be a bit simpler to just store the setpoints in an array.

"casting"... hmm, I don't recall that from my C++ class. :confused:

Note: You should make your array of string pointers 'const'.

... Noted. Is this just better memory management? Like using the F macro for serial printing text?

To get the value of the variable you will have to re-cast the 'int' back to the appropriate pointer type... which means different casts for different types.

I'm sorry but you lost me here... How would i 're-cast' a variable. Why different casts for different variables?

Would probably be a bit simpler to just store the setpoints in an array.

Yes, I am definitely seeing that now- the simplest solution is usually the best. However, I wouldn't learn anything about pointers. If I don't use pointers, I would have to save the all the setpoint values to the array on startup and then pull the individual value from the array every time I want to check a setpoint. In my opinion, I think a more 'elegant' approach is keeping track of where the value is already stored, and updating it at that location.
Thank you both for your responses.

A pointer is an address. You defined the VariableValues as an array of int but you are initializing it with pointers! This is the reason you have to cast each pointer.

It would be better to declare the array as an array of pointers. Like this:

unsigned long* VariableValues[] = { &Setpoint1, &Setpoint2, &Setpoint3, &Setpoint4, &state1, &state2
                         };

Unfortunately, you have included pointers to state1 and state2 in the array and those are byte variables. Those pointers really do not belong in that array and I’m not sure the reason for that anyhow.

If your array looked like this it would be acceptable:

unsigned long* VariableValues[] = { &Setpoint1, &Setpoint2, &Setpoint3, &Setpoint4};

Good programming practice dictates explicit typing. If that guideline must be violated then good programming practice dictates explicit casting. Implicit casting is frowned upon and leads to coding errors.

When I teach C, I use the Bucket Analogy to introduce pointers. Think of a variable in memory as a bucket. For an Uno, an int is a 2-byte bucket, a char is a 1-byte bucket, a long is a 4-byte bucket, and so on. These different sized buckets can be placed at different memory locations. For example, suppose you have the statement:

int number = 10;
long bigNumber = 30000;

The compiler will find 2 bytes in memory that are not used and reserve them for number. We'll assume that memory address is 2000. If you look inside the bucket the bucket at address 2000, you see the 2-byte value 10. The compiler will also find 4 bytes for bigNumber (assume it's memory address 3000) and place 30000 in its bucket.

Now suppose you do this:

bigNumber = number;

This statement fetches to 2 bytes stored at memory address 2000 and pours the bucket's contents into the bigNumber bucket at memory address 2000. The statement is a "bucket-contents-from-one-place-to-another-bucket-somewhere-else".

Now, what if you did this:

number = bigNumber;

Now you are fetching a 4-byte bucket and trying to pour its contents into a 2-byte bucket. This runs the risk of slopping 2 bytes of data on the floor. The cast operator is used to tell the compiler to make the effort to adjust the bucket contents so they fit into one another. This statement:

number = (int) bigNumber;

says to take the contents of bigNumber and (if it can) squish it down enough to fit into number's 2-byte bucket. The cast operator is a set of parentheses which surround the type of data you need on the left side of the equal sign: or expression1 = (target-data-type) expression2; If you wanted to fit bigNumebr into a floating point variable, you would use:

float x = (float) bigNumber;

A pointer variable is a little different in that instead of holding a value in its bucket, it holds a memory address. For example,

long *ptr; // pointer definition

ptr = &bigNumber; // pointer initialization

C uses the asterisk to tell the compiler that this is a different type of variable called a pointer. All pointer buckets are the same size: big enough to hold a memory address of the host machine, or 2 bytes for an Uno. The contents of all pointer variables is either garbage, or a memory address. The second statement above, because it places the address-of operator (&) in front of bigNumber, tells the compiler to take the memory address of bigNumber (3000) and place it into ptr's bucket. This process initializes ptr to bigNumber's memory address. Note what this means: ptr now knows where bigNumber lives in memory.

Finally, we can use a process call indirection to use ptr to change the value of bigNumber

*ptr = 12345;

The asterisk in front of a pointer variable says to the compiler: "Ok, take the value 12345, go to the memory address I've stored in my bucket (i.e., 3000) and deposit that 4-byte value starting at that memory location." How do the compiler know to use 4 bytes? Because ptr was defined to point to a long data type, so its scalar is 4 bytes. If it were an int pointer, its scalar would be 2 bytes. That's why:

ptr++;

would increase the bucket number for ptr by 4, but if it were an int pointer the same expression would only bump the value up by 2. So, incrementing or decrementing a pointer is always done in terms of its scalar size as determined when it was defined.

Now, read this about 50 times and see if it makes sense.

seanz2003:
How would i ‘re-cast’ a variable. Why different casts for different variables?

You have an array of type ‘int’. The values in that array are either addresses pointing to ‘unsigned long’ or addresses pointing to ‘byte’. When you want to store pointers to different types in an array, the traditional data type is ‘void *’ rather than ‘int’. The ‘void *’ is a pointer to an unspecified type and can hold any pointer without requiring a cast.

void *VariableValues[] = {&Setpoint1, &Setpoint2, &Setpoint3, &Setpoint4, &state1, &state2};

Then to re-cast the pointer to the right type you have to know which type each variable is:

  for (int i = 0; i < VariableCount ; i++)
  {
    Serial.print (VariableNames[i]);
    Serial.print(F(": "));
    if (i < 4)
      Serial.println(*(unsigned long *)VariableValues[i]);
    else
      Serial.println(*(byte *)VariableValues[i]);
  }

The result is:

Setpoint1: 750
Setpoint2: 250
Setpoint3: 1530
Setpoint4: 60000
state1: 1
state2: 0