Using pointers to values in functions (increment/decrement)

This may be basic 101 stuff everyone else knows. This might not be the right part of the forum to post this, but I tried to choose carefully.

I learned a lesson the hard way. As debugging on the Arduino isn't quite like Visual Studio.NET (it's not easy to follow variable values as the code executes), I'll pass this along in case it helps anyone.

I have a function, I want it to modify some of the values I pass into it. Passing in pointers is fairly well documented. The problem I had was in changing the values using the increment/decrement operators.

Inside the function we use something like *foo or *bar to refer to the value. *foo = *foo + 1; works and if *foo pointed to a value of 5 it will now point to a value of 6. But *foo++ doesn't change the 5 to 6, it changes what the pointer points to. To change the 5, in this example, you need to do this: (*foo)++;

I had been passing in values and for some I used the *var = *var + 60; and got the expected results. But when I just needed to change the value by 1, I tried *var++; and got...unexpected data. Searching online I first ran across a Microchip article which suggested (*foo++) but that didn't work for the Arduino implementation of C++.

Everything was great when the conditions only called for the modification of those variables I used the *foo = *foo + value; but when the conditions changed to require the modification of variables for which I used *foo++;, things went south. That lack of knowledge cost me about an hour.

--HC

make a stand alone code using (*foo)++;
if it is not working post your code and you might get help here.

In C++:

int v1 = 5;
int* ptr = &v1;
int v2 = *ptr++;

This copies the value 5 of v1 into v2, and moves ptr to the next address in memory (which is unrelated and likely invalid in this case).

The reason is that in *ptr++, the post-increment (++) has higher precedence than the dereference (*).

So it’s evaluated like this :
First ptr++ evaluates as the current value of ptr, whilst increments ptr.

Then * dereferences the value returned by ptr++ (so ptr)

That’s why it reads the value at ptr, whilst moving ptr to the next address.

Precedence is tricky and indeed best to use parentheses when unsure.

The *ptr++ notation is used when traversing arrays for example.

1 Like

It might help to note a common loop body to copy bytes (or bigger) from src to dest is

  *dest++ = *src++;

It's not nearly as useful if the precedence worked the other way.

When using parentheses to force a non-default precedence, it's worth considering a clearer way to do the same thing. In this case

  *var += 1;

isn't much longer, and easily works with 60 or any other value.

2 Likes

Thank you for that. I had to read that a few times. I think I've got it.

Line 3 actually does this:

int v2 = v1;
ptr++;

Where we might be expecting it to yield v2 == 6 instead it's v2 == 5 and now the pointer is incremented to point at...whatever, which, as you said, probably is invalid.

One thing I wasn't recognizing is that the parentheses were still doing what they'd do in any other code: manually setting a precedence: dereference foo then foo++.

I see now why the *ptr++ would be very helpful for navigating an array. I also, as I was side-checking what I was thinking and writing, found that the ++ can be at the beginning to do it first before the value is used. I didn't know that. Yeah, I don't know much.

Thanks again, this was helpful.

--HC

Thank you for this. It took a hot minute to grasp what @J-M-L had written but that time made it easier to understand what you've written.

I very much see how that would be a slick way to bang through a number of data to copy. But you make a very good point about the forced dereference: the "+=" is clearer than the overridden precedence.

--HC

Please, moderate the following diagram (Fig-1) and sketch.


Figure-1:

void setup()
{
  Serial.begin(9600);
  int data1 = 0x1234; //data are saved in decreasing RAM memory locations
  int data2 = 0xABCD;
  int data3 = 0x5678;
  int *ptr; //pointer variable ptr holds address of a word-wide location

  ptr = &data1; //ptr holds address of data1
  Serial.println((unsigned int)ptr, HEX); //shows: address of data1: 08FA
  int rdData1 = *ptr;
  Serial.println((unsigned int)rdData1, HEX);  //shows: 1234
  Serial.println();

  ptr = &data2; //ptr holds address of data2
  Serial.println((unsigned int)ptr, HEX); //shows:address of data2: 08F8
  int rdData2 = *ptr;
  Serial.println((unsigned int)rdData2, HEX);  //shows: ABCD
  Serial.println();

  ptr = &data3; //ptr holds address of data3
  Serial.println((unsigned int)ptr, HEX); //shows:address of data3: 08F6
  int rdData3 = *ptr;
  Serial.println((unsigned int)rdData3, HEX);  //shows: 5678
  Serial.println();
}

void loop()
{

}
1 Like

Since Arduino is programmed in C++, you're better off using references instead of pointers for this type of thing:

void tripple(int &var);

void setup() {
  Serial.begin(115200);
  delay(1000);

  int x {100};
  Serial.print("Before Calling Function, x = ");
  Serial.println(x);
  tripple(x);
  Serial.print("After Calling Function, x = ");
  Serial.println(x);
}

