[SOVED] function pointer array uses a lot of program memory

Hi,

I want to improve my multitasking management.
Of course the basic system of running the functions is round robin.

But it came now where I want to run a sequence of functions as a thread, and also running another thread that has other tasks in parallel.

The main idea of the thread manager is:
Each thread may has multiple tasks that should run in sequential mode, if not then just put the functions in loop area and they should run in round robin.

I was thinking of a way to store the addresses of the functions I want to run in sequential mode, because functions pointers didn't work very well because when calling back a function pointer, I didn't find a way to pass the required arguments so I decided to find another way.

Which is to use array of function pointer to store addresses of functions, then use these addresses to match the counter in the thread manager so this way I can run the functions in sequential mode.

But one thing now I noticed is that just storing only 7 function pointers raised the program memory usage from 1438 to 3034 !

As each function pointer is only 8 bytes long, so 7 x 8 = 54 bytes, but the difference is 3034 - 1438 = 1,596 bytes ! Why this jump in used memory for only 7 function pointers ?

This is the code:

  1. task_manager.h
typedef void (*f_ptr)(void);
  1. Arduino sketch file:
#include "task_manager.h"
#include "glcd_spi.h"
#include "sensors_modules.h"
#include "arrays.h"

#define LCD_THRD            0
#define SERIAL_THRD         1

#define LCD_TASKS            4
#define SERIAL_TASKS         3

THREAD_STATE pgm_thread[2];  // 2 threads: lcd & serial threads

f_ptr lcd_fptr[LCD_TASKS];
f_ptr serial_fptr[SERIAL_TASKS];

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

  lcd_fptr[0] = glcd_init;              // just uncommenting this line,
                                        // the program space jumps from 1438 to 2404
//  lcd_fptr[1] = glcd_graphics_mode;
//  lcd_fptr[2] = glcd_clr;
//  lcd_fptr[3] = glcd_img;
//
//  serial_fptr[0] = function1;
//  serial_fptr[1] = function2;
//  serial_fptr[2] = function3;
  
}

Now my first problem is the memory usage with defining one function pointer in arduino sketch ! What is the reason for that and how to solve it ?

If this problem is solved then I can proceed using them in source files to match address of the function called and unlock it in program run until it's finished, then open the lock for the next function and so on.

wolfrose:
But it came now where I want to run a sequence of functions as a thread, and also running another thread that has other tasks in parallel.

not sure what you're trying to say. a task is normally a block of code and variables that can have multiple execution threads that run independently of one another but share access to the variables. each thread would be defined by a sub-function.

some scheduler would determine when a task and its threads run

wolfrose:
I was thinking of a way to store the addresses of the functions I want to run in sequential mode,

normally there would be a queue (array) of task/threads that are ready to run and executed sequentially. each tread may be defined by both a function pointer and an (void*) argument. an idle task would always remain on queue.

wolfrose:
because functions pointers didn't work very well because when calling back a function pointer, I didn't find a way to pass the required arguments so I decided to find another way.

typedef void (*f_ptr)(void* arg);

a NULL argument can be passed and the function would know how to interpret (recast) the void * arg to what it expects

wolfrose:
But one thing now I noticed is that just storing only 7 function pointers raised the program memory usage from 1438 to 3034 !

a function ptr requires no more memory than any other pointer. could the compiler be optimizing out unused data?

consider

typedef void (*Func_t) (void *arg);

struct Thread_t  {
    Func_t  func;
    void   *arg;
};

#define MAX_THREAD  5
Thread_t threads [MAX_THREAD] = {};
int      thrIdxHead           = 0;
int      thrIdxTail           = 0;

// -----------------------------------------------------------------------------
void
qFunc (
    Func_t  func,
    void   *arg )
{
    int idx = thrIdxHead;

    if (0 != threads [idx].func)
        return;

    threads [idx].func = func;
    threads [idx].arg  = arg;

    if (MAX_THREAD == ++idx)
        idx = 0;
    
    thrIdxHead = idx;
}

// -------------------------------------
void
qExec (void)
{
    int idx = thrIdxTail;

    if (0 == threads [idx].func)
        return;

    Func_t func = threads [idx].func;
    void  *arg  = threads [idx].arg;

    if (MAX_THREAD == ++idx)
        idx = 0;
    
    thrIdxTail = idx;

    func (arg);
}

// -----------------------------------------------------------------------------
enum  {OFF = HIGH, ON = LOW };

