Issues with Pass Array of Structures to Library by Reference

I am humbled to be back at the well so soon but I have spent the better part of the day fighting with pointers. I am able to pass a pointer for one row of my structure but not for the entire structure. I recognize that I am doing something stupid and would appreciate a [gentle] slap on the head...here is my code

The called CPP library:

#ifndef Forms4Utft_h
#define Forms4Utft_h

class Forms4Utft {
   public:
        String test(struct F4Utft*);
};
#endif

The library .H

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

struct F4Utft {
    String name;
    int x;
    int y;
    String formatName;
};

String Forms4Utft::test(struct F4Utft* menu) {
    return(menu[0].formatName);
}

The calling INO:

#include <UTFT.h>
#include <UTouch.h>

#include <Forms4Utft.h>

Forms4Utft forms;

struct F4Utft {
    String name;
    int x;
    int y;
    String formatName;
};

const int DEF_MENU1 = 3;

struct F4Utft Def_Menu[DEF_MENU1] = {
     {"selStats", 15, 195, "BigFontS"},
     {"selGraph", 215, 195, "BigFontS"},
     {"line2", 15, 185, "None"},
};

void setup() {
    // For debugging messages
    Serial.begin(57600);

    struct F4Utft ptr = Def_Menu[2];

    Serial.println("--->" + Def_Menu[0].formatName);
    Serial.println("--->" + String(forms.test(&ptr)));
}

void loop() {
    while (true) {

    }
}

When I do what I think should be done:

Change...
    struct F4Utft ptr = Def_Menu[0];
...to:
    struct F4Utft ptr = &Def_Menu;

I get errors: conversion from F4Utft[3] to non-scalar type F4Utft requested

What you need there is not an array of structs, but an array of pointers to structs.

There are two syntaxes that you want to know for that:

myStruct **structArrayPtr;

and

myStruct *structArrayPtr[];

Both do pretty much the same thing - the latter is the easier to get your head around.

You need to pass the address of the start of the array of pointers, and tell it that it is an array of pointers. Note, I haven't tested this, so it may not work :wink:

struct F4Utft *Def_Menu[DEF_MENU1] = {
     {"selStats", 15, 195, "BigFontS"},
     {"selGraph", 215, 195, "BigFontS"},
     {"line2", 15, 185, "None"},
};

String Forms4Utft::test(struct F4Utft* menu[]) {
    return(menu[0]->formatName);
}

/...

    Serial.println("--->" + String(forms.test(&Def_Menu)));

Or something along those lines. It should give you some clues anyway.

Majenko is right. In fact, his statement that:

myStruct **structArrayPtr;
myStruct *structArrayPtr[];

"do pretty much the same thing" is spot-on. Indeed, they are exactly the same thing. If you want a good way to parse out complex data declarations in C or C++, Dr. Purdum invented the Right-Left Rule 30 years ago in his C Programming Guide for that exact purpose. You can find out how to use the Rule at:

http://jdurrett.ba.ttu.edu/3345/handouts/RL-rule.html

If you've ever tried to figure out what:

double (*(*pf)())[3][4];

actually defines, the Right-Left Rule can help.

econjack:

My degree in CS dates back a long long ways...in fact so far back that "C" was only just starting to gain in popularity and Unix System V had just come out of Bell Labs into the university community. Pointers made my head hurt then and they still do today!

majenko:

Thanks for the pointer as it did put me exactly on the path to what I need. What I ended up with is the below all pulled into one sketch:

#include <UTFT.h>
#include <UTouch.h>

// #include <Forms4Utft.h>
// Forms4Utft forms;

struct F4Utft {
    String name;
    int x;
    int y;
    String formatName;
};

const int DEF_MENU1 = 3;

static struct F4Utft m01 = {"selStats", 15, 195, "BigFontS"};
static struct F4Utft m02 = {"selGraph", 215, 195, "BigFontS"};
static struct F4Utft m03 = {"line2", 15, 185, "None"};

struct F4Utft *Def_Menu[] = {&m01, &m02, &m03};

void setup() {
    // For debugging messages
    Serial.begin(57600);

    Serial.println("--->" + Def_Menu[1]->formatName);
    Serial.println("--->" + String(test(Def_Menu)));
}

void loop() {
    while (true) {

    }
}

String test(struct F4Utft** menu) {
    return(menu[1]->formatName);
}

The initializer gave me fits and there is another format (single statement with a structure declaration before each entry) that I liked better could not get to work...but this does so I am happy and can move on!

My next challenge is to try and understand why I can not seem to put this resultant structure into PROGMEM without it killing my script (any ideas?)

