Pointer/reference question

I have this code I have been working on for over 6 months. Some of you have seen previous iterations and helped me through some of my struggles. The code works fine, right now I am still trying to learn from it, and optimize it a little bit more.

One of the things that stuck out to me, and this is small mind you, but I am looking for more "precious" bits...

I have a button class, in the definition one of the variables is:
const char *text;
My understanding is this is a string pointer.

The constructor is the following

 void initButton(int16_t xPos, uint8_t yPos, uint8_t butWidth, uint8_t butHeight, uint16_t buttonColor, uint16_t textColor, uint8_t textSize, const char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    bcolor = buttonColor;
    tcolor = textColor;
    tsize = textSize;
    render();
  }

There is a reference to the pointer? I'm really trying to figure out how this all works. Anyway, what I want to be able to do is save a couple of bits...
There are a number of buttons that have the same "text" in the button, I would like to use a string array and point to it in the initButton since this button appears in 4 different parts of my code. I figure it would save space to use a pointer instead of the string in every instance.

 btn[4].initButton(90, 200, 140, 25, ILI9341_BLUE, ILI9341_WHITE, 2, " Main Menu");

I think to create the string array it would be:

char mainMenu[11] = {" Main Menu"};

But not sure where to go from there...

There are 4 buttons with " Main Menu", 4 buttons with "+" and 4 buttons with "-" I would like to apply this to. The other buttons need to be unaffected.

And here is the entire button class:


int8_t blockWidth = 20;  // block size
int8_t blockHeight = 20;
int16_t blockX = 0, blockY = 0;  // block position (pixels)

class ScreenPoint {
public:
  int16_t x;
  int16_t y;

  // default constructor
  ScreenPoint() {
  }

  ScreenPoint(int16_t xIn, int16_t yIn) {
    x = xIn;
    y = yIn;
  }
};

//Touch Screen
ScreenPoint getScreenCoords(int16_t x, int16_t y) {
  int16_t xCoord = round((x * xCalM) + xCalC);
  int16_t yCoord = round((y * yCalM) + yCalC);
  if (xCoord < 0) xCoord = 0;
  if (xCoord >= tft.width()) xCoord = tft.width() - 1;
  if (yCoord < 0) yCoord = 0;
  if (yCoord >= tft.height()) yCoord = tft.height() - 1;
  return (ScreenPoint(xCoord, yCoord));
}
ScreenPoint sp;

class Button {
public:

  int16_t x;
  uint8_t y;
  uint8_t width;
  uint8_t height;
  uint16_t bcolor;
  uint16_t tcolor;
  uint8_t tsize;
  const char *text;

  Button() {
  }

  void initButton(int16_t xPos, uint8_t yPos, uint8_t butWidth, uint8_t butHeight, uint16_t buttonColor, uint16_t textColor, uint8_t textSize, const char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    bcolor = buttonColor;
    tcolor = textColor;
    tsize = textSize;
    render();
  }

  void render() {
    tft.fillRect(x, y, width, height, bcolor);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(tsize);
    tft.setTextColor(tcolor);
    tft.print(text);
  }
  bool isClicked(ScreenPoint sp) {
    return ((sp.x >= x) && (sp.x <= (x + width)) && (sp.y >= y) && (sp.y <= (y + height)));
  }
};

Button btn[19];  // Array for touch screen buttons.  Buttons are initialized on the pages they appear

Someone may say, "post your entire code". It is on the longer side and would take time for someone to navigate it, especially since my arrangement of it still needs work.

You on the right track.

This

char myCString[] = "Programming.\0";

I just found googling to be sure; I think the '\0' is redundant/unnecessary, I've not seen it used.

Then, by the magic of C/C++'s intimate relationship between pointers and arrays, you can pass myCString to your constructor, viz:

btn[4].initButton(90, 200, 140, 25, ILI9341_BLUE, ILI9341_WHITE, 2, myCString);

a7

1 Like

Yeah, the '\0' is put there by the compiler when you use double quotes. You don't need to put it there explicitly unless you are putting it in the middle creating two strings in one go.

char myTwoStrings[] = "Hello\0World"
Serial.println(myTwoStrings);
Serial.println(myTwoStrings + 6);

Should print

Hello
World
1 Like

Ok, Yeah, I was close. My issue was in the init button statement, I was actually trying to use *mainMenu. This would create a compiler warning and the button wouldn't have any text on it.

btn[4].initButton(90, 200, 140, 25, ILI9341_BLUE, ILI9341_WHITE, 2, *mainMenu);

This on the other hand works just fine.