byte ledPins [] = { 10, 11, 12, 13 };
#define N_LEDS  sizeof(ledPins)

void
ledBlink (
    void *arg )
{
    int cnt = (int) arg;
    while (cnt--)  {
        for (unsigned n = 0; n < N_LEDS; n++)  {
            digitalWrite (ledPins [n], ! digitalRead (ledPins [n]));
            delay (100);
        }
        delay ( 500);
    }

    delay (500);
    qFunc (ledBlink, (void*)1);
}

// -----------------------------------------------------------------------------
void
nonsense (
    void *arg)
{
    int  iArg = (int) arg;

    switch (iArg)  {
    case 0:
        Serial.println ("now is the time for all good men");
        break;
    case 1:
        Serial.println ("the quick brown fox");
        break;
    case 2:
        Serial.println ("it's easy to tell the depth of a well");
        break;
    }

    iArg = (iArg+1) % 3;

    qFunc (nonsense, (void*) iArg);
}

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

    for (unsigned n = 0; n < N_LEDS; n++)  {
        digitalWrite (ledPins [n], OFF);
        pinMode (ledPins [n], OUTPUT);
    }

    qFunc (ledBlink, (void*)3);
    qFunc (nonsense, 0);
}

// -----------------------------------------------------------------------------
void loop()
{
    qExec ();
}

Thank you so much for this answer.

I'm considering your answer for my work.

As a small example in Arduino sketch, I tried the following:

void function1(void){Serial.println("function1");}

void (*f_ptr1)(void);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  f_ptr1 = function1;      // this line uses 46 bytes
}

I compiled this code and just storing the function address in the function pointer, used 46 bytes ! Why is that ?

wolfrose:
I compiled this code and just storing the function address in the function pointer, used 46 bytes ! Why is that ?

is there any need for function1() if it is never called? as well as the line calling it?

Update:

OK, I did a new sketch and actually found that you're right:

a function ptr requires no more memory than any other pointer. could the compiler be optimizing out unused data?

The problem isn't in the function pointer, the problem actually the function I assigned to the function pointer uses a lot of program memory and I'm surprised because it's not a long one.

The function is "glcd_init" when I assign it to the function pointer it uses 900 bytes of program memory = ! Why is that ?

Actually "glcd_init" has a callback function "glcd_cmdt", and I commented the callback and the compile size got to 1936.

void glcd_init(void){
	if(!function_lock){
		switch(lcd_init){
			case START:
				pinMode(CS_PIN, OUTPUT);	pinMode(CLK_PIN, OUTPUT);	pinMode(MOSI_PIN, OUTPUT);
				digitalWrite(CS_PIN, LOW);	SPI.begin();	lcd_init = CS_HI;	delay_pr = 100000;	
			break;

			case CS_HI:
				digitalWrite(CS_PIN, HIGH);	lcd_init = INIT_CMD; delay_pr = 40000;
			break;
			
			case INIT_CMD:
				data_buf[0] = FUNCTION_SET_BASIC;	data_buf[1] = FUNCTION_SET_BASIC;
				data_buf[2] = DISPLAY_CLEAR;		data_buf[3] = ENTERY_MODE;
				data_buf[4] = DISPLAY_CONTROL;
				lcd_cmdt = CMD;	buf_cnts = 5; buf_cntr = 0;
			break;
		}
		if((lcd_init == START) || (lcd_init == CS_HI)){delay_st = micros();	delay_lock = 1;}
		function_lock = 1;
	}
	glcd_cmdt();
}

void glcd_cmdt(void){
	if(function_lock && !delay_lock){
		if((lcd_cmdt) && (buf_cntr >= buf_cnts)){
			switch(lcd_mode){
				case PIXEL_WRITE:
					if(lcd_cmdt == CMD)			{lcd_cmdt = DATA; buf_cnts = 4;}
					else if(lcd_cmdt == DATA)	{function_lock = 0;}
				break;

				case GRAPHICS_SET:	case CURSOR_MOVE:	case INIT:
					function_lock = 0;	lcd_st_flag = FINISHED;
				break;										
			}		
		}
		else{
			switch(lcd_cmdt){
				case CMD:	glcd_cmd(data_buf[buf_cntr]);	break;
				case DATA:	glcd_data(data_buf[buf_cntr]);	break;	
				case IDLE:	function_lock = 0;				break;
			}
			////////////////////////////////////////////////////////
			// locking / time setting for CMD/DATA tasks
			if(lcd_cmdt){
				if((data_buf[buf_cntr] == DISPLAY_CLEAR) && (lcd_mode == INIT)){delay_pr = 1600;}
				else{delay_pr = 72;}
				delay_lock = 1; delay_st = micros(); buf_cntr++;
			}
		}
	}
	if(function_lock && delay_lock){glcd_delay_routine();}
}

