Pointers

The following code has 33 floats that I want to fill with data coming in via serial messages from 11 remote units. The serial messages will have a "pointer" byte that is the offset from the first variable (in this case it would be "T_mainin"), eg, the variable "T_OAT" would have an offset of 4. However, when I try to print out what the pointers are, no matter which variables I pick, the LCD always prints:
521
517
513
509
The integers change when I try other lines in the code, but they are always 4 digits apart (which I expect for floats). I don't understand why they aren't other multiples of 4 when I pick variables that are not adjacent in the declarations.

//                 "01234567890123456789"
char this_file[] = "pointer_anal" ;
char descp[] =     "pointers-variables" ;
char ver[] =       "ver1 2/15/21 w/44780" ;

// Load Libraries for 4x20 LCD  =======================
	#include <Wire.h>
	#include <hd44780.h>                       // main hd44780 header
	#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
	hd44780_I2Cexp lcd; // declare lcd object: auto locate & config exapander chip
	int status ;
	// LCD geometry
	const int LCD_COLS = 20;
	const int LCD_ROWS = 4;
	
 float 	T_mainin ;
 float 	T_mainout ;
 float 	T_bsmtin ;
 float 	T_bsmtout ;
 float 	T_OAT ;
 float 	T_BFlr ;
 float 	T_UFlr ;
 float 	T_Foot ;
 float 	T_Art ;
 float 	HB_set ;
 float 	T_Lab ;
 float 	T_Wine ;
 float 	T_Dirt ;
 float 	T_Ebed ;
 float 	T_Wbed ;
 float 	T_Gym ;
 float 	T_Gymlo ;
 float 	T_Gymhi ;
 float 	T_Fplace ;
 float 	T_Main2 ;
 float 	T_Mainhi ;
 float 	T_Cool1 ;
 float 	T_Cool2 ;
 float 	CG_Set ;
 float 	HM_Set ;
 float 	T_Main  ;
 float 	T_MBR ;
 float 	CB_Set ;
 float 	T_OAT2 ;
 float 	T_Car ;
 float 	T_Studio ;
 float 	T_Paint ;
 float 	T_Painthi ;
 float 	T_Shop ;
 float 	T_Shophi ;
 float 	T_OAT3 ;
 float 	T_Plants ;


float keys1 ;
float keys2 ;
float keys3 ;

float *p ; // pointer
int ptr ;

// ------------------------------------------------------------------------
// ---------------- $$$$$$$$$$$$$$$$$$$ ----------------
void setup() { // ---------------- Setup() --------------------------------
	 
  pinMode(13,OUTPUT) ; // LED

  Serial.begin(9600);  // reserved for laptop comm link

// ----------------version message ------------------------
  Serial.println() ;          //line feed
  Serial.println(this_file) ; //file name
  Serial.println(descp) ;     //description
  Serial.println(ver) ;       //version and date
// ------------ end of version message --------------------

  status = lcd.begin(LCD_COLS, LCD_ROWS);

 // -------------- Show version on LCD ----------
  lcd.clear() ;
  lcd.home() ;
  lcd.print(this_file) ;
  lcd.setCursor(0,1) ;
  lcd.print(descp) ;
  lcd.setCursor(0,2) ;
  lcd.print(ver) ; 
  lcd.setCursor(0,3) ;
  lcd.print("----- LCD Test -----") ; 
  delay(2000) ;
  
  lcd.clear() ;
  lcd.home() ;
  
T_mainin = 0 ;
T_mainout = 0 ;
T_bsmtin = 0 ;
T_bsmtout = 0 ;
T_OAT = 0 ;
T_BFlr = 0 ;
T_UFlr = 0 ;
T_Foot = 0 ;
T_Art = 0 ;
HB_set = 0 ;
T_Lab = 0 ;
T_Wine = 0 ;
T_Dirt = 0 ;
T_Ebed = 0 ;
T_Wbed = 0 ;
T_Gym = 0 ;
T_Gymlo = 0 ;
T_Gymhi = 0 ;
T_Fplace = 0 ;
T_Main2 = 0 ;
T_Mainhi = 0 ;
T_Cool1 = 0 ;
T_Cool2 = 0 ;
CG_Set = 0 ;
HM_Set = 0 ;
T_Main  = 0 ;
T_MBR = 0 ;
CB_Set = 0 ;
T_OAT2 = 0 ;
T_Car = 0 ;
T_Studio = 0 ;
T_Paint = 0 ;
T_Painthi = 0 ;
T_Shop = 0 ;
T_Shophi = 0 ;
T_OAT3 = 0 ;
T_Plants = 0 ;

}
// ========================== end of setup =============================

