typedef in C

笔者对C语言只是知道简单的使用方法,并不是非常精通。近日在Redis的 server.h 文件中发现了这样的代码:

1
2
3
4
5
6
typedef void redisCommandProc(client *c);
struct redisCommand {
char *name;
redisCommandProc *proc;
......
};

对于此处的 typedef 语句感到较为迷惑。就以前所知,C语言中的 typedef 就是用于创建一个数据类型的别名的关键字。此处的 typedef 语句应该是一个函数指针类型的别名. 而在结构体redisCommand中的 proc成员应该就是一个指向函数的指针,该函数接受一个client指针作为参数,返回为空。

函数指针类型的别名在笔者印象中应该采用如下的方式声明:

1
typdef type (*funcptr)(args...);

此处,假设需要声明一个函数指针,其参数为一个client指针,返回为空,那么应该如此声明:

1
typedef void (*redisCommandProc)(client *c);

在Redis源码中的这个声明并没有显示将redisCommandProc声明成一个指针。那么这两种声明方式又有什么异同呢?于是笔者写了一个简单的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
typedef void funca(char);
typedef void (*funcb)(char);
typedef struct {
/* Get compiler error : funca_without_pointer declared as a function*/
//funca funca_without_pointer;
funca *funca_with_reference;
funca *funca_without_reference;
funcb funcb_with_reference;
funcb funcb_without_reference;
} FuncCaller;

static void func(char);

int main(int argc, char** argv) {
FuncCaller caller;
caller.funca_with_reference = &func;
caller.funca_without_reference = func;
fprintf(stdout, "funca_with_reference %p, funca_without_reference %p\n", caller.funca_with_reference, caller.funca_without_reference);
fprintf(stdout, "*funca_with_reference %p, *funca_without_reference %p\n", *caller.funca_with_reference, *caller.funca_without_reference);
caller.funcb_with_reference = &func;
caller.funcb_without_reference = func;
fprintf(stdout, "funcb_with_reference %p, funcb_without_reference %p\n", caller.funcb_with_reference, caller.funcb_without_reference);
fprintf(stdout, "*funcb_with_reference %p, *funcb_without_reference %p\n", *caller.funcb_with_reference, *caller.funcb_without_reference);
fprintf(stdout, "funca\n");
caller.funca_with_reference('a');
fprintf(stdout, "funcb\n");
caller.funcb_without_reference('b');
fprintf(stdout, "funca address%p\n", &caller.funca_without_reference);
fprintf(stdout, "funcb address%p\n", &caller.funcb_without_reference);
}

void func(char source) {
fprintf(stdout, "Come from %c\n", source);
}

此处采用的编译器信息为:

1
2
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.3.0

获得了如下的输出:

1
2
3
4
5
6
7
8
9
10
funca_with_reference 0x104f34e40, funca_without_reference 0x104f34e40
*funca_with_reference 0x104f34e40, *funca_without_reference 0x104f34e40
funcb_with_reference 0x104f34e40, funcb_without_reference 0x104f34e40
*funcb_with_reference 0x104f34e40, *funcb_without_reference 0x104f34e40
funca
Come from a
funcb
Come from b
funca address0x7ffeeaccb948
funcb address0x7ffeeaccb958

根据上述结果可以看出,本质上,Redis中的声明方式:

1
typedef void redisCommandProc(client *c);

本质上是对函数类型的一个别名,而非对函数指针的一个别名。当使用的时候,需要使用 redisCommandProc * 来声明一个指向函数的指针 ,如果遗漏了指针声明,则会变成一个函数类型,将会产生编译时的错误。

对于C语言中的函数名与函数指针,本质上二者并没有差别,都是存储了指向该函数的地址。因此,以下的调用其实是一样的:

1
2
func('a');
(*func)('a');

以上可以从 C99 Specification 中的 6.3.2.1.4 获得认证:

a function designator with type “function returning type” is converted to an expression that has type “pointer to function returning type”