Thanks again for the help,
Wisar and a little Wiser

Wisar:
I am humbled to be back at the well so soon but I have spent the better part of the day fighting with pointers. I am able to pass a pointer for one row of my structure but not for the entire structure. I recognize that I am doing something stupid and would appreciate a [gentle] slap on the head...here is my code

Gently now, C pointers are just addresses with data type info. The details you write on a card until you get used to them again. Thank someone you're not trying to deal with trig and log identities, huh?

I can make a pointer to int and set it to the base of an int array of known dimensions. If I know how the array is arranged then I can reach any spot by adding a number to that base address.
Simple example.
int A[ 10 ] = { 0,1,2,3,4,5,6,7,8,9 };

the base pointer is A, the name of every 1 dimension array is a pointer
if I want the 1st element then

int X = *( A + 0 );

the * can be thought of as 'at'
the parens are to evaluate A + 0 as an address before using 'at' to reference it

in a loop or operation or arg passed to a function, ( A + i ) works as well.

or I can go A[ 0 ] or A[ i ] after passing A as the base address.

Multiple dimensions.
int A[][], the name A is a pointer to a pointer. But the entire array is still ints, one after the other 2 bytes wide so a simple int pointer can address the whole shebang and offsets are not really that hard to work out.

What goes for ints goes for structs.

In practice, use the basics to keep from getting completely lost and draw the occasional map of your data. I hope this helps, I'm sure it's stuff you know somewhere.

The first code I wrote that ran on a computer was Fortran 4 on punch cards in 1975. I'd hate to try Fortran today.

I took your last piece of code and modified it to work as I think you want it to work. It also reinforces what GoForSmoke says about pointers:

#include <UTFT.h>
#include <UTouch.h>

// #include <Forms4Utft.h>
// Forms4Utft forms;

struct F4Utft {
    String name;
    int x;
    int y;
    String formatName;
};
const int DEF_MENU1 = 3;

struct F4Utft Def_Menu[] = {
     {"selStats", 15, 195, "BigFontS"},
     {"selGraph", 215, 195, "BigFontS"},
     {"line2", 15, 185, "None"},
};
//struct F4Utft *Def_Menu[] = {&m01, &m02, &m03};

void setup() {
  int i;
  
    // For debugging messages
    Serial.begin(57600);

  for (i = 0; i < DEF_MENU1; i++) {
    Serial.println("--->" + Def_Menu[i].formatName);
    Serial.println("--->" + String(test(&Def_Menu[i])));
  }
}

void loop() {
    while (true) {

    }
}