void tripple(int &var) {
  var *= 3;
}

void loop() {
}

Output:

Before Calling Function, x = 100
After Calling Function, x = 300

That is a very good example. I'm going to have to re-read it a few more times and let that sink in. But I got a lot out of that.

Thank you.

--HC

Thank you for the reply. I'm going to have to learn a bit to understand this. In my head, "by pointer" and "by reference" are the same thing. Obviously, from your post, they're not the same thing, hence why I need to learn something.

Here is the function heading to which I am passing pointers:

void AdjustOpenCloseTimes(int* OpenTH, int* OpenTM, int* CloseTH, int* CloseTM, unsigned long int SR, unsigned long int SS, int SRO, int SSO)

I call it thusly:

AdjustOpenCloseTimes(&OpenTimeHour, &OpenTimeMinute, &CloseTimeHour, &CloseTimeMinute, sunrise, sunset, SunriseOffset, SunsetOffset);

I think now that I don't know enough about the use of * and & and pointers and references to intelligently articulate anything further. It's past 2AM...I'm going to bed and I'm going to go read about those over coffee in the (later) morning. Then I'll post back.

--HC

They are not :slight_smile:

In C++, passing by pointer allows a function to access and modify the original variable through an explicit address using * and & operators, while passing by reference provides direct access to the original variable without requiring explicit dereferencing. Note that a pointer can be set to nullptr to indicate it points to nothing, whereas a reference must always alias a valid object and cannot be null.

you are missing the types. The & just informs the compiler that you want to pass by reference

void AdjustOpenCloseTimes(int& OpenTH, int& OpenTM, int& CloseTH, int& CloseTM, unsigned long int SR, unsigned long int SS, int SRO, int SSO) 
{
  ...
}

and of course, since they are references and not pointers, in the function you refer directly to OpenTHor OpenTM etc when you want to read or modify their content. No need to dereference with a *.

Yes, but it seems to me to be just a syntactic difference handled by the compiler.

Perhaps for this simple application. Although references do provide a couple safety features even here … As mentioned, you are always assured that a reference is bound to a valid object (verses the possibility of being passed an invalid pointer). And, with a pointer, the function could access unintended memory by doing pointer arithmetic (not possible with a reference).

However, references are indispensable for implementing more advanced C++ features. Perhaps the most of important being Operator Overloading.

You can think of the situation as a Venn Diagram where the possible uses of pointers and references intersect. In some applications you can use either. But there are some applications where only either one or the other will work.

1 Like

That’s what meets the eyes at first. As you dig into C++ you’ll see that references offer safer, clearer, and semantically stricter indirect access than pointers, which makes them the default choice unless nullability or reseating is explicitly needed (Once a reference is bound to an object, it can’t be reseated to another object, which makes the code safer and more predictable versus pointers which can be reassigned).

2 Likes

Okay, so, I lied...I got in bed and read about pointers and references before I went to sleep...a little obsessive.

I see now how they are different but it's going to take a bit of time to let that really sink in. I very much see the difference between their ability to be null or not and the need or not to explicitly dereference them.

In the code example provided by @gfvalvo I like the "var *= 3;" clarity (to me) of not having an explicit dereference.

There may (or may not...seems to be some variance) in whether or not an address storage space is used for a reference.

The lines of code I posted are from a working sketch. The &OpenTimeHour etc are in the function call not the definition.

I see now the difference but, again, it's going to take a bit of time for the understanding to really settle in. To bring that back to clarify my original post about dereferencing a pointer with the post-increment operator: where I point out the need to use () in (foo*)++, if this was passed by reference I would simply use foo++;, correct? If so, that would have eliminated my (now silly to me) parenthetic overriding of precedence.

As I was proofreading...in one thread I started someone pointed out best practices and in some reading about the pointers/references it was stated to use references when possible and pointers when they aren't. Is this maybe more a matter of best practice, choosing between pointers and references?

--HC

The point about the safety of a reference always pointing to a valid object is a good one and maybe sufficient reason alone to use them when possible, at least for me. Watching garbage show up in the Monitor while the sketch doesn't do what is intended because of an accidental (misunderstanding of) incrementing a pointer instead of the target value is confusing. A reference would have prevented that in my case.

--HC

Yes, what you wrote. In one paragraph is the information I gathered over several pages I read across.

I'd only add: "...versus pointers which can be reassigned or mis-assigned". Done it. :slight_smile:

--HC

That's good advice and easy to follow. Go with it.

1 Like

Second guessing the compiler about such minutiae is a fool's errand.

1 Like

I don't like references, because it seems like it would be so simple to modify a structure that the caller function didn't intend to be modified. Having to explicitly pass pointers is a big "hint" that your data may be subject to change...