So the scenarios I did:

  1. Just assigning the "glcd_init" to the function pointer = 2404.
  2. Calling "glcd_init" with "glcd_cmdt" callback = 2384.
  3. Calling "glcd_init" with commenting "glcd_cmdt" callback = 1936.

Are these numbers OK, or my programming isn't using the memory properly ?

gcjr:
is there any need for function1() if it is never called? as well as the line calling it?

I'm just doing this function for demonstration, I want to know what's going on with memory usage.

Also I'm wondering about the memory usgage when calling Serial.begin(); which raises the memory usage from 444 to 1438. Is this ok or I should find a better Serial library that uses less memory ?

wolfrose:
So the scenarios I did:

  1. Just assigning the "glcd_init" to the function pointer = 2404.
  2. Calling "glcd_init" with "glcd_cmdt" callback = 2384.
  3. Calling "glcd_init" with commenting "glcd_cmdt" callback = 1936.

Are these numbers OK, or my programming isn't using the memory properly ?

i believe so because as soon as you remove presumably the only call to glcd_cmdt(), you also remove the need for that function, eliminating quite a few lines of code

gcjr:
i believe so because as soon as you remove presumably the only call to glcd_cmdt(), you also remove the need for that function, eliminating quite a few lines of code

OK, let me investigate scenario 1.
So what does assigning the address of a function to a function pointer do ? Shouldn't that only use 8 bytes of memory ?
So this line:

lcd_fptr[0] = glcd_init;

uses 966 bytes, why ?
Does assigning the address of a function to a function pointer actually depends on the size of the function ? So it not only the compiler put the assigning the address of a function to a function pointer and uses 8 bytes.
But I think the compiler allocate the size of that function in program memory, and I think it's the only reason.
Because this line:

glcd_init();

2384, the difference is 20 bytes, I think with using a function pointer there's 20 bytes more and I can understand that it because the compiler allocate memory space for the function and the function pointer.
So I think that means using function pointers isn't ideal ? Is it an essential method ?
Also according to your provided example code for me to develop a thread manager, where you also use function pointers too, so you know that function pointers uses more memory, right ? but we have to use function pointers for developing callback points or manage function calls.

I tested your way of the function pointer:

typedef void (*f_ptr)(void* arg);

Thank you so much it worked :slight_smile:

I was working on developing the function pointers part previously and because I didn't know about putting pointer to void in arguments area. So it didn't work and I canceled the idea of using function pointers, but now it's working that's a lot more better and now calling functions from task_manager is now possible and a lot easier than before :slight_smile:

wolfrose:
so you know that function pointers uses more memory, right ?

Function pointers use no more memory than any other type of pointer .... 2 Bytes / each on an AVR and 4 Bytes / each on an ARM or ESP.

wolfrose:
But I think the compiler allocate the size of that function in program memory, and I think it's the only reason.

yes, the compiler does not include the function in the binary in this case because there is no other reference to it.

would you want the compiler to include code that is never used?

wolfrose:
So I think that means using function pointers isn't ideal ? Is it an essential method ?

i don't understand why you say "using function pointers isn't ideal".

there are many uses for function ptrs, especially in a scheduler where the function to call cannot be hardcoded, or state machines, or callback functions as you said

like any code, it can be poorly written, just as any feature of a programming language can be misused.

wolfrose:
Also according to your provided example code for me to develop a thread manager, where you also use function pointers too, so you know that function pointers uses more memory, right ?

a function ptr requires the same amount of memory as any other ptr.

maybe you should set a function ptr to the same function in different parts of the code. then remove one and then remove the 2nd

gfvalvo:
Function pointers use no more memory than any other type of pointer .... 2 Bytes / each on an AVR and 4 Bytes / each on an ARM or ESP.

Yep, thanks for pointing me to this detail.
I tested a simple code and yes the program size went from 444 to 446 bytes so it's 2 bytes for the AVR chips !
That's really nice to know.
So the size of a function pointer isn't dependent on chip arch line ? Like AVRs are 8-bit but that isn't the case for pointers ? Why ?