// =================== Loop ===========================================
void loop() {
	lcd.home() ;
	lcd.clear() ;
        p = &T_Art ;
	ptr = (int)p ;
	lcd.print(ptr) ; // this is line 141
	lcd.setCursor(0,1) ;
	p = (int)&T_Ebed ;
	ptr = (int)p ;
	lcd.print(ptr) ;
	lcd.setCursor(0,2) ;
	p = (int)&T_Wbed ;
	ptr = (int)p ;
	lcd.print(ptr) ;
	lcd.setCursor(0,3) ;
	p = (int)&T_OAT3 ;
	ptr = (int)p ;
	lcd.print(ptr) ;	
	*p = 29.0 ;
	lcd.print("  ") ;
	lcd.print(T_OAT3) ;
	p = 521 ; // 521 is the address printed by line 141
	*p = 75.4 ;
	lcd.print("  ") ;
	lcd.print(T_Art) ;
	while(1) ;		
}

I'm also getting warning about converting ints to ints and floats to floats, but the code compiles and runs. I don't understand why a pointer (in this case *p) is ever a float, just because it's pointing at a float. Seems like it would be an integer.

Dr_Quark:
However, when I try to print out what the pointers are, no matter which variables I pick, the LCD always prints:
521
517
513
509

The optimizer expunges the ones you have not referenced.

Dr_Quark:
I'm also getting warning about converting...

	p = (int)&T_Ebed ;
	    ^^^^^

Why?

Your plan is fragile. The linker is under no obligation to place the variables in the order they are declared.

There is no guarantee that the compiler will store the variables in the order in which you declare them, or adjacent to each other.

Forget about the pointers and just use an array.

Definitely sounds like an array is needed so you can index the variables.

You can use named #defines or enerations for the indexes to get the best of both worlds, ie
names for the variables and also integer indexes:

#define T_mainin 0
#define T_mainout 1
#define T_bsmtin 2

float vars[3] ;
int index = 0 ;

vars[T_mainin] = .... ;
vars[index++] = .... ;

// etc  etc

thanks all. Sometimes my head still operates in assembly mode, where I had control of all the address space.

Special thanks to MarkT. I had considered an array, as all of you suggested, but didn't have a clue how to get a named pointer to access the desired variable. The only limitation of the array method is if I add another unit months later and don't want to modify all the others' code, particularly if only one of the current units will use the new variable. I'll take a look and see if I can't handle that in my switch case code so that a new message is just ignored by most of the receivers. (just did a dope slap. Using the array I never need to use a pointer or know anything about the address space, I only need to send a single byte in the message that is the index in the array. This means the code in each unit can be different (the array can be in different address space in some units), yet still successfully access the correct variable.)

I really appreciate the help, since I was "confident" about the code, I just had no clue what was going on in the compiler.

Dr Quark
better at rocket science than coding

MarkT, I've Googled "enerations" and I don't find anything. Is this just another word for #defined?

I also see that arduino.cc prefers "const" to using #define. I'm assuming that both of these are only used at compile time, so they don't use any memory storage space, just in-line bytes of code.

thanks, Dr Quark

I expect he meant enumerations.

Coding Badly,

p = &vars[T_Art] ;
ptr = (int)p ;
lcd.print(ptr) ;

I was experimenting with cast because the compiler would not allow printing of a pointer, eg, lcd.print(p). I was also getting warnings like "warning: invalid conversion from 'int' to 'float*' [-fpermissive]" and "warning: invalid conversion from 'int' to 'int' [-fpermissive]." It worked either way (with or without casting), but I was investigating if I could find some C++ totally legal expression.

Dr Quark

WildBill,

I think I'll just stick with "const." I looked at enumeration and although it's a new concept to me, which I like finding, I like to know what the values are. Thanks.

Dr Quark

You need to dereference the pointer to refer to the data it points to, example:

float nums[10] = { ... };

// Point to the first element of nums
float *p = &nums[0]

// The p on it's own references the address, which is an integer,
// so we can print that address:
Serial.print(p)

// If we want to reference the value that p points to:
Serial.print(*p);

You probably are better off using an array, but here's some code that might help with the pointer stuff:

// Let's have an array of 10 floats...
float someNums[10] = {
  11.23, 99.99, 100.1, 299.5, 1024.0,
  900, 2500.50, 81.55, 40.44, 9000.1
};

// And point a pointer to element 0 of the array.
float *p = &someNums[0];

