在某些库代码中,有一种为事件设置回调的模式,其中回调可能会收到一个参数。回调本身可能不会对参数执行任何操作,或者参数可能是0
,但它会被传递。将其分解为基础知识,如下所示:
#include <stdio.h>
#include <string.h>
void callback_1(char *data) {
printf("Length of data: %d\n", strlen(data));
}
void callback_2() {
printf("No parameters used\n");
}
typedef void (*Callback)(char *);
int main(void) {
Callback callback;
callback = callback_1;
callback("test");
callback = callback_2;
callback("test");
return 0;
}
这在GCC 4.9.3(32位)上编译并运行,没有任何意外。
一个好的回调函数有一个像callback_1这样的签名,但是如果它没有被使用,我偶尔会忘记data
参数。虽然我知道C并不总是类型安全的(特别是关于void指针),但我预计会发出警告,因为如果我提供了一个不匹配的参数,例如int data
,我会收到有关不兼容类型的警告。如果Callback
的typedef不接受参数,如果回调函数在签名中有一个参数,我会收到编译错误。
在C中是否有一种方法可以获得警告,其中函数指针被分配给签名缺少参数的函数?如果回调缺少参数,堆栈上会发生什么?是否可能会在回调中错过参数?
答案 0 :(得分:3)
这是因为您将callback2
定义为:
void callback_2()
空括号表示它需要未指定的个参数。因此,它有资格被分配到Callback
类型。
如果您将定义更改为:
void callback_2(void)
这显式指定该函数接受0个参数,并且您将获得“从不兼容的指针类型分配”警告。
为了正确捕获这个条件,使用-Wstrict-prototypes
和-Wall -Wextra
进行编译,如果声明或定义一个带有空参数列表的函数,你将获得以下内容:
warning: function declaration isn’t a prototype
答案 1 :(得分:1)
您的代码不仅具有未定义的行为,还包含已弃用的功能。
6.7.5.3/14
标识符列表仅声明参数的标识符 功能。函数声明符中的空列表,它是a的一部分 该函数的定义指定该函数没有 参数。函数声明符中的空列表不是其中的一部分 该函数的定义指定没有关于的信息 提供参数的数量或类型。
注意区别。 void f();
是没有定义的声明者。 void f() {}
是一个定义的声明者。
6.5.2.2/2:
如果表示被调用函数的表达式具有类型 包括一个原型,参数的数量应该是一致的 参数数量。每个参数都应具有一个类型 value可以分配给具有非限定版本的对象 其相应参数的类型。
6.11.6
使用带有空括号的函数声明符(不是 prototype-format参数类型声明符)是一个过时的 特征
void f()
和void f(void)
是兼容类型,但由于f()
定义了不带参数的函数,因此使用参数调用它是未定义的行为。
足够好的迂腐,那么究竟发生了什么? C中没有名称错位,因此链接器只能看到函数的名称。 GCC和Clang都发出以完全相同的方式调用函数的代码。他们将指向函数的指针推入堆栈,参数(&#34; test&#34;字符串)然后他们调用它。这里真的没有什么可疑的。这是我objdump
所得到的:
00000000004006b3 <callback_2>:
...
我只包含此内容以显示callback_2
的地址。此示例中callback_1
的地址为400686。
首先是一些一般的堆栈内容:
4006c4: 55 push rbp
4006c5: 48 89 e5 mov rbp,rsp
4006c8: 48 83 ec 10 sub rsp,0x10
我们存储callback_one
的地址:
4006cc: 48 c7 45 f8 86 06 40 mov QWORD PTR [rbp-0x8],0x400686
4006d3: 00
4006d4: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
然后我们的&#34;测试&#34;串。使用objdump -s -j .rodata
表示我们的字符串的地址位于索引7,地址4007b0。
4007b0 73207573 65640074 65737400 s used.test.
1234567
4007b0 + 7是4007b7。
4006d8: bf b7 07 40 00 mov edi,0x4007b7
致电callback_one
:
4006dd: ff d0 call rax
重复callback_two
:
4006df: 48 c7 45 f8 b3 06 40 mov QWORD PTR [rbp-0x8],0x4006b3
4006e6: 00
4006e7: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
4006eb: bf b7 07 40 00 mov edi,0x4007b7
4006f0: ff d0 call rax
那么为什么你没有得到任何警告?嗯,从技术上讲,你并没有违反任何语言规则。未定义的行为不需要可诊断。这个故事的寓意是:如果你认为代码需要警告,就不要写它。但C是一种棘手的语言。只要您了解语言,警告以及您的编译器正在做什么就应该没问题。