wolfrose:
Like AVRs are 8-bit but that isn't the case for pointers ? Why ?

Because you'd be severely limited in what you could do if your processor only had a 8-bit (256 byte) address space.

wolfrose:
So the size of a function pointer isn't dependent on chip arch line ? Like AVRs are 8-bit but that isn't the case for pointers ? Why ?

of course the size of a ptr depends on the arch. it must be able to specify any address the arch supports.

gcjr:
yes, the compiler does not include the function in the binary in this case because there is no other reference to it.

would you want the compiler to include code that is never used?

you mean never called ? yes defining a function doesn't take space but calling it is the action that tells the compiler to put this function with its size in program memory.

i don't understand why you say "using function pointers isn't ideal".

Sorry that was a mistaken quick guessing of me, it's actually the opposite; with testing, calling a function from a function pointer takes less size than calling it directly, that's I got now compiling this code:

    typedef void (*prgm_fptr)(void* arg);
    glcd_init_fptr = glcd_init;

void setup() {
  Serial.begin(9600);
    glcd_init_fptr(0);       // this line use less program memory = 4098 bytes
    glcd_init();             // this line uses more program memory = 4124 bytes
}

maybe you should set a function ptr to the same function in different parts of the code. then remove one and then remove the 2nd

Yeah I should start considering using function pointers and measure the difference. Thanks !

gcjr:
of course the size of a ptr depends on the arch. it must be able to specify any address the arch supports.

Yep got the answer here:

The data path to program memory is indeed 16-bit, while it is 8-bit only to data memory.

Now I got it !

gfvalvo:
Because you'd be severely limited in what you could do if your processor only had a 8-bit (256 byte) address space.

Oh I got it now, that's why Atmega328 flash memory is 32kb, and can be up to 65k max.
If we want more memory then we have to extend the size of program memory address bus.

wolfrose:

void glcd_init(void){

if(!function_lock){
switch(lcd_init){
case START:
pinMode(CS_PIN, OUTPUT); pinMode(CLK_PIN, OUTPUT); pinMode(MOSI_PIN, OUTPUT);
digitalWrite(CS_PIN, LOW); SPI.begin(); lcd_init = CS_HI; delay_pr = 100000;
break;

case CS_HI:
digitalWrite(CS_PIN, HIGH); lcd_init = INIT_CMD; delay_pr = 40000;
break;

case INIT_CMD:
data_buf[0] = FUNCTION_SET_BASIC; data_buf[1] = FUNCTION_SET_BASIC;
data_buf[2] = DISPLAY_CLEAR; data_buf[3] = ENTERY_MODE;
data_buf[4] = DISPLAY_CONTROL;
lcd_cmdt = CMD; buf_cnts = 5; buf_cntr = 0;
break;
}
if((lcd_init == START) || (lcd_init == CS_HI)){delay_st = micros(); delay_lock = 1;}
function_lock = 1;
}
glcd_cmdt();
}

void glcd_cmdt(void){
if(function_lock && !delay_lock){
if((lcd_cmdt) && (buf_cntr >= buf_cnts)){
switch(lcd_mode){
case PIXEL_WRITE:
if(lcd_cmdt == CMD) {lcd_cmdt = DATA; buf_cnts = 4;}
else if(lcd_cmdt == DATA) {function_lock = 0;}
break;

case GRAPHICS_SET: case CURSOR_MOVE: case INIT:
function_lock = 0; lcd_st_flag = FINISHED;
break;
}
}
else{
switch(lcd_cmdt){
case CMD: glcd_cmd(data_buf[buf_cntr]); break;
case DATA: glcd_data(data_buf[buf_cntr]); break;
case IDLE: function_lock = 0; break;
}
////////////////////////////////////////////////////////
// locking / time setting for CMD/DATA tasks
if(lcd_cmdt){
if((data_buf[buf_cntr] == DISPLAY_CLEAR) && (lcd_mode == INIT)){delay_pr = 1600;}
else{delay_pr = 72;}
delay_lock = 1; delay_st = micros(); buf_cntr++;
}
}
}
if(function_lock && delay_lock){glcd_delay_routine();}
}

For the love of all things holy and just, ONE CODE STATEMENT PER LINE! This code is hideous to read.

I thought about that but I wanted to shorten the length of the codes, I wanted as scrolling the source file over and over to have the code to be more compacted and shorter.

But I should really consider your point to have more typical code to read than a shorter one.

Thanks,

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