函数指针地址未传递

4

我有一些针对AVR的C代码。该代码是使用avr-gcc编译的,基本上是具有正确后端的GNU编译器。

我正在尝试在我的事件/中断驱动库中创建回调机制,但似乎我无法保留函数指针的值。

首先,我有一个静态库。它有一个头文件(twi_master_driver.h),看起来像这样:

#ifndef TWI_MASTER_DRIVER_H_
#define TWI_MASTER_DRIVER_H_

#define TWI_INPUT_QUEUE_SIZE 256

// define callback function pointer signature
typedef void (*twi_slave_callback_t)(uint8_t*, uint16_t);

typedef struct {
    uint8_t buffer[TWI_INPUT_QUEUE_SIZE];
    volatile uint16_t length; // currently used bytes in the buffer
    twi_slave_callback_t slave_callback;
} twi_global_slave_t;

typedef struct {
    uint8_t slave_address;
    volatile twi_global_slave_t slave;
} twi_global_t;

void twi_init(uint8_t slave_address, twi_global_t *twi, twi_slave_callback_t slave_callback);

#endif

现在是 C 文件 (twi_driver.c):

#include <stdint.h>
#include "twi_master_driver.h"

void twi_init(uint8_t slave_address, twi_global_t *twi, twi_slave_callback_t slave_callback)
{
    twi->slave.length = 0;
    twi->slave.slave_callback = slave_callback;

    twi->slave_address = slave_address;

    // temporary workaround <- why does this work??
    twi->slave.slave_callback = twi->slave.slave_callback;
}

void twi_slave_interrupt_handler(twi_global_t *twi)
{
    (twi->slave.slave_callback)(twi->slave.buffer, twi->slave.length);

    // some other stuff (nothing touches twi->slave.slave_callback)
}

接着,我将这两个文件构建成一个静态库(.a),并构建我的主程序 (main.c) #include #include #include #include #include "twi_master_driver.h"

//  ...define microcontroller safe way for mystdout ...

twi_global_t bus_a;

ISR(TWIC_TWIS_vect, ISR_NOBLOCK)
{
    twi_slave_interrupt_handler(&bus_a);
}

void my_callback(uint8_t *buf, uint16_t len)
{
    uint8_t i;

    fprintf(&mystdout, "C: ");
    for(i = 0; i < length; i++)
    {
        fprintf(&mystdout, "%d,", buf[i]);
    }
    fprintf(&mystdout, "\n"); 
}

int main(int argc, char **argv)
{
    twi_init(2, &bus_a, &my_callback);

    // ...PMIC setup...

    // enable interrupts.
    sei();

    // (code that causes interrupt to fire)

    // spin while the rest of the application runs...
    while(1){
        _delay_ms(1000);
    }
    return 0;
}

我仔细触发导致中断触发并调用适当处理程序的事件。通过一些fprintf,我能够确定在twi_init函数中分配给twi->slave.slave_callback的位置与twi_slave_interrupt_handler函数中的位置不同。
尽管数字没有意义,但在twi_init中的值为0x13b,在twi_slave_interrupt_handler中打印时的值为0x100。
通过在twi_driver.c中添加注释的解决方法行:
twi->slave.slave_callback = twi->slave.slave_callback;

问题得到了解决,但这显然是一种神奇而不受欢迎的解决方法。我做错了什么?
据我所知,我已经标记了适当的变量为volatile,并尝试标记其他部分为volatile并删除vol - atile标记。当我注意到在twi_init赋值后删除fprintf语句会导致稍后读取该值的方式不同时,我想出了解决方法。
问题似乎出在我如何传递函数指针上,特别是访问指针值的程序部分(函数本身?)技术上处于不同的线程中。
有什么想法吗?
编辑:
  • 修正了代码中的拼写错误。
  • 文件实际链接:http://straymark.com/code/ [test.c | twi_driver.c | twi_driver.h]
  • 编译器选项:-Wall -Os -fpack-struct -fshort-enums -funsigned-char -funsigned-bitfields -mmcu=atxmega128a1 -DF_CPU=2000000UL
  • 我尝试了直接包含相同的代码(而不是通过库),我遇到了相同的问题。
编辑(第二轮):
  • 我删除了所有优化,没有我的“解决方法”,代码按预期工作。添加回-Os会导致错误。为什么-Os会破坏我的代码?

