【问题标题】:Call C function without declaring it beforehand调用 C 函数而不事先声明它
【发布时间】:2020-05-09 15:07:13
【问题描述】:

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

// 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 的行,类似于函数指针语法,或者我在“短版本”中发布的示例。有可能吗?

假设我精通 C 并且我有一个有效的动机 - 我想通过使用 -Dfoo=bar 重新编译来“覆盖”foo 的行为。

【问题讨论】:

  • "但我宁愿不声明 foo" --> 解释一下为什么不声明?否则声明 void* foo(int a, int b); 是最好的解决方案。
  • 这有什么作用?为什么这是我们应该接受的请求?它使哪些方面更清晰、更有效或更好?
  • 为了解释为什么我不想声明你可以阅读问题的最后一行。
  • 最后一行不解释。如果foo 被定义为bar,那么bar 被调用,而不是foo,它仍然必须被声明。这个定义并没有避免声明被调用函数的要求;它只是调用了一个不同的函数,它仍然必须被声明。
  • 如果你实际上一些指向函数的指针,并且它不是它指向的函数的正确类型(例如,你就是使用通用或灵活的函数指针编写接口),然后您可以将指针转换为所需的类型并调用它。但这不是“声明”函数标识符的问题。这是一种转换。

标签: c gcc compilation static-linking


【解决方案1】:

因此,如果我理解正确,您的动机是您拥有看起来像的现有代码

p = bar(1,2);

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

既然您已标记此,也许您愿意考虑对 C 语言进行非标准 gcc 扩展。如果是这样,您可以使用 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 “注入”它在源文件的顶部:

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

从技术上讲,这不是您所要求的,因为在某种程度上它确实涉及“预先”声明 foo,但它仍然可能有助于解决您的问题。在这种情况下,一切都是标准 C,但我们将“不可移植性”从代码转移到了构建过程。


另一方面,如果 bar 的所需替换很简单,可以放入宏中,例如示例中的常量 return,那么您可以删除中间人 foo 并定义一个宏:

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

【讨论】:

  • 尽管没有直接回答我(可能无法回答)的问题,但这两个答案都非常有帮助!我个人根本没想过定义函数宏,也不知道标志-include
【解决方案2】:

没有办法绕过声明要求。您必须定义一个符号供编译器使用。一些编译器允许您使用编译指示或其他非标准功能来创建符号和物理/虚拟地址之间的映射。

编译你的 mock_foo.c 文件并将目标文件链接到程序而不是 foo.c。

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

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

否则,您必须了解编译器/链接器和操作系统/加载器的工作原理,才能正确挂钩函数以调用模拟。高质量的模拟框架工具花费这么多钱是有原因的。它们非常复杂。

【讨论】:

  • 令人沮丧的答案,但在意料之中。我确实会使用一个简单的动态库代理来解决我想要覆盖的功能,幸运的是我就是这种情况..
  • 买家自责不只限于购车。然而,在这种特殊情况下,我想不出有一种编程语言能找到更好的解决方案。
【解决方案3】:

您可以按自己的方式转换函数:

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

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

【讨论】:

  • 你测试过这个吗?我希望出现未声明的标识符错误。这并不能解决问题,因为这应该通过隐式声明。
  • 这不是正确答案 - foo 不是变量,不能强制转换。
  • 啊,好,点,隐式声明只针对函数调用。
猜你喜欢
  • 1970-01-01
  • 2019-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-25
  • 2012-05-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多