函数名就是函数的指针的错误说法

函数名本身并不是一个指针变量。它是一个代表函数代码块的“符号”(Function Designator)。只是在大多数表达式中,C语言编译器会自动将这个符号**隐式转换(退化)**为一个指向该函数的指针。

基于这个前提,我们来看看它的“指向”和“自身的地址”分别是什么:

1. 它所指向的地址(func

当你在代码中使用函数名(例如 func)时,它退化成了一个函数指针。

  • 它指向哪里:它指向的是这个函数在内存中的**代码段(Text Segment)**里的起始物理/虚拟地址。
  • 具体含义:也就是CPU执行这个函数时,第一条机器指令所在的内存位置。

2. 它自身的地址(&func

因为函数名只是一个符号,它并不是像普通的指针变量(比如 int *p)那样在内存的栈区或数据区占用4个或8个字节来存储地址。

  • 它的地址是什么:因为没有独立的内存空间来存储这个“指针”,所以对函数名取地址(&func),在C语言标准中的定义是:直接返回该函数本身的入口地址
  • 核心结论:在数值上,函数名本身的值对函数名取地址的值 是完全相等的。

💡 一个有趣的代码现象

因为上述的语法规则,在C语言中会出现一个看起来很反直觉,但在逻辑上完全自洽的现象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

void myFunction() {
// 只是一个测试函数
}

int main() {
printf("函数名隐式转换的地址: %p\n", (void*)myFunction);
printf("对函数名取地址的地址: %p\n", (void*)&myFunction);
printf("对函数名解引用的地址: %p\n", (void*)*myFunction);
printf("甚至多次解引用的地址: %p\n", (void*)***myFunction);

return 0;
}

运行结果: 你会发现上面这四个 printf 打印出来的十六进制内存地址是一模一样的。

  • myFunction:退化为指向函数入口的指针。
  • &myFunction:明确要求获取函数的地址,返回的也是函数的入口地址。
  • *myFunction:对函数指针解引用,得到函数本身(符号),然后它在表达式中又立刻再次退化成了函数指针。

核心结论

函数名本质上是一个“不可修改的符号地址常量”(Function Designator)。

它在 C 语言的底层逻辑中扮演着**“入口标签”**的角色。我们可以从以下三个维度来彻底定义它:


1. 它不是变量,而是符号

在程序编译成汇编代码时,函数名 myFunction 会被转换成一个具体的内存地址(通常在代码段 .text 中)。

  • 指针变量:在内存中有一个实实在在的“坑位”,里面存着别人的地址。
  • 函数名:它自己就是那个“地址”本身。它不占用数据区的内存,它只存在于编译器的符号表中。

2. 它具有“自动退化”属性

这是导致大家混淆的根本原因。根据 C 语言标准:

一个函数标识符(除了在 sizeof& 运算符中)都会被自动转换为“指向该函数的指针”。

这就好比:函数名是“冰”,指针是“水”。冰在常温下(参与运算时)会立刻化成水。你平时看到的、用到的,其实都是它化成“水”(指针)之后的样子。

3. 三位一体的奇观

由于函数名是符号常量,且 C 标准规定对函数名取地址(&)或解引用(*)最终都要导向那个入口,所以产生了 C 语言中唯一的三位一体现象:

  • func(自动退化成的指针值)

  • &func(显式获取的函数入口地址)

  • *func(解引用后又立即退化回来的指针值)

    这三者的数值完全相等,且类型在表达式中也被视为一致。


总结对比表

特性 函数名 (func) 函数指针变量 (void *p)
本质 常量符号(Label) 变量(Container)
存储位置 代码段(符号表) 栈、堆或静态数据区
能否赋值 不能(它是只读的标签) (可以指向不同函数)
sizeof 1 (GCC) / 非法 (标准) 4 或 8 (指针长度)

总结

  • 它指向的地址:函数第一条机器指令所在的内存入口。
  • 它的地址:它没有作为变量的独立地址,&函数名 依然等于函数的入口地址。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#include <stdio.h>
#include <stdlib.h>

/* ==================== 基础概念:函数名就是函数指针 ==================== */

/* 函数声明 */
void my_function() {
printf("my_function 被调用\n");
}

void demonstrate_function_name() {
printf("=== 函数名本身就是函数指针(大部分情况正确) ===\n\n");

/* 1. 函数名是什么? */
printf("1. 函数名的本质:\n");
printf(" 在C语言中,函数名实际上就是一个指针,指向函数在内存中的入口地址。\n\n");

/* 2. 证明函数名是地址 */
printf("2. 证明函数名是地址:\n");
printf(" my_function 的地址: %p\n", (void*)my_function);
printf(" &my_function 的地址: %p\n", (void*)&my_function);
printf(" 两者相同,说明 my_function 和 &my_function 是等价的。\n\n");

/* 3. 通过函数名调用函数的等价写法 */
printf("3. 调用函数的多种等价方式:\n");
printf(" a. 直接调用: ");
my_function();

printf(" b. 通过解引用函数指针调用: ");
(*my_function)();

printf(" c. 通过取地址后解引用: ");
(*(&my_function))();

printf(" d. 通过函数指针变量: ");
void (*func_ptr)() = my_function;
func_ptr();

printf(" 所有方式都能正确调用函数,因为它们最终都指向同一个函数地址。\n");
}

/* ==================== 标准库函数示例 ==================== */
void demonstrate_stdlib_functions() {
printf("\n=== 标准库函数(malloc/free/realloc)也是函数指针 ===\n\n");

/* 1. 标准库函数的函数名也是指针 */
printf("1. 标准库函数的本质:\n");
printf(" malloc, free, realloc 是标准库<stdlib.h>中声明的函数。\n");
printf(" 在链接时,这些函数名会被解析为实际的内存地址。\n\n");

/* 2. 获取标准库函数的地址 */
printf("2. 获取标准库函数的地址:\n");
printf(" malloc 函数的地址: %p\n", (void*)malloc);
printf(" free 函数的地址: %p\n", (void*)free);
printf(" realloc 函数的地址: %p\n", (void*)realloc);
printf(" 这些地址是运行时确定的,由C标准库提供。\n\n");

/* 3. 标准库函数名与函数指针的赋值 */
printf("3. 函数指针可以指向标准库函数:\n");

/* 定义函数指针类型 */
typedef void* (*malloc_func)(size_t);
typedef void (*free_func)(void*);
typedef void* (*realloc_func)(void*, size_t);

/* 将标准库函数赋值给函数指针 */
malloc_func my_malloc = malloc;
free_func my_free = free;
realloc_func my_realloc = realloc;

printf(" 可以将 malloc 赋值给 malloc_func 类型的函数指针。\n");
printf(" 同理,free 和 realloc 也可以。\n\n");

/* 4. 通过函数指针调用标准库函数 */
printf("4. 通过函数指针使用标准库函数:\n");
int* arr = (int*)my_malloc(5 * sizeof(int));
if (arr) {
printf(" 通过函数指针 my_malloc 分配了内存\n");

/* 重新分配 */
arr = (int*)my_realloc(arr, 10 * sizeof(int));
printf(" 通过函数指针 my_realloc 重新分配了内存\n");

/* 释放 */
my_free(arr);
printf(" 通过函数指针 my_free 释放了内存\n");
}
}

/* ==================== 模拟cJSON的内存钩子系统 ==================== */
/* 定义内存钩子结构体(简化版cJSON_Hooks) */
typedef struct {
void* (*malloc_fn)(size_t);
void (*free_fn)(void*);
void* (*realloc_fn)(void*, size_t);
} cJSON_Hooks;

/* 全局钩子(模拟cJSON的global_hooks) */
static cJSON_Hooks global_hooks = {
malloc, /* 默认使用标准库的malloc */
free, /* 默认使用标准库的free */
realloc /* 默认使用标准库的realloc */
};

/* 模拟cJSON_InitHooks函数 */
void cJSON_InitHooks(cJSON_Hooks* hooks) {
if (hooks == NULL) {
/* 重置为默认(标准库函数) */
global_hooks.malloc_fn = malloc;
global_hooks.free_fn = free;
global_hooks.realloc_fn = realloc;
printf("钩子已重置为标准库函数\n");
return;
}

/* 如果用户提供了自定义函数,则使用自定义函数 */
global_hooks.malloc_fn = (hooks->malloc_fn != NULL) ? hooks->malloc_fn : malloc;
global_hooks.free_fn = (hooks->free_fn != NULL) ? hooks->free_fn : free;

/* 只有在使用标准库的malloc和free时才使用标准库的realloc */
if ((global_hooks.malloc_fn == malloc) && (global_hooks.free_fn == free)) {
global_hooks.realloc_fn = realloc;
} else {
global_hooks.realloc_fn = NULL; /* 用户自定义分配器可能没有realloc */
}

printf("钩子已更新\n");
}

/* 演示cJSON_InitHooks的工作原理 */
void demonstrate_cjson_hooks() {
printf("\n=== 模拟cJSON_InitHooks的工作原理 ===\n\n");

printf("1. 初始状态:\n");
printf(" global_hooks.malloc_fn 指向: %p (malloc)\n", (void*)global_hooks.malloc_fn);
printf(" global_hooks.free_fn 指向: %p (free)\n", (void*)global_hooks.free_fn);
printf(" global_hooks.realloc_fn 指向: %p (realloc)\n", (void*)global_hooks.realloc_fn);

/* 2. 自定义内存分配函数 */
printf("\n2. 定义自定义内存分配函数:\n");

void* my_custom_malloc(size_t size) {
printf(" [自定义malloc] 分配 %zu 字节\n", size);
return malloc(size);
}

void my_custom_free(void* ptr) {
printf(" [自定义free] 释放内存\n");
free(ptr);
}

/* 创建自定义钩子 */
cJSON_Hooks custom_hooks = {
my_custom_malloc,
my_custom_free,
NULL /* 不提供自定义realloc */
};

/* 3. 初始化自定义钩子 */
printf("\n3. 调用cJSON_InitHooks(&custom_hooks):\n");
cJSON_InitHooks(&custom_hooks);

printf("\n 更新后:\n");
printf(" global_hooks.malloc_fn 指向: %p (自定义函数)\n", (void*)global_hooks.malloc_fn);
printf(" global_hooks.free_fn 指向: %p (自定义函数)\n", (void*)global_hooks.free_fn);
printf(" global_hooks.realloc_fn 指向: %p (NULL,因为使用了自定义函数)\n", (void*)global_hooks.realloc_fn);

/* 4. 重置为默认 */
printf("\n4. 调用cJSON_InitHooks(NULL)重置:\n");
cJSON_InitHooks(NULL);

printf("\n 重置后:\n");
printf(" global_hooks.malloc_fn 指向: %p (malloc)\n", (void*)global_hooks.malloc_fn);
printf(" global_hooks.free_fn 指向: %p (free)\n", (void*)global_hooks.free_fn);
printf(" global_hooks.realloc_fn 指向: %p (realloc)\n", (void*)global_hooks.realloc_fn);
}

/* ==================== 编译器/链接器的视角 ==================== */
void compiler_linker_perspective() {
printf("\n=== 编译器/链接器如何处理函数名 ===\n\n");

printf("1. 编译阶段:\n");
printf(" 编译器看到 'malloc' 时,知道它是一个外部函数声明。\n");
printf(" 编译器不会立即知道它的地址,所以生成一个未解析的符号引用。\n\n");

printf("2. 链接阶段:\n");
printf(" 链接器在标准库中找到 malloc 函数的实现。\n");
printf(" 链接器将 'malloc' 替换为实际的函数地址。\n\n");

printf("3. 运行时:\n");
printf(" 当程序运行时,'malloc' 已经是一个确定的内存地址。\n");
printf(" 所以代码中的 'malloc' 就是一个指向分配内存函数的指针。\n\n");

printf("4. 赋值操作:\n");
printf(" 当执行 'global_hooks.allocate = malloc' 时,\n");
printf(" 实际上是将 malloc 函数的地址赋值给 allocate 函数指针。\n");
printf(" 这就像将整数 10 赋值给整数变量一样,只是这里赋值的是地址。\n");
}

/* ==================== 实际代码验证 ==================== */
void practical_verification() {
printf("\n=== 实际验证代码 ===\n\n");

/* 1. 验证函数名是常量指针 */
printf("1. 验证函数名是常量指针(不能修改):\n");
printf(" malloc 的类型: 函数指针常量\n");
printf(" malloc 可以被赋值给其他指针,但不能修改 malloc 本身。\n\n");

/* 2. 对比函数指针和函数调用 */
printf("2. 对比函数指针和函数调用:\n");
printf(" malloc 表示函数的地址(指针)\n");
printf(" malloc(100) 表示调用函数,传入参数100\n");
printf(" 注意:malloc 和 malloc(100) 是完全不同的东西!\n\n");

/* 3. 演示错误的用法 */
printf("3. 演示错误的用法(编译错误):\n");
printf(" malloc = NULL; // 错误!函数名是常量,不能赋值\n");
printf(" &malloc = some_function; // 错误!不能取函数名的地址再赋值\n");

/* 4. 正确的用法 */
printf("\n4. 正确的用法:\n");
printf(" void* (*func_ptr)(size_t) = malloc; // 正确\n");
printf(" void* ptr = malloc(100); // 正确,调用函数\n");
printf(" void* ptr2 = func_ptr(100); // 正确,通过函数指针调用\n");
}

/* ==================== 总结 ==================== */
void summary() {
printf("\n=== 总结:为什么 malloc, free 等单词会被认为是函数 ===\n\n");

printf("1. 历史原因:\n");
printf(" 在C语言设计中,函数名被设计为函数指针常量。\n");
printf(" 这是语言规范的一部分,简化了函数调用的语法。\n\n");

printf("2. 语法糖:\n");
printf(" 函数调用语法 func() 实际上是 (*func)() 的简写。\n");
printf(" 所以 func 和 *func 是等价的,都指向函数地址。\n\n");

printf("3. 标准库约定:\n");
printf(" malloc, free, realloc 是C标准库定义的函数。\n");
printf(" 在包含 <stdlib.h> 后,这些名字就有了函数声明。\n\n");

printf("4. 在cJSON_InitHooks中的具体应用:\n");
printf(" global_hooks.allocate = malloc; // 将malloc函数的地址赋给allocate指针\n");
printf(" global_hooks.deallocate = free; // 将free函数的地址赋给deallocate指针\n");
printf(" 这不是函数调用,而是函数指针的赋值。\n\n");

printf("5. 关键理解:\n");
printf(" 在C语言中,函数名在表达式中使用时,会自动转换为函数指针。\n");
printf(" 唯一的例外是当它作为 sizeof 或 & 操作符的操作数时。\n");
printf(" 但即使 &malloc,得到的还是 malloc 的地址,与 malloc 相同。\n");
}

/* ==================== 主函数 ==================== */
int main() {
printf("C语言函数名与函数指针详解\n");
printf("===========================\n\n");

demonstrate_function_name();
demonstrate_stdlib_functions();
demonstrate_cjson_hooks();
compiler_linker_perspective();
practical_verification();
summary();

return 0;
}