0

I have a function that goes through an array of function pointers and calls each of these functions in order.

What happens if the contents of the array of functions pointers is changed during this process? Can the function calls be considered atomic enough to be sure that nothing unexpected happens, or must care be taken not to change the function pointers while for example the push on the stack is taking place?

Some example (pseudoish) code below. init() is run once at startup, and callFunctions() is run periodically, say once each second. Then changeFunctions() comes along and changes the content of functionPtrArray[]. This may happen at any point of time, since the code is run in different processes a OS like environment.

void (*functionPtrArray[3]) ( void );

void init( void )
{
   functionPtrArray[0] = function1;
   functionPtrArray[1] = function2;
   functionPtrArray[2] = function3;
}

void callFunctions( void )
{
   for ( i = 0; i < 3; i++ )
   {
      *functionPtrArray[i]();
   }
}

void changeFunctions( void )
{
   functionPtrArray[0] = newFunction1;
   functionPtrArray[1] = newFunction2;
   functionPtrArray[2] = newFunction3;
}
Daniel R
  • 103
  • 4
  • The key question is whether it is important that, once f[0] is executed, that f[1] and f[2] are from the same set as f[0]. Otherwise your code is fine (assuming that retrieving and storing a function pointer are atomic, basically meaning they are native CPU and bus word size so read/write cycles fetch/store whole native machine words in one cycle). – Paul Ogilvie Feb 13 '17 at 10:38
  • @PaulOgilvie What makes you think any given, generic CPU can read a word (x bytes) in one cycle? Even if that assumption was true - which it isn't - plenty of architectures have extended addresses beyond the machine word size, intended to be used for storing both code and data. – Lundin Feb 13 '17 at 10:47

3 Answers3

2

callFunctions() is run periodically, say once each second. Then changeFunctions() comes along and changes the content of functionPtrArray[]. This may happen at any point of time, since the code is run in different processes a OS like environment.

From your description, it's obvious that functionPtrArray array is modified and accessed 4by more than one thread/process(es) in an unsychronized way which is a data race. So, you'd need to provide synchronization one way or other.

P.P
  • 117,907
  • 20
  • 175
  • 238
2

It's the same case as for any variable in a multi-threaded scenario. No, they cannot be considered atomic unless you use the _Atomic qualifier from C11.

There are many situations where a read of a function pointer will not be atomic. 8 bit CPUs with 16 bit addresses is one example. Some of those architectures have an instruction to ensure safe, non-interruptable processing of a 16 bit index register, others do not. Another example is any architecture that supports extended memory beyond the default address bus width (banking, "far pointers").

As the code stands, the array of function pointers must not get changed by another thread/process/ISR/callback, or race condition bugs may happen. You have to protect access to the array with semaphore/mutex/critical section.

Since the function calls may take up some execution time, you don't want to block all other threads during that execution though. It is probably best to copy down the function pointers locally, as in this pseudo-code:

void callFunctions( void )
{
  for ( i = 0; i < 3; i++ )
  {
    void(*local)(void);

    grab_mutex();
      local = functionPtrArray[i];
    release_mutex();

    local(); // call is completely thread-safe
  }
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • As the call has no parameters, the call will be atomic (symbolicly `mov ax, &f` followed by `call ax`). Therefore I do not think this solution adds anything (unless the whole array would have been copied). – Paul Ogilvie Feb 13 '17 at 10:26
  • @PaulOgilvie No specific system was stated. To believe that atomic access is guaranteed on all systems, portably, would be very naive. To begin with, consider the very common case of 8 bit MCUs with 16 bit addresses - they _can't_ have atomic access of function pointers by definition. But sure, assume that every computer in the world is a PC and down vote me because _you_ don't know of any other computers... – Lundin Feb 13 '17 at 10:42
  • Lundin. I may be a bit naive indeed. Can't unvote until solution edited. Apologies. – Paul Ogilvie Feb 13 '17 at 10:46
2

edit: I was too long and pre-empted by Lundin : https://stackoverflow.com/a/42201493/5592711

An important point : calling a function in C boils down to a jump, so when the program is executing the function, changing a hypothetical pointer used to make the function call will not change anything to the program flow, since the instruction pointer is already within the function.

For example, in this snippet some_function() will execute fine and is not affected by the modification of ptr.

void(*ptr)(void) = &some_function;
(*ptr)();
// ... in another thread, while 'some_function()' is executing
ptr = &other_function;

However, you're raising an important point here, because memory operations are not atomic, some thread can modify the functionPtrArray[0] element while another thread was reading it, that will lead it to jump to some garbage address and will cause your program to fail, like in this hypothetical example.

To ensure synchronization between threads, you can use mutexes, for example using the pthread library (you can Google that yourself, you will find plenty of information).

In your example, using such synchronization could look like :

// You can use static mutex initialization
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void callFunctions( void )
{
   pthread_mutex_lock(&mutex);
   for ( i = 0; i < 3; i++ )
   {
      *functionPtrArray[i]();
   }
   pthread_mutex_unlock(&mutex);
}

void changeFunctions( void )
{
   pthread_mutex_lock(&mutex);
   functionPtrArray[0] = newFunction1;
   functionPtrArray[1] = newFunction2;
   functionPtrArray[2] = newFunction3;
   pthread_mutex_unlock(&mutex);
}

This way, modifying your function pointer array and executing the functions are mutually-exclusive tasks. You should be aware that this example has several limitations :

  1. The mutex is locked during the whole functions execution (that is not needed, you can just lock it to read the function pointers, unlock and then execute them)
  2. The functions must not call changeFunctions themselves, otherwise you will end up in a deadlock.
Community
  • 1
  • 1
A. Monti
  • 491
  • 3
  • 4
  • "modify...while another thread was reading it": I consider fetching the function address from the array an atomic action in assembler, assuming function addresses are native machine word size. – Paul Ogilvie Feb 13 '17 at 10:32
  • Yes and no, this is platform dependent : http://stackoverflow.com/questions/1350994/is-it-safe-to-read-an-integer-variable-thats-being-concurrently-modified-withou – A. Monti Feb 13 '17 at 10:38
  • Monti, thanks for the pointer. I once read: "_You have the capacity to learn from mistakes. You'll learn a lot today_" ("you" meaning "me"). – Paul Ogilvie Feb 13 '17 at 10:55