函数指针位置未传递
我有一些针对 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 "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;
}
我小心地触发导致中断触发的事件并调用适当的处理程序。使用一些 fprintfs,我可以看出在 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;
问题消失了,但这显然是一个神奇且不受欢迎的解决方案。我做错了什么?
据我所知,我已经标记了适当的变量易失性
,并且我尝试将其他部分标记为易失性并删除易失性标记。当我注意到在 twi_init
中赋值后删除 fprintf
语句导致稍后以不同方式读取值时,我想出了解决方法。
问题似乎在于我如何传递函数指针——特别是访问指针值的程序部分(函数本身?)从技术上讲是在不同的线程中。
有什么想法吗?
编辑:
解决了代码中的拼写错误。
实际文件的链接:http://straymark.com/code/ [test.c| twi_driver.c|twi_driver.h]
fwiw:编译器选项:
-Wall -Os -fpack-struct -fshort-enums -funsigned-char -funsigned-bitfields -mmcu =atxmega128a1 -DF_CPU=2000000UL
我尝试过直接包含相同的代码(而不是通过库),并且遇到了相同的问题。
编辑(第 2 轮):
- 我删除了所有优化,如果没有我的“解决方法”,代码将按预期工作。添加回 -Os 会导致错误。为什么 -Os 会破坏我的代码?
I've got some C code I'm targeting for an AVR. The code is being compiled with avr-gcc, basically the gnu compiler with the right backend.
What I'm trying to do is create a callback mechanism in one of my event/interrupt driven libraries, but I seem to be having some trouble keeping the value of the function pointer.
To start, I have a static library. It has a header file (twi_master_driver.h
) that looks like this:
#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
Now the C file (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)
}
Then I build those two files into a static library (.a) and construct my main program (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;
}
I carefully trigger the events that cause the interrupt to fire and call the appropriate handler. Using some fprintfs I'm able to tell that the location assigned to twi->slave.slave_callback
in the twi_init
function is different than the one in the twi_slave_interrupt_handler
function.
Though the numbers are meaningless, in twi_init
the value is 0x13b, and in twi_slave_interrupt_handler
when printed the value is 0x100.
By adding the commented workaround line in twi_driver.c
:
twi->slave.slave_callback = twi->slave.slave_callback;
The problem goes away, but this is clearly a magic and undesirable solution. What am I doing wrong?
As far as I can tell, I've marked appropriate variables volatile
, and I've tried marking other portions volatile and removing the volatile markings. I came up with the workaround when I noticed removing fprintf
statements after the assignment in twi_init
caused the value to be read differently later on.
The problem seems to be with how I'm passing around the function pointer -- and notably the portion of the program that is accessing the value of the pointer (the function itself?) is technically in a different thread.
Any ideas?
Edits:
resolved typos in code.
links to actual files: http://straymark.com/code/ [test.c|twi_driver.c|twi_driver.h]
fwiw: compiler options:
-Wall -Os -fpack-struct -fshort-enums -funsigned-char -funsigned-bitfields -mmcu=atxmega128a1 -DF_CPU=2000000UL
I've tried the same code included directly (rather than via a library) and I've got the same issue.
Edits (round 2):
- I removed all the optimizations, without my "workaround" the code works as expected. Adding back -Os causes an error. Why is -Os corrupting my code?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
只是一种预感,但是如果您交换这两行会发生什么:
删除
-fpack-struct
gcc 标志是否可以解决问题?我想知道您是否没有偶然发现一个错误,即写入length
字段会覆盖部分回调值。在我看来,就像启用了
-Os
优化(您可以尝试组合使用-Os
启用的各个优化,以准确查看是哪一个优化引起的),编译器当 uint16_t 长度字段未在 2 字节边界上对齐时,不会发出正确的代码来操作该长度字段。当您在打包的twi_global_t
中包含twi_global_slave_t
时,会发生这种情况,因为twi_global_t
的初始uint8_t
成员会导致要放置在奇数地址的twi_global_slave_t
结构体。如果您将 twi_global_t 的初始字段设置为 uint16_t ,它可能会修复它(或者您可以关闭结构打包)。尝试最新的 gcc 版本,看看它是否仍然发生 - 如果仍然发生,您应该能够创建一个显示问题的最小测试用例,以便您可以向 gcc 项目提交错误报告。
Just a hunch, but what happens if you switch these two lines around:
Does removing the
-fpack-struct
gcc flag fix the problem? I wonder if you haven't stumbled upon a bug where writing thatlength
field is overwriting part of the callback value.It looks to me like with the
-Os
optimisations on (you could try combinations of the individual optimisations enabled by-Os
to see exactly which one is causing it), the compiler isn't emitting the right code to manipulate theuint16_t
length field when its not aligned on a 2-byte boundary. This happens when you include atwi_global_slave_t
inside atwi_global_t
that is packed, because the initialuint8_t
member oftwi_global_t
causes thetwi_global_slave_t
struct to be placed at an odd address.If you make that initial field of
twi_global_t
auint16_t
it will probably fix it (or you could turn off struct packing). Try the latest gcc build and see if it still happens - if it does, you should be able to create a minimal test case that shows the problem, so you can submit a bug report to the gcc project.这听起来确实像是堆栈/内存损坏问题。如果你在 elf 文件上运行 avr-size ,你会得到什么?确保 (data + bss) <您拥有的 RAM。这些类型的问题很难追踪。删除/移动不相关的代码会改变行为这一事实是一个很大的危险信号。
This really sounds like a stack/memory corruption issue. If you run avr-size on your elf file, what do you get? Make sure (data + bss) < the RAM you have on the part. These types of issues are very difficult to track down. The fact that removing/moving unrelated code changes the behavior is a big red flag.
在函数 main() 中将“&my_callback”替换为“my_callback”。
由于不同的线程访问回调地址,请尝试使用互斥锁或读写锁来保护它。
如果信号处理程序不访问回调函数指针,则不需要“易失性”限定符。
Replace "&my_callback" with "my_callback" in function main().
Because different threads access the callback address, try protecting it with a mutex or read-write lock.
If the callback function pointer isn't accessed by a signal handler, then the "volatile" qualifier is unnecessary.