函数名就是函数的指针的错误说法
函数名本身并不是一个指针变量。它是一个代表函数代码块的“符号”(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) |
函数指针变量 (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"); printf("1. 函数名的本质:\n"); printf(" 在C语言中,函数名实际上就是一个指针,指向函数在内存中的入口地址。\n\n"); 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"); 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"); printf("1. 标准库函数的本质:\n"); printf(" malloc, free, realloc 是标准库<stdlib.h>中声明的函数。\n"); printf(" 在链接时,这些函数名会被解析为实际的内存地址。\n\n"); 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"); 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"); 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"); } }
typedef struct { void* (*malloc_fn)(size_t); void (*free_fn)(void*); void* (*realloc_fn)(void*, size_t); } cJSON_Hooks;
static cJSON_Hooks global_hooks = { malloc, free, realloc };
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; if ((global_hooks.malloc_fn == malloc) && (global_hooks.free_fn == free)) { global_hooks.realloc_fn = realloc; } else { global_hooks.realloc_fn = NULL; } printf("钩子已更新\n"); }
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); 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 }; 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); 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"); printf("1. 验证函数名是常量指针(不能修改):\n"); printf(" malloc 的类型: 函数指针常量\n"); printf(" malloc 可以被赋值给其他指针,但不能修改 malloc 本身。\n\n"); printf("2. 对比函数指针和函数调用:\n"); printf(" malloc 表示函数的地址(指针)\n"); printf(" malloc(100) 表示调用函数,传入参数100\n"); printf(" 注意:malloc 和 malloc(100) 是完全不同的东西!\n\n"); printf("3. 演示错误的用法(编译错误):\n"); printf(" malloc = NULL; // 错误!函数名是常量,不能赋值\n"); printf(" &malloc = some_function; // 错误!不能取函数名的地址再赋值\n"); 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; }
|