Pointers... again

Status
Not open for further replies.

ilium007

Well-known member
I am stuck again. Take a look at the code below and please tell me why I can't get the testClick function to print "6". I have spent 2 days trying to work this out, looking at various resources online. I just can't work it out.

I have simplified a class I am writing and a class I am extending in the code below. I think the problem I am having is with the referencing / de-referencing of pointers.

Code:
void testClick(void *id) {
  Serial.println( (int) id, DEC);
  Serial.println( (int) &id, DEC);
}

void setup() {
  Serial.begin(9600);
  Serial.println("start");

  int *buttonIdPtr;
  int buttonId = 6;

  buttonIdPtr = &buttonId;
  
  testClick(buttonIdPtr);
}

void loop() {

}

What I get is:

Code:
start
537362412
537362388

Which looks like memory addresses, I expect (want) to see the integer "6" printed to serial.

Background, I am extending the OneButton library (https://github.com/mathertel/OneButton) to suite my purposes of debouncing I/O expander and I'm trying to get my callback functions working (these will eventually generate CANbus messages from debounced button pushes on the I/O expander. The callback function must fit the typedef in the base OneButton library:

Code:
typedef void (*parameterizedCallbackFunction)(void*);

hence my class method which takes a void* as a parameter. I need a way to get to the value of the void pointer passed in.
 
Last edited:
It is printing the memory address, because you are assigning the memory address of buttonId as its value with buttonIdPtr = &buttonId; Try buttonIdPtr = buttonId instead, that works in your code. Alternatively, leave the assignment of the address via &buttonId and change testClick to this:

Code:
void testClick(int *id) {  
  Serial.println( (int) *id, DEC);
}
 
It is printing the memory address, because you are assigning the memory address of buttonId as its value with buttonIdPtr = &buttonId; Try buttonIdPtr = buttonId instead, that works in your code. Alternatively, leave the assignment of the address via &buttonId and change testClick to this:

Code:
void testClick(int *id) {  
  Serial.println( (int) *id, DEC);
}

I will give it a go, but the problem is that the underlying OneButton class requires a void * as defined in its typedef for the callback function that gets passed in:

Code:
typedef void (*parameterizedCallbackFunction)(void*);
 
Try something like:
Code:
void testClick(void *id) {
  Serial.println( *((int*) id), DEC);
}
 
Try something like:
Code:
void testClick(void *id) {
  Serial.println( *((int*) id), DEC);
}

Screen Shot 2020-04-26 at 10.47.28 pm.png

I wish I was smart :(

Ok, so that works..... can you please explain in words suitable for my intellect the pointer magic going on here?

Thanks :D
 
With the above: *((int*) id)
It says cast (int*) the variable id to be a pointer to a type of int. From there get the contents of that variable * ...

Yes I know cryptic...
More long hand version:

Code:
void testClick(void *id) {
  int *id_int = (int*)id;

  Serial.println( *id_int, DEC);
}
Or sorry in advance maybe more confusing.
Code:
void testClick(void *id) {
  int *id_int = (int*)id;

  Serial.println(id_int[0], DEC);
}
Which says take the first element (0) from that pointer as if it was an array.

Again why I mention this is often you will see places where maybe the calling code does something like:

Code:
int my_array[5] = {0, 1, 2, 3, 4};
test_click(my_array);
Note this could give compiler warning, so I would normally cast this to void*: test_click((void*)my_array);

Sorry if that added confusion, but often one of the next thing that comes up with pointers, is passing arrays.
 
The ordinary way to use pointers involves declaring them with the specific type of variable they access. Normally type casting should not be used with pointers. In a typical program "void *" (a pointer that doesn't specify what type of variable) should not be used.

For this specific case, since you know that variable at the location the pointer specifies is "int", the pointer should be declared as "int *", like this:

Code:
void testClick(int *id) {
  Serial.println(*id, DEC);
}

There certainly are exceptional cases where "void *" and pointer type casting makes sense. But those sorts of situations are the exception, not the rule. That sort of programming is difficult and error prone, even for experts. Just because you can do a thing does not necessarily mean you should.

When the type of variable at the location indicated by the pointer is clearly defined, which is almost always the situation for ordinary (not device driver) programming, you definitely should declare the pointer specifically for that type. Initially this can seem frustrating because you might get errors or warning from the compiler for wrong types. But those sorts of error messages are trying to help you. Don't "solve" them by using void or type casting, unless absolutely necessary.

Also as a general guideline, when you know a pointer will be used to only read the actual variable, best practice is to declare it with const. While this isn't usually necessary, it gives you some extra protection (compiler errors) if you accidentally do something that uses the pointer in a way where it could write to the variable.
 
Thanks for the detailed reply. After spending a few hours last night reading more about pointers I concluded that void pointers could be dangerous. In this case the state machine button library that I really like using implements the callback functionality with a method that accepts a function pointer and the void pointer parameter.
 
There certainly are exceptional cases where "void *" and pointer type casting makes sense. But those sorts of situations are the exception, not the rule. That sort of programming is difficult and error prone, even for experts. Just because you can do a thing does not necessarily mean you should.

I have been thinking about this some more....

I may look at re-writing the OneButton library but for now I'd like to know if there is any other way of doing what I am attempting to do.

The OneButton library takes a parameter to its attachClick method as below:

Code:
// save function for click event
void OneButton::attachClick(callbackFunction newFunction)
{
  _clickFunc = newFunction;
} // attachClick

// save function for parameterized click event
void OneButton::attachClick(parameterizedCallbackFunction newFunction, void* parameter)
{
  _paramClickFunc = newFunction;
  _clickFuncParam = parameter;
} // attachClick

And the typedef for the above pointer types:
Code:
typedef void (*callbackFunction)(void);
typedef void (*parameterizedCallbackFunction)(void*);

It can ether take a straight function pointer with no parameter tat will just run a callback function by itself, or it can take the parameterizedCallbackFunction with the additional void pointer. From what Paul posted above this is where it gets messy in that, in my case, I know I will be passing an int pointer but the class method could just as easily take an array pointer for example.

Is there a better way of doing this?

Is there any other way to have a callback function execute the callback WITH the dynamic parameters that may include different parameter types?

Should I modify the OneButton library to contain the callback types I will use? ie. do I add:
Code:
typedef void (*parameterizedCallbackFunction)(int*);
typedef void (*parameterizedCallbackFunction)(long*);

**update** that didn't work. I thought I could 'override' the typedef:
Code:
/Users/xxxx/Documents/Arduino/libraries/OneButton/OneButton.h:33:51: error: conflicting declaration 'typedef void (* parameterizedCallbackFunction)(int*)'
 typedef void (*parameterizedCallbackFunction)(int*);
                                                   ^
/Users/xxxx/Documents/Arduino/libraries/OneButton/OneButton.h:32:16: note: previous declaration as 'typedef void (* parameterizedCallbackFunction)(void*)'
 typedef void (*parameterizedCallbackFunction)(void*);
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

One last question... when a parameterizedCallbackFunction variable is instantiated:

Code:
typedef void (*parameterizedCallbackFunction)(void*);

parameterizedCallbackFunction exampleCallback;

how does the compiler know how much memory to allocate to the variable given the void* parameter? Or is it as simple as "the instantiation of the exampleCallback variable results in memory required to hold the two pointer addresses"?
 
Last edited:
how does the compiler know how much memory to allocate to the variable given the void* parameter? Or is it as simple as "the instantiation of the exampleCallback variable results in memory required to hold the two pointer addresses"?

On 32 bit processors (Teensy LC, 3.x, 4.x) any type of pointer is always 4 bytes. The pointer is just the numerical location where something else is located in the machine's memory space. So the same size number is used regardless of what type of thing the pointer actually references. On 64 bit computers (most PCs these days) when running 64 bit software, pointers are 8 bytes. The size of the pointer itself is determined by computer's maximum memory space.

How many bytes the compiler will read from that location depends on what type of variable the pointer is declared to reference. For an "int *", the compiler will read 4 bytes as a signed integer. For a "char *" is reads 8 bits.

For "void *" attempting to read is an error, because the compiler doesn't know what is supposed to actually be at that location. The size of the thing it points to is unknown or undefined for a void pointer. A void pointer can't be used to actually access the variable. That's why you need to type cast it to another a different pointer type, so the compiler can know how to actually access the variable.

One of the uses for void pointers is with callback functions. Suppose a library provides a way for a function to be called when an event occurs. Your usage might be to update an integer. But maybe someone else wants the event to change a string. Someone else might be using an array of floats. Usually generic callback setup takes a void pointer, so it can be used with any type of data. Then in your function that gets called, you get the void pointer, because the library was written without any knowledge of what type of data you will use. Usually the very first thing you do is declare a pointer to the type of data you're actually using, and assign it to have the value of the void pointer. Then you use your pointer to actually access your data.
 
That’s awesome. Thanks for your patience and excellent explanation.

For my testing this is what the callback looks like now:

Code:
void callbackClick(void *buttonId) {
    int* _buttonId = ((int*) buttonId);  //  cast the void pointer to an int pointer
    Serial.print("Button ");
    Serial.print( *_buttonId, DEC);  //  de-reference the int pointer
    Serial.println(" clicked");
}
 
Last edited:
I just want to add that using a bit of modern c++ you can easily work around that void* interface by using lambda expressions. The good thing is that this doesn't even need the parameterizedCallbackFunction but works with standard void(*callback)() type of callbacks. I.e. you can use this pattern for all libs providing only a void(*callback)() interface (e.g. IntervalTimer...)

Code:
#include "OneButton.h"

OneButton button1(0, true, true);
OneButton button2(1, true, true);
OneButton button3(2, true, true);

void myIntCallback(int cnt) // normal function taking an int parameter cnt and flashing cnt times
{
  for (int i = 0; i < cnt; i++)
  {
    digitalWriteFast(LED_BUILTIN, HIGH);
    delay(50);
    digitalWriteFast(LED_BUILTIN, LOW);
    delay(200);
  }
}

void myFloatCallback(float x)
{
  Serial.println(sin(x));
}

int someInt = 17;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);

  button1.attachClick([] { myIntCallback(3); });       // Attach with int literal
  button2.attachClick([] { myIntCallback(someInt); }); // Attach with global int
  button3.attachClick([] { myFloatCallback(2.45f); }); // Attach float callback
}

void loop()
{
  button1.tick();
  button2.tick();
  button3.tick();

  delay(10);
}
 
Awesome!! Lamdas are something I have heard people talk about but hadn't yet investigated. I will pull this apart tonight. I love this forum!
 
Trying to get the Lamda functions working but need to pass in the int value as below. I have been reading about the need to 'capture' variables passed in to the Lamda but I can't get it to work:

Code:
for (uint8_t i = 0; i < numIdButtons; i++) {
    idButtons[i].begin(i);
    idButtons[i].attachClick([=](int i) {buttonIntCallback(i); });
  }

Code:
/Users/xxx/Documents/Arduino/CANButton/CANButton.ino: In function 'void setup()':
CANButton:46:65: error: no matching function for call to 'IdButton::attachClick(setup()::<lambda(int)>)'
     idButtons[i].attachClick([=](int i) {buttonIntCallback(i); });
                                                                 ^
/Users/xxx/Documents/Arduino/CANButton/CANButton.ino:17:10: note: candidate: void IdButton::attachClick(ButtonCallback)
     void attachClick(ButtonCallback f) {
          ^~~~~~~~~~~
/Users/xxx/Documents/Arduino/CANButton/CANButton.ino:17:10: note:   no known conversion for argument 1 from 'setup()::<lambda(int)>' to 'ButtonCallback {aka void (*)()}'
exit status 1
no matching function for call to 'IdButton::attachClick(setup()::<lambda(int)>)'
 
I just want to add that using a bit of modern c++ you can easily work around that void* interface by using lambda expressions. The good thing is that this doesn't even need the parameterizedCallbackFunction but works with standard void(*callback)() type of callbacks. I.e. you can use this pattern for all libs providing only a void(*callback)() interface (e.g. IntervalTimer...)


Using your code example I tried to access a variable inside the for loop. If I try to access 'buttonId' it won't compile but it is happy to access the global variable 'someInt'

Code:
#include "OneButton.h"

void myIntCallback(int cnt) // normal function taking an int parameter cnt and flashing cnt times
{
  for (int i = 0; i < cnt; i++)
  {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(50);
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}

int someInt = 5;

OneButton buttons[2];

void setup()
{
  Serial.begin(9600);
  
  pinMode(LED_BUILTIN, OUTPUT);

  for (int i=0; i < 2; i++) {
    int buttonId = ((i+4));
    buttons[i].begin(buttonId,true, true);

     //buttons[i].attachClick([] { myIntCallback(someInt); });
    buttons[i].attachClick([]() { myIntCallback(buttonId); });
  }
}

void loop()
{
  for (int i=0; i < 2; i++) {
    buttons[i].tick();
  }
}


Code:
/Users/xxx/Documents/Arduino/sketch_apr27d/sketch_apr27d.ino: In lambda function:
sketch_apr27d:29:49: error: 'buttonId' is not captured
     buttons[i].attachClick([]() { myIntCallback(buttonId); });
                                                 ^~~~~~~~
/Users/xxx/Documents/Arduino/sketch_apr27d/sketch_apr27d.ino:29:29: note: the lambda has no capture-default
     buttons[i].attachClick([]() { myIntCallback(buttonId); });
                             ^
/Users/xxx/Documents/Arduino/sketch_apr27d/sketch_apr27d.ino:25:9: note: 'int buttonId' declared here
     int buttonId = ((i+4));
         ^~~~~~~~
exit status 1
'buttonId' is not captured
 
Last edited:
That won't work with the OneButton library as it is.

Reason:
You can only have a lambda as a replacement for a void(*)() function if the lambda doesn't capture variables. Otherwise the type of the lambda is not compatible to void(*)() which is what your library expects (and what the compiler complains about). If you can't or don't want to pass the buttonId as a constant or global variable this approach simply doesn't work.

Anyway, out of interest I'll have a look if a small tweak of the library will fix this.
 
So, the good thing is that the lib authors did use a typedef for the callback types. Thus, the lib can be rejuvenated by a very simple surgical procedure. Just replace the typedef for the callback function by the more modern std::function

Code:
// starting at line 23:
#ifndef OneButton_h
#define OneButton_h

#include "Arduino.h"
#include <functional>   <============= ADD

// ----- Callback function types -----

extern "C" {
//typedef void (*callbackFunction)(void);
using callbackFunction = std::function<void(void)>;  // <======= replace dumb function pointer by std::function
typedef void (*parameterizedCallbackFunction)(void*);
}
/....

After this changes you can throw more or less anything callable at the lib (function pointers, functors, member functions etc). In particular your lambdas with captured variables will work without problems. (BTW, it seems like you use a slightly different OneButton, because mine doesn't have a begin(); I therefore did the initialization of the buttons directly in the array definition.)

Code:
#include "OneButton.h"

OneButton idButtons[]{// tree buttons on pins 0,1 and 2
    {0, true, true},
    {1, true, true},
    {2, true, true}
};

constexpr unsigned numIdButtons = sizeof(idButtons) / sizeof(idButtons[0]);

void buttonIntCallback(int cnt) // normal function taking an int parameter cnt and flashing cnt times
{
  for (int i = 0; i < cnt; i++)
  {
    digitalWriteFast(LED_BUILTIN, HIGH);
    delay(50);
    digitalWriteFast(LED_BUILTIN, LOW);
    delay(200);
  }
}

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);

  for (unsigned i = 0; i < numIdButtons; i++)
  {
    idButtons[i].attachClick([i] { buttonIntCallback(i + 1); });
  }
}

void loop()
{
  for (unsigned i = 0; i < numIdButtons; i++)
  {
    idButtons[i].tick();
  }

  delay(10);
}
 
That works and compiles for the Teensy. Thanks. I also wanted to use this for some of the smaller boards that run ATMega328's but functional is not available:

Code:
fatal error: functional: No such file or directory
 
No, that doesn't make sense on a 8bit machine. I suggest to stick with the function pointers as shown in #12
 
Something like this will work on any Arduino board and you won't have to resort to templates or lambdas:
Code:
#include "Arduino.h"
#include "OneButton.h"

class NewButton: public OneButton {
public:
	NewButton(uint8_t p) : OneButton(p, true), pin(p) {
		OneButton::attachClick(callBack, this);
	}

private:
	uint8_t pin;

	void doSomething() {
		Serial.println(pin);
	}

	static void callBack(void *p) {
		((NewButton *) p)->doSomething();
	}

};

NewButton buttons[] = {8, 9, 10};
uint8_t numButtons = sizeof(buttons) / sizeof(buttons[0]);

void setup() {

}

void loop() {
	for (uint8_t i=0; i<numButtons; i++) {
		buttons[i].tick();
	}
}
 
Status
Not open for further replies.
Back
Top