gcc中是否有一个魔术变量,其中包含指向当前函数的指针?
我想有一种表包含每个函数指针的一组信息。
我知道有一个__func__变量,它包含当前函数的名称作为字符串,但不作为函数指针。
这不是为了调用函数而只是用作索引。
修改 基本上我想做的是能够在执行当前函数之前运行嵌套函数(并且还捕获返回以执行某些操作。) 基本上,这就像__cyg_profile_func_enter和__cyg_profile_func_exit(检测函数)......但问题是这些检测功能是全局的而不是功能专用的。
修改
在Linux内核中,您可以使用unsigned long kallsyms_lookup_name(const char *name)
中的include/linux/kallsyms.h
...请注意,必须激活CONFIG_KALLSYMS
选项。
答案 0 :(得分:6)
void f() {
void (*fpointer)() = &f;
}
答案 1 :(得分:3)
#define FUNC_ADDR (dlsym(dlopen(NULL, RTLD_NOW), __func__))
编译您的程序,如
gcc -rdynamic -o foo foo.c -ldl
答案 2 :(得分:2)
我认为您可以使用字符串(函数名称)作为键来构建表,然后通过与__func__
内置变量进行比较来查找。
要强制使用有效的函数名,可以使用获取函数指针的宏,对其执行一些虚拟操作(例如,将其分配给兼容的函数类型临时变量)以检查它确实是有效的函数标识符,然后在用作键之前将函数名称字符串化(带#)。
<强>更新强>
我的意思是:
typedef struct {
char[MAX_FUNC_NAME_LENGTH] func_name;
//rest of the info here
} func_info;
func_info table[N_FUNCS];
#define CHECK_AND_GET_FUNC_NAME(f) ({void (*tmp)(int); tmp = f; #f})
void fill_it()
{
int i = -1;
strcpy(table[++i].func_name, CHECK_AND_GET_FUNC_NAME(foo));
strcpy(table[++i].func_name, CHECK_AND_GET_FUNC_NAME(bar));
//fill the rest
}
void lookup(char *name) {
int i = -1;
while(strcmp(name, table[++i]));
//now i points to your entry, do whatever you need
}
void foo(int arg) {
lookup(__func__);
//do something
}
void bar(int arg) {
lookup(__func__);
//do something
}
(代码可能需要一些修复,我还没有尝试编译它,只是为了说明这个想法)
答案 3 :(得分:1)
如果您使用C ++,以下信息可能会对您有所帮助:
对象是类型化的,functors是包装为对象的函数,RTTI允许在运行时标识类型。
Functors带有运行时开销,如果这对你来说是个问题我会建议使用代码生成来硬编码知识,或者利用仿函数的OO-heirarchy。
答案 4 :(得分:1)
这是一个获取调用者地址的技巧,它可能会被清理一下。 依赖于GCC extension for getting a label's value。
#include <stdio.h>
#define MKLABEL2(x) label ## x
#define MKLABEL(x) MKLABEL2(x)
#define CALLFOO do { MKLABEL(__LINE__): foo(&&MKLABEL(__LINE__));} while(0)
void foo(void *addr)
{
printf("Caller address %p\n", addr);
}
int main(int argc, char **argv)
{
CALLFOO;
return 0;
}
答案 5 :(得分:1)
我还有一个问题,当我创建宏模板协程抽象时,我需要当前函数的地址,人们可以像现代协程语言功能(等待和异步)一样使用它们。当有一个中央循环将不同的异步功能调度为(协作)任务时,它将补偿缺少的RTOS。将中断处理程序转换为异步功能甚至会导致竞态条件,例如在抢先式多任务系统中。
我注意到我需要知道协程的最终返回地址的调用者函数的地址(这当然不是初始调用的返回地址)。只有异步函数需要知道它们自己的地址,以便它们可以将其作为隐藏的第一个参数传递给AWAIT()宏。由于使用宏解决方案来检测代码就像定义函数一样简单,因此拥有一个类似于async-keyword的宏就足够了。
这是具有GCC扩展名的解决方案:
#define _VARGS(...) _VARGS0(__VA_ARGS__)
#define _VARGS0(...) ,##__VA_ARGS__
typedef union async_arg async_arg_t;
union async_arg {
void (*caller)(void*);
void *retval;
};
#define ASYNC(FUNCNAME, FUNCARGS, ...) \
void FUNCNAME (async_arg_t _arg _VARGS FUNCARGS) \
GENERATOR( \
void (*const THIS)(void*) = (void*) &FUNCNAME;\
static void (*CALLER)(void*), \
CALLER = _arg.caller; \
__VA_ARGS__ \
)
#define GENERATOR(INIT,...) { \
__label__ _entry, _start, _end; \
static void *_state = (void*)0; \
INIT; \
_entry:; \
if (_state - &&_start <= &&_end - &&_start) \
goto *_state; \
_state = &&_start; \
_start:; \
__VA_ARGS__; \
_end: _state = &&_entry; \
}
#define AWAIT(FUNCNAME,...) ({ \
__label__ _next; \
_state = &&_next; \
return FUNCNAME((async_arg_t)THIS,##__VA_ARGS__);\
_next: _arg.retval; \
})
#define _I(...) __VA_ARGS__
#define IF(COND,THEN) _IF(_I(COND),_I(THEN))
#define _IF(COND,THEN) _IF0(_VARGS(COND),_I(THEN))
#define _IF0(A,B) _IF1(A,_I(B),)
#define _IF1(A,B,C,...) C
#define IFNOT(COND,ELSE) _IFNOT(_I(COND),_I(ELSE))
#define _IFNOT(COND,ELSE) _IFNOT0(_VARGS(COND),_I(ELSE))
#define _IFNOT0(A,B) _IFNOT1(A,,_I(B))
#define _IFNOT1(A,B,C,...) C
#define IF_ELSE(COND,THEN,ELSE) IF(_I(COND),_I(THEN))IFNOT(_I(COND),_I(ELSE))
#define WAIT(...) ({ \
__label__ _next; \
_state = &&_next; \
IF_ELSE(_I(__VA_ARGS__), \
static __typeof__(__VA_ARGS__) _value;\
_value = (__VA_ARGS__); \
return; \
_next: _value; \
, return; _next:;) \
})
#define YIELD(...) do { \
__label__ _next; \
_state = &&_next; \
return IF(_I(__VA_ARGS__),(__VA_ARGS__));\
_next:; \
} while(0)
#define RETURN(VALUE) do { \
_state = &&_entry; \
if (CALLER != 0) \
CALLER((void*)(VALUE +0));\
return; \
} while(0)
#define ASYNCALL(FUNC, ...) FUNC ((void*)0,__VA_ARGS__)
我知道,一种更可移植(且可能更安全)的解决方案将使用switch-case语句而不是标签地址,但是我认为gotos比switch-case-statement效率更高。它还具有一个优点,您可以轻松地在任何其他控件结构中使用这些宏,并且break
不会产生意外的影响。
您可以像这样使用它:
#include <stdint.h>
int spi_start_transfer(uint16_t, void *, uint16_t, void(*)());
#define SPI_ADDR_PRESSURE 0x24
ASYNC(spi_read_pressure, (void* dest, uint16_t num),
void (*callback)(void) = (void*)THIS; //see here! THIS == &spi_read_pressure
int status = WAIT(spi_start_transfer(SPI_ADDR_PRESSURE,dest,num,callback));
RETURN(status);
)
int my_gen() GENERATOR(static int i,
while(1) {
for(i=0; i<5; i++)
YIELD(i);
}
)
extern volatile int a;
ASYNC(task_read, (uint16_t threshold),
while(1) {
static uint16_t pressure;
int status = (int)AWAIT(spi_read_pressure, &pressure, sizeof pressure);
if (pressure > threshold) {
a = my_gen();
}
}
)
您必须使用AWAIT
来调用异步函数来获取返回值,而必须使用ASYNCALL
来返回值。 AWAIT
只能由ASYNC
函数调用。您可以使用WAIT
(带或不带值)。 WAIT
产生作为参数给出的表达式,该表达式在函数恢复后返回。 WAIT
仅可用于ASYNC
功能。尽管将WAIT
保留为参数会浪费每个WAIT()
的每次调用的一个新的静态内存,所以建议使用WAIT()
不带参数。如果所有WAIT
调用将对整个函数使用相同的单个静态变量,则可以进行改进。
这只是协程抽象的一个非常简单的版本。由于所有静态变量都包含一个静态堆栈框架,因此该实现不能具有同一函数的嵌套或交错调用。
如果要解决此问题,还需要区分恢复旧函数调用和开始新函数调用。您可以在函数开始的ASYNC宏中添加诸如堆栈帧队列之类的详细信息。为每个函数的堆栈框架创建一个自定义结构(也可以在宏和其他宏参数中完成)。此自定义堆栈帧类型在进入宏时从队列中加载,在退出宏时存储回去,或在调用结束时被删除。
您可以在async_arg_t
联合中将堆栈帧索引用作替代参数。当参数是一个地址时,它将启动一个新的调用,或者当给定一个堆栈帧索引时,它将继续一个旧的调用。堆栈帧索引或连续性必须作为用户定义的参数传递给恢复协程的回调。
答案 6 :(得分:0)
不,该功能不了解自己。你必须构建你自己正在谈论的表,然后如果你想让一个函数意识到自己,你将不得不将索引作为参数传递给全局表(或函数的指针)。
注意:如果要执行此操作,则应该具有一致的参数命名方案。
答案 7 :(得分:0)
如果您想以“通用”方式执行此操作,那么您应该使用已经提及的设施(__cyg_profile_func*
),因为这是他们的设计目的。其他任何东西都必须像你的个人资料一样临时。
老实说,以通用方式(使用过滤器)执行操作可能比您将即时插入的任何新方法更不容易出错。
答案 8 :(得分:0)
您可以使用setjmp()
捕获此信息。由于它会保存足够的信息以返回当前功能,因此必须在提供的jmp_buf
中包含该信息。
此结构非常不可移植,但您明确提到了GCC,因此可能不是阻塞问题。请参阅this GCC/x86示例,了解其大致工作原理。
答案 9 :(得分:0)
如果你想做代码生成,我会从Imatix推荐GSLGen。它使用XML来构建代码模型,然后使用简单的PHP(如自上而下的生成语言)来吐出代码 - 它已被用于生成C代码。
我个人一直在玩lua来生成代码。
答案 10 :(得分:0)
static const char * const cookie = __FUNCTION__;
__FUNCTION__
将存储在二进制文件的文本段中,指针始终是唯一且有效的。
答案 11 :(得分:-1)
另一个选择,如果可移植性不是问题,那就是调整GCC源代码......任何志愿者?!
答案 12 :(得分:-2)
如果你需要的只是每个函数的唯一标识符,那么在每个函数的开头,把它放在:
static const void * const cookie = &cookie;
然后保证cookie
的值是唯一标识该函数的值。