String test(struct F4Utft* menu) {
    return(menu->formatName);

Note how I left out the size of the array in its definition:

struct F4Utft Def_Menu[] //...rest of the definition

This is always a good idea because compilers are good at counting elements in initializer lists and, if you decide to add more elements later on, you just add the element, recompile, and you're done. You don't have to remember to adjust the element count.

Also, always keep in mind that an array name, such as DefMenu, is exactly the same as the base address (i.e., the lvalue) of the array. Therefore, if I want to march through an array using a for loop and want to look at things along the way, I need to use the address of a particular element of the array. That's why the address-of operator (&) is used in the call to your test() method. Also, the indirection operator (i.e., *) is always used to fetch the contents of what it stored at a given memory address (i.e., the rvalue). If you want to read about lvalues and rvalues and Dr. Purdum's Bucket Analogy, try starting at page 80, Defining a Variable, at:

A similar discussion appears in his Beginning C Programming for Arduino, but it has two chapters just on using pointers. Unfortunately, this is a ripoff of Dr. Purdum's book, since the entire book is online for free but I do think it will help you understand pointers.

econjack:
A similar discussion appears in his Beginning C Programming for Arduino, but it has two chapters just on using pointers. Unfortunately, this is a ripoff of Dr. Purdum's book, since the entire book is online for free but I do think it will help you understand pointers.

I'm confused, didn't you write "Beginning C Programming for Arduino"?

econjack:
Note how I left out the size of the array in its definition...

This is always a good idea because compilers are good at counting elements in initializer lists and, if you decide to add more elements later on, you just add the element, recompile, and you're done. You don't have to remember to adjust the element count.

Remember the opposite too, by specifying a size, you can create an array larger than the list, allowing you to only initialise the elements needing a default value, which in turn generates less code in the initialisation vector.

econjack:
Also, always keep in mind that an array name, such as DefMenu, is exactly the same as the base address (i.e., the lvalue) of the array.

With a 2D array it is a bit more complicated to get at, but the address is there.

Therefore, if I want to march through an array using a for loop and want to look at things along the way, I need to use the address of a particular element of the array. That's why the address-of operator (&) is used in the call to your test() method.

To address an element I add the number of elements into the array to the base. Usually I assign that to a pointer but last night when I wrote that post and now I'm too tired to lay it all out. If I have something that I can & I don't need to compute an address.

Also, the indirection operator (i.e., *) is always used to fetch the contents of what it stored at a given memory address (i.e., the rvalue).

int A[ 10 ] = { 0,1,2,3,4,5,6,7,8,9 };

int *B = A + 5; // B is an int pointer now pointing at A[5]

int C = *B + 10; // C now == 15

*B = 21; // *B is A[5], the * makes the pointer act as the variable, A[5] now == 21

Here is a nice tutorial on pointers, better than I can write and more than I can post.
http://pw1.netcom.com/~tjensen/ptr/pointers.htm

pYro_65:

Yes, I wrote both of the books. However, I thought it more important for Wisar to understand pointers than sell another book. Also, after teaching this stuff to literally thousands of students over the years, I kinda know where most will stumble when it comes to pointers. I genuinely think that my teaching method for pointers is useful to students and the Bucket Analogy is very useful for understanding many things, including pointers and the need for casts. The web site I mentioned is in Turkey and there's little my publisher can do to shut such sites down. Indeed, Wrox (the publisher) has a group of six people who spend most of their time ferreting out such sites. Last time I talked with them, they had shut down over 20,000 sites that were ripping off their books over the years, and it hasn't even put a dent in the problem. The end result is that publishers and writers end up the losers. I actually enjoy writing, so the money isn't why I write books. However, such sites probably do discourage some good writers from expending the effort to write a book. What really gets me PO'ed is when someone reads a ripped off copy like this and then bitches that there are spelling errors everywhere. That happened to me on an Amazon review. The guy should have known that no publisher is going to the word "frst" escape editorial review when the word should be "first". Does he really think publishers and writers don't have and use spell checkers? Still, the bad review stands and the Amazon customer thinks the publisher/writer screwed up when the blame should fall on whoever engaged in faulty copyright infringement. Oh well...

You are right: You can state the array size explicitly to something greater than the initializer list and get empty elements in the array if you want, although with the small amounts of memory in the Arduino, most of the time you wouldn't want to do this.

GoForSmoke:
Your line that defines and initializes the pointer variable B:

int *B = A + 5; // B is an int pointer now pointing at A[5]

can also be written a definition followed by its initialization as in :

int *B; // B is an int pointer
B = A + 5;

It's important to recognize the purpose of the keyword int in the definition of B. The int keyword sets the scalar size of the pointer. That is, the expression B++ adds sizeof(int) bytes to the memory address stored in B. The B++ will add 2 bytes to the base address of A, making it point to A[1]. If B happens to be a pointer to long, the base address would increase by 4 bytes. So if A[] happens to reside at memory address 1000, the expression A + 5 actually works out to the base address of A[] (i.e., 1000) plus 10. Anytime there is a pointer variable on the left side of the assignment statement that does not have an asterisk (*) or address-of (&) operator in front of it, the pointer is receiving a memory address in the assignment (e.g., B is now 1010). This reinforces the idea that the rvalue of a valid pointer variable can only be a memory address or null. Anything else will cause you to shoot yourself in the foot. For example:

int *B; // B is an int pointer now pointing at ???
B++;

Because B is not initialized to point to anything useful, the second statement simply increments by 2 whatever random bit pattern happens to be in memory at B's memory address. On many operating systems, this draws an error message because the pointer was initialized to 0 by default (e.g., a null pointer) and trying to increment a null pointer is flagged as an error. This does not cause an error in the Arduino IDE. In the old days, setting the pointer to 0 and then incrementing it with B++ caused B to point to the second byte of the operating system...almost never a good idea when you then use B in an assignment statement. The whole idea of pointers is extremely powerful, but can open up multiple cans of worms if you don't understand what your doing. Maybe that's why Java never made pointers part of the language!

That's nice. I think that maybe the best book I have on C/C++ was written by Kaare Christian for Borland C/C++. He was very good on technique, something not so easy to find.

If you want a neat language for address use, learn Forth.

GoForSmoke, EconJack, Pyro: Thanks for giving me so much to think about! Great discussion. Pointers still make my head hurt but to work in Arduino'land I guess I will have to deal.

EconJack: I had actually arrived at your version of my script but was hoping for a way to pass a single pointer to the whole mess and then have the function navigate the array? At this point I think I need to step back and really try to understand what is happening, and what has been said above, rather than to keep flailing at the ball! I also have this dream of using PROGMEM but that is another topic.....

Cheers,
Wisar

Little bit of simplifying. Try it out.

#include <UTFT.h>
#include <UTouch.h>

// #include <Forms4Utft.h>
// Forms4Utft forms;

struct F4Utft 
{
  char name[ 10 ];
  int x;
  int y;
  char formatName[ 10 ];
};

const int DEF_MENU1 = 3;

F4Utft  tft[ DEF_MENU1 ] = 
{ 
  "selStats", 15, 195, "BigFontS",
  "selGraph", 215, 195, "BigFontS",
  "line2", 15, 185, "None"
};

void setup() 
{
  // For debugging messages
  Serial.begin(57600);
  byte i;

  Serial.println();
  for ( i = 0; i < DEF_MENU1; i++ )
  {
    Serial.print( i, DEC );
    Serial.print( "--->" );
    Serial.println( tft[i].name );
  }

  Serial.println();
  for ( i = 0; i < DEF_MENU1; i++ )
  {
    Serial.print( i, DEC );
    Serial.print( "--->" );
    Serial.println( test( tft + i ));
  }

  while (true); // never reaches loop()
}

void loop() 
{
}

char *test(struct F4Utft* menu) 
{
  return( menu->formatName );
}

Below is the output from a sketch that tries to demonstrate a lot of the pointers on pointers that I got from this thread. The code was too long to post here but is available here: https://docs.google.com/file/d/0BxF8IoGgsWZwdUtZMmE5MTdHYzA/edit?usp=sharing I know there are probably a zillion more ways to address things but these feel like the ones that I will be using for my current projects!

Pointer to an integer
a is now = 200
Another way to use that pointer...
a is now = 300
...which allows you to do this
a is now = 301

Pointer to a char array
abcdefghijklmnopqrstuvwxyz0123456789 <- Printed using original assignment of 'myS'
a <- Printed using '*myS'
a <- Printed using pointer '*ptrmyS'
abcdefghijklmnopqrstuvwxyz0123456789 <- Printed using pointer 'ptrmyS'
abcdefghijklmnopqrstuvwxyz0123456789 <- Printed by walking pointer down char array
abcde#ghijklmnopqrstuvwxyz0123456789 <- after modifying using array notation
abcde#ghij#lmnopqrstuvwxyz0123456789 <- after modifying using pointer notation

Printed in a function using a variable...
abcde#ghij#lmnopqrstuvwxyz0123456789 ...passed using original assignment
abcde#ghij#lmnopqrstuvwxyz0123456789 ...passed using pointer '*ptr2myS
abcde#ghij#lmno#qrstuvwxyz0123456789 ...passed using pointer '*ptr2myS and modified

Pointers to an integer array...
...int *B = A + 5; = 5
...int C = *B + 10; = 15
...*B = 21; = 21

Pointers into a two dimensional array...
abcdefg <- Printed as myData[3]
d <- Printed as myData[3][3]
e <- Printed as *( myData[3] + 4 )
abcdefg <- Printed as myDataAsPtr[3]
defg <- Printed as myDataAsPtr[3] + 3
a <- Printed as *myDataAsPtr[3]
t <- Printed as *( myDataAsPtr[1] + 3 )
22222 <- Printed as *ptr after 'const char (*ptr)[10] = (myData) + 2;'

Pointers into a two dimensional array...
abcdefg <- Printed as myData[3]
d <- Printed as myData[3][3]
e <- Printed as *( myData[3] + 4 )
abcdefg <- Printed as myDataAsPtr[3]
defg <- Printed as myDataAsPtr[3] + 3
a <- Printed as *myDataAsPtr[3]
t <- Printed as *( myDataAsPtr[1] + 3 )
wertyu <- Printed as *ptr1 after 'const char (*ptr1)[10] = (myData) + 2;'

Pointers into an array of structures...
line2 <- Printed via function passed a pointer to a row
selGraph <- Printed via a pointer to a row
selStats
selGraph
line2
<- Printed via function passed the whole blob

So your comfort zone is beginning to include pointers?

When using the sting.h functions, pointers abound as they're the best fit so often.

You probably have this web page bookmarked:

But do you have this? It gives you doc for the AVR libraries we use, like string.h:
http://www.nongnu.org/avr-libc/user-manual/modules.html

GoForSmoke:

Comfortable not yet...but ironically I very vaguely remember doing a similar exercise trying to get familiar with C and pointers at some point around about 1978 or so on a PDP-11 running a very early release of Unix System V. Except that now you can run Linux on a credit card sized computer.

That second link is a real good one...needed it a bit earlier when trying to get a CRC routine working :-o

Thanks,
Wisar