2017-02-13 48 views
0

我有一个函数会遍历函数指针数组,并按顺序调用这些函数中的每个函数。在更改数组内容时通过位于数组中的指针调用函数

如果在此过程中更改函数指针数组的内容会发生什么?函数调用是否可以被认为是足够原子的,以确保没有任何意外事件发生,或者必须注意不要改变函数指针,例如在堆栈上进行推送?

下面的一些示例(伪)代码。 init()在启动时运行一次,并且callFunctions()定期运行,例如每秒一次。然后changeFunctions()出现并更改functionPtrArray []的内容。这可能发生在任何时间点,因为代码是在不同的进程中运行的,就像环境一样的操作系统。

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; 
} 
+0

关键问题是,一旦执行f [0],f [1]和f [2]是否与f [0]来自同一集合,是否重要。否则,你的代码是好的(假设检索和存储函数指针是原子的,基本上意味着它们是本地CPU和总线字的大小,所以读/写周期在一个周期中获取/存储整个本地机器字)。 –

+0

@PaulOgilvie什么让你认为任何给定的通用CPU可以在一个周期内读取一个字(x字节)?即使这种假设是真的 - 事实并非如此 - 大量架构已经将地址扩展到机器字大小以外,旨在用于存储代码和数据。 – Lundin

回答

2

编辑:我太长时间和伦丁捷足先登:https://stackoverflow.com/a/42201493/5592711

重要的一点:调用C中的函数归结为跳转,所以当程序执行该函数时,更改用于进行函数调用的假设指针不会改变程序流程的任何内容,因为指令指针已经在函数中。

例如,在此片段中some_function()将会正常执行并且不会受到ptr修改的影响。

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

但是,你在这里提出了一个重要问题,因为内存操作不是原子,某个线程可以修改functionPtrArray[0]元素,而另一个线程正在读它,这将导致它跳转到一些垃圾地址,会导致你的程序失败,就像这个假设的例子。

为确保线程之间的同步,您可以使用mutexes,例如使用pthread库(您可以自己Google,您将找到大量信息)。

在您的例子,使用这种同步可能看起来像:

// 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); 
} 

这种方式,修改您的函数指针阵列和执行的功能是相互排他的任务。 你应该知道,这个例子有几个限制:

  1. 全功能的执行过程中,互斥被锁定(即是不需要的,你可以将其锁定读函数指针,解锁,然后执行它们)
  2. 函数必须不是自己调用changeFunctions,否则你最终会陷入僵局。
+0

“修改......而另一个线程正在读取它”:我认为从汇编中的原子动作获取数组中的函数地址,假设函数地址是本地机器字大小。 –

+0

是和不是,这是平台相关:http://stackoverflow.com/questions/1350994/is-it-safe-to-read-an-integer-variable-thats-being-concurrently-modified-withou –

+0

Monti,感谢指针。我曾经读过:“你有能力从错误中学习,今天你会学到很多东西”(“你”的意思是“我”)。 –

2

callFunctions()被周期性地运行,说一旦每个第二。然后changeFunctions()出现并更改functionPtrArray []的内容。这可能发生在任何时间点,因为代码是在不同的进程中运行的,就像环境一样的操作系统。

从你的描述,很明显,functionPtrArray阵列被修改,并且在其为data race一个unsychronized方式4BY多于一个的线程/进程(ES)进行访问。 所以,你需要提供同步或其他方式。

2

对于多线程场景中的任何变量,情况都是如此。不,它们不能被视为原子,除非您使用C11中的_Atomic限定符。

有许多情况下,函数指针的读取不会是原子的。具有16位地址的8位CPU就是一个例子。其中一些体系结构有一条指令来确保16位索引寄存器的安全,不可中断处理,而另一些则没有。另一个例子是支持超出默认地址总线宽度(银行业务,“远指针”)的扩展内存的任何架构。

如代码所示,函数指针数组一定不能被另一个线程/进程/ ISR /回调改变,否则就会出现竞态条件错误。你必须通过信号/互斥/临界区保护对阵列的访问。

由于函数调用可能会占用一些执行时间,所以您不希望在执行过程中阻止所有其他线程。这可能是最好的向下函数指针本地复制,因为在这个伪代码:

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 
    } 
} 
+0

由于调用没有参数,因此调用将是原子的(符号为'mov ax,&f',紧跟'call ax')。因此我不认为这个解决方案增加了任何东西(除非整个数组会被复制)。 –

+0

@PaulOgilvie没有说明具体的系统。为了相信所有系统都能保证原子访问,可移植性将非常幼稚。首先,考虑具有16位地址的8位MCU的常见情况 - 按照定义,它们不能按原子访问函数指针。但是,当然,假设世界上的每台电脑都是个人电脑并投票支持我,因为_你不知道任何其他电脑...... – Lundin

+0

Lundin。我可能确实有点天真。编辑解决方案之前无法解除投票。道歉。 –