我要出去几个小时,稍后再检查,如果没有解决,我会再试一次:简要想法;如果不使用库,它是否有效?(即将所有内容放在一个文件中)。你真的可以从中断中fprintf()吗(听起来很危险)?重新发布显示所有内容;你的细节还不够完美,例如my_callback()中的length vs len,在哪里启用中断?是twi_driver.h还是twi_master_driver.h?抱歉目前帮不上太多忙,我会稍后再试。 - Bill Forster
avr-gcc生成的汇编代码在有和没有解决方案行的情况下有何不同?也就是说,您能否发布两个版本的“avr-gcc -S twi_driver.c”的结果?您的目标AVR是哪个? - mrkj
一些澄清:8位AVR,目标= ATxmega128A1,fprintf可以从中断中工作。尝试删除函数名称中的“&”没有任何问题。我将在几分钟内在网上发布完整文件。 - Mark Elliot
我应该补充一下,让这段代码在你自己的stk600/atxmega128a1上运行需要进行一些布线。对于那些真正想要运行它的人,我可以提供一个布线图。 - Mark Elliot
抱歉,我再仔细看了一遍,但是我找不到问题所在。我能提供的只是一些通用建议。尝试将问题最小化,放在一个文件中,并尽可能减少依赖关系。目前有很多可见噪声,甚至还有一些我看不到的,比如TWI_t的定义。目前你只在一个地方打印回调地址 - 那里是否正确?祝你好运,希望我能提供更多帮助。 - Bill Forster
显示剩余3条评论
3个回答

2

我有一个直觉,如果你交换这两行代码会发生什么:

twi->slave.slave_callback = slave_callback;
twi->slave.length = 0;

删除-fpack-struct gcc标志是否解决了问题?我想知道是否存在一个bug,其中写入length字段会覆盖部分回调值。
在我的看来,使用-Os优化时(您可以尝试启用-Os的各个单独的优化组合来确定是哪个导致此问题),当uint16_t长度字段未对齐到2字节边界时,编译器没有发出正确的代码来操作它。 当您将twi_global_slave_t包含在打包的twi_global_t中时,这种情况就会发生,因为twi_global_t的初始uint8_t成员使twi_global_slave_t结构体被放置在奇地址上。
如果您将twi_global_t的初始字段更改为uint16_t,它可能会修复它(或者您可以关闭结构体打包)。 尝试最新的gcc构建版本并查看它是否仍然存在 - 如果存在,则应该能够创建一个最小的测试用例来显示问题,以便您可以向gcc项目提交错误报告。

看起来这是正确的方向,我交换了顺序,问题得到了解决。为什么长度字段会覆盖回调字段? - Mark Elliot
进一步复杂化的是,如果我重新排列结构元素,问题似乎也会消失。 - Mark Elliot

1
这真的听起来像是堆栈/内存损坏问题。如果您在elf文件上运行avr-size,会得到什么?确保(数据+bss)<部件上的RAM。这些类型的问题非常难以追踪。删除/移动不相关的代码改变行为的事实是一个很大的警告信号。

0

在函数main()中将"&my_callback"替换为"my_callback"。

由于不同的线程访问回调地址,尝试使用互斥锁或读写锁来保护它。

如果回调函数指针不被信号处理程序访问,则“volatile”限定符是不必要的。


我已经尝试过了,没有任何区别。在启用中断之前,该值已设置(且未更改),因此不需要访问互斥读写锁(访问控制是按照约定进行的)。 - Mark Elliot
1
由于my_callback是一个函数,因此&my_callbackmy_callback之间没有区别。而且,由于“不同的线程”实际上只是在中断上下文中执行的代码,所以锁定将会导致死锁。在twi_init函数运行时禁用中断是必要的,这可以通过确保在该点之后不启用中断来实现。 - caf
@caf,这就是我所拥有的。@Steve Emmerson:回调函数指针由信号处理程序访问,这就是关键所在,它在初始化后不会被该处理程序或任何其他处理程序修改 - Mark Elliot
保留"volatile"关键字。我仍然怀疑在序列点方面存在不当行为。尝试降低优化级别。 - Steve Emmerson
我也会尝试将“-Os”优化选项更改为“-O0”,以查看问题是否是由优化引起的。 - Steve Emmerson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接