void setup()
{
  Serial.begin(9600);

  // Now let's loop through each of the 10 values.
  for (int n = 0; n < 10; n++) {
    // The p is a pointer pointing to an address, which
    // we can reference like any other integer value,
    // so we can treat it like any other integer.
    int ptrAddress = p;

    // If we want to reference the value that p points
    // to we need to dereference the pointer using the
    // * character - we're no longer dealing with the
    // address but the data that the address points to.
    float ptrValue = *p;

    // Now we can increment p as normal, since it's a
    // pointer to type float the incrementation will
    // take us to the next element in that array.
    p++;

    Serial.print("Address: ");
    Serial.print(ptrAddress);
    Serial.print(", value: ");
    Serial.println(ptrValue);
  }
}

void loop() {
  //
}

Edit: Here's another version - same code but instead of using an integer to iterate 0-9 in the loop, we can do some other bounds checking tricks since we know the length of the array, this is perhaps a little cleaner.

// Let's have an array of 10 floats...
float someNums[10] = {
  11.23, 99.99, 100.1, 299.5, 1024.0,
  900, 2500.50, 81.55, 40.44, 9000.1
};

void setup()
{
  Serial.begin(9600);

  // Now let's loop through each of the 10 values. This
  // time we'll break when p (address) is > the address of
  // element 9 of the array:
  for (float *p = &someNums[0]; p <= &someNums[9]; p++) {
    int ptrAddress = p;
    float ptrValue = *p;

    Serial.print("Address: ");
    Serial.print(ptrAddress);
    Serial.print(", value: ");
    Serial.println(ptrValue);
  }
}

void loop() {
  //
}

Hope this helps.

You can print the value of a pointer, but the cast would be made in the print() function. Note that the cast needs to be to the correct size for an address pointer on the processor you are using, which is 16 bits on most of the atmega chips.

float test;
float* p = &test;

void setup() {
  Serial.begin(9600);
  test = 2.68 * random(100);
  Serial.println(test);            //prints value of test
  Serial.println(*p);              //prints value pointed to by p (test in this case);
  Serial.println((uint16_t)p);     //prints value of the pointer (memory address) stored in p
  Serial.println((uint16_t)&test); //prints memory address of test
}

void loop() {
}

I looked at enumeration and although it's a new concept to me, which I like finding, I like to know what the values are.

It is possible to specify the values in an enum, which allows you to do unusual things like have more than one with the same value, but can be cumbersome if you have to manually renumber when adding something in the middle.

enum floatVars {
  T_mainin = 0,
  T_mainout = 1,
  T_bsmtin = 2,
  T_bsmtout = 3,
  T_OAT = 4,
  T_BFlr = 5,
  T_UFlr = 6,
  T_Foot = 7,
  T_Art = 8,
  HB_set = 9,
  T_Lab = 10,
  T_Wine = 11,
  T_Dirt = 12,
  T_Ebed = 13,
  T_Wbed = 14,
  T_Gym = 15,
  T_Gymlo = 16,
  T_Gymhi = 17,
  T_Fplace = 18,
  T_Main2 = 19,
  T_Mainhi = 20,
  T_Cool1 = 21,
  T_Cool2 = 22,
  CG_Set = 23,
  HM_Set = 24,
  T_Main  = 25,
  T_MBR = 26,
  CB_Set = 27,
  T_OAT2 = 28,
  T_Car = 29,
  T_Studio = 30,
  T_Paint = 31,
  T_Painthi = 32,
  T_Shop = 33,
  T_Shophi = 34,
  T_OAT3 = 35,
  T_Plants = 36
};

with all your help, I might actually become a coder.

One note to thapthoptheep. The print() method will not compile if the variable is a pointer, eg

float *p = &nums[0] ;[color=#222222][/color]
lcd.print(p) ; // would not compile

but if I added a cast, then it compiled

float *p = &nums[0] ;
int ptr = (int)p ; // I know it's already an int, but the print(0) method wouldn't compile without this??[color=#222222][/color]
lcd.print(ptr) ;

and I definitely wanted to see the address, not the value at the address.

Dr Quark

I see, maybe I misread the situation.

Yeah, lcd.print() doesn't have a function to handle that type of parameter:

exit status 1
no matching function for call to 'print(float*)'

The print() method doesn't know how to handle a pointer to float, you could always do the cast conversion like:

  lcd.print((int) p);

But it's six and half a dozen - it stands to reason you'd have to perform a cast and I'm not sure what the problem actually is. You could always write your own print() method that will accept and handle a pointer as a parameter but in the end you're still gonna have to do some casting if you want this method to understand the input you give it.

enum's are like name constants. In this example:

  • enum day {Mon, Tue, Wed, Thu, Fri, Sat, Sun};*

They are referenced like an object and, by default, the enum list start at zero (i.e., day.Mon = 0). You can override the default number by assignment:

  • enum day {Mon, Tue, Wed, Thu, Fri, Sat = 10, Sun};*

which would mean day.Sat = 10 and day.Sun = 11.

in most circumstances, I prefer symbolic constants because they are typeless.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.