btn[4].initButton(90, 200, 140, 25, ILI9341_BLUE, ILI9341_WHITE, 2, mainMenu);

Still struggling a bit with the pointers and references...

Anyway, now onto a question that arises from your post...

The difference between

char mainMenu[11] = {" Main Menu"};

and

char mainMenu[] = {" Main Menu/0"};

Wouldn't these differ in that I am declaring the array size keeping in mind the null terminator vs not declaring an array size and adding the null terminator in the array init?

Besides that, make it const
const char myCString[] = "Programming";

And, it will only work if the array is global or local static. Otherwise the pointer in the class member will end up pointing to invalid memory when the calling function terminates.

2 Likes

Loose the curly brackets, this is all you need:
char mainMenu[] = " Main Menu";
The compiler will add the null terminator and set the array size appropriately.

1 Like

Not really. In both cases the compiler is going to add the null terminator to the end of the literal string. When you write something in double quotes you can just assume that it has a null terminator.

In the first assignment:

You've explicitly created an array of 11 char. Then you've assigned the string which is also 11 char long and it is copied into the array.

In this one:

You're letting the compiler fill in the size. So it looks and the string in this case is 12 characters long. There are two null terminators in this case, the one you put there and the one the compiler adds to the string in double quotes.

So it puts a 12 in the brackets and mainMenu is now an array of 12 char and has an extra redundant null terminator.

1 Like

One thing that helped me to understand pointers better was to realize that I'd been using them all along. That's all an array is. The name of an array IS a pointer to the first element. Putting the brackets and a number is the same as dereferencing it.

These two statements are equivalent:

myString[3] = 'T';
*(myString + 3) = 'T';

The [3] is the same thing as adding to the pointer using pointer math and dereferencing.

1 Like

I changed the array to:

const char mainMenu[] = " Main Menu";

And added in the the other two I wanted.

Made no difference the the program size or dynamic memory.
Still at 32128 and 74%. No compiler warnings and it work, so there is that.

The compiler is way smarter than you are when it comes to optimizing memory use. It's quite possible your source code changes resulted in no object code changes as the string literals may have already been consolidated.

3 Likes

Are you saying you can declare the variable like the latter? Or the latter is used in your code when you want to change the string?

But I think I see what you are posting.

myString[3] = 'T';

This is saying I have an array size of 3 and that = 'T'. Do the quotes take up part of the array size? Hmm...

And this:

'*(myString + 3) = 'T';'

Says dereference myString, so we are not pointing to the space in memory, we are going to use that space in memory and the next 2 spaces after it and assign 'T' to it.

I think. I may have to go watch those sections again, lol.

No no no no. This is setting element 3 of an array that already exists. If it were creating an array it would have char in front of it.

Not at all. The declaration is the one place that makes a difference. When you just declare a pointer, then that's all you've created. When you declare an array you create a pointer AND some space for it to point to.

myString[3] = 'T';
This says to assign a 'T' to the 3rd slot of the myString array.

*(myString + 3) = 'T';
This says to assign T to the location 3 slots after where myString points.

But those are both putting a T in the same place.

Good lord, I feel like an idiot on that one...

Ok, this has to do with the 3rd element in the string array, not the whole thing. Ok, I think I am good.

Two null terminators ?

You aren't done until you can say what's in xx after this:

char xx = "abc"[1];

As for mistakenly adding the '*', C has a nice concept which I can't find now in the book, something like "use mimics declaration", I'll try to find that, it helps when you do stuff like that.

I've overheard real C programmers, probably not good ones, talking about just flailing around throwing in '*' or '&' until code compiles and does what they want.

Better to know…

a7

I'm probably missing some small detail again, but I'm not following that one.

Ok, I guess technically the first one isn't a terminator.

I guess I should say this string has two nulls. There's the one explicitly put there inside the quotes and the one that's added to the end of the literal string by the compiler.

What if it were:

char xx = *("abc" + 1);

The string literal is going to be in memory as an array of 4 characters. In this line of code it will be treated like a pointer to itself.

No; the first element is at index zero, the same address the pointer itself "points to". The element at index 3 is the 4th one.

This is why the standard for loop uses less-than

int values[] = {9, 8, 7, 6};  // compiler will count these for you
int total = 0;
for (int i = 0; i < sizeof(values) / sizeof(int); ++i) {
  total += values[i];
}
Serial.println(total);
Serial.println(sizeof(int));
Serial.println(sizeof(values));

Is the /0 actually a null or is it just two characters, ie a forward slash followed by a zero, that will be printed ?