在不事先声明的情况下调用 C 函数

Call C function without declaring it beforehand

简短版本:我想在调用它的同一语句中声明一个函数。我正在寻找的语法是这样的:

// foo is undeclared in this file, and implemented in another file
int main() {
    void* p = (cast_to_function_that_receivs_ints_and_returns_pointer)foo(1,2);
}

长版:

以下代码会产生明显的 implicit declaration 警告和 undefined reference 错误,因为调用了 foo:

// a.c
int main() {
    void* p = foo(1,2);
}

我在编译中加入如下文件来解决undefined reference:

// b.c
void* foo(int a, int b) {
    return (void*)0xbadcafe;
}

我现在想解决implicit declaration。通常的解决方案是将 a.c 修改为 #include 对 foo 的声明或声明它本身,例如:

// a.c
void* foo(int a, int b);
int main() {
    void* p = foo(1,2);
}

但我宁愿不声明 foo,而是修改调用 foo 的行,类似于函数指针语法,或者类似于我在 "short versions" 中发布的示例.有可能吗?

假设我精通 C 并且我有一个有效的动机 - 我想通过 -Dfoo=bar.

重新编译 "override" foo 的行为

您可以在调用时转换函数:

void *p = ((void *(*)(int, int))foo)(1, 2);

它很丑,我看不出有什么正当理由,但你可以。

没有办法绕过申报要求。您必须定义一个符号供编译器使用。一些编译器允许您使用 pragma 或其他非标准功能来创建符号和 physical/virtual 地址之间的映射。

将您的 mock_foo.c 文件和 link 目标文件编译到程序而不是 foo.c。

另一种方法是只通过宏定义调用:

#ifdef MOCK_FOO
  #define (FOO(a, b) mock_foo(a, b))
#else
  #define (FOO(a, b) foo(a, b)
#endif

否则,您必须了解 compiler/linker 和 OS/loader 的工作原理,才能正确挂钩函数以调用模拟。高质量模拟框架的工具成本如此之高是有原因的。它们非常复杂。

所以如果我理解正确的话,你的动机是你有看起来像

的现有代码
p = bar(1,2);

并且您想定义宏,以便它调用 foo(1,2)。但是您不想修改源文件以包含 foo 的声明 - 您希望通过命令行宏定义来完成所有操作。我没听错吗?

既然你已经标记了这个 , perhaps you are willing to consider non-standard gcc extensions to the C language. If so, you can do it with gcc statement expressions,也由 clang 和 icc 支持。定义 bar 以扩展为包含块的表达式,该块声明 foo 并且其值是指向 foo 的指针。即:

#define bar ({ extern void *foo(int, int); foo; })

或从命令行:

gcc -D'bar=({ extern void *foo(int, int); foo; })'  call_bar.c

Try it on godbolt.

这有

一个变体是定义一个带有两个参数的宏bar(a,b),其中相应的语句表达式实际调用foo:

gcc -D'bar(a,b)=({ extern void *foo(int, int); foo((a), (b)); })' call_bar.c

但如果原始代码尝试调用 p = (bar)(a,b) 或尝试获取 bar 的地址,这将失败。

我不知道有什么方法可以在标准 C 中获得这种确切效果。但另一种方法是创建一个包含 foo 声明的头文件,然后使用 -include 到 "inject" 它在源文件的顶部:

gcc -include declare_foo.h -Dbar=foo call_bar.c

这在技术上不是您所要求的,因为在某种程度上它确实涉及声明 foo "beforehand",但它仍可能有助于解决您的问题。在这种情况下,一切都是标准 C,但我们已将 "non-portability" 从代码移至构建过程。


另一方面,如果 bar 所需的替代品足够简单,可以放入宏中,例如示例中的常量 return,那么您可以去掉中间人 foo 然后定义一个宏:

gcc -D'bar(a,b)=((void *)0xbadcafe)' call_bar.c