函数指针和函数地址

58

我认为在创建函数指针时,您不需要使用operator &来获取初始函数的地址:

#include <stdio.h>

double foo (double x){
    return x*x;
}

int main () {

    double (*fun1)(double) = &foo;
    double (*fun2)(double) =  foo;

    printf("%f\n",fun1(10));
    printf("%f\n",fun2(10));

    printf("fun1 = %p \t &foo = %p\n",fun1, &foo);
    printf("fun2 = %p \t  foo = %p\n",fun2,  foo);       

    int a[10];

    printf("  a = %p \n &a = %p  \n",a,&a);

    return 0;
}

输出:

>./a.out 
100.000000
100.000000
fun1 = 0x4004f4      &foo = 0x4004f4
fun2 = 0x4004f4       foo = 0x4004f4
  a = 0x7fff26804470 
 &a = 0x7fff26804470 

后来我意识到对于数组也是如此,这意味着如果你有int a[10],那么a&a都指向同一位置。为什么数组和函数会这样呢?是因为地址被保存在一个与其值(即地址)相同的内存位置中吗?


我从来没有完全理解为什么在获取函数地址时不需要使用取地址运算符。我一直认为这只是一点语法糖。 - BillRobertson42
@Bill:确实!这就是我以前总是认为的,但似乎你不需要这样! - mmirzadeh
如果地址保存在与其本身相同的内存位置,则实际的函数代码/数组数据就不存在了!您将实际上将数组视为指针,并且将作为参数传递的函数也视为指针,因此您不需要使用addressof运算符。基本上,函数或数组的“值”是无意义的。只有地址有意义,因此当您查询“值”时以及查询地址时都会得到它。 - user529758
5个回答

78

给定int a [10]a&a 都返回相同的地址,但它们的类型不同。

a 的类型为int [10],当它被隐式转换为指针类型时,指针的类型为int*,并且指向数组的初始元素。 &a 的类型为int(*)[10] (即指向包含十个整数的数组的指针)。 因为数组中不能有填充,所以它们都产生具有相同的指针,但指针具有不同的类型

函数与数组类似,但并非完全相同。 函数foo 的类型为double(double)。每当foo在表达式中使用且不是一元&运算符的操作数时,它会被隐式转换为指向其自身的指针,该指针的类型为double(*)(double)

因此,在所有实际目的中,函数名称和指向同一函数的指针是可互换的。 有一些微妙之处,我在回答“为什么所有这些疯狂的函数指针定义都有效? 到底发生了什么?”中讨论了所有这些细节。 (该问题是关于C++的,但C++中的非成员函数规则与C中的函数相同。)


9

没有额外的存储空间专门用于指向函数/数组。

对于大多数变量,variable_name 除了获取该变量的地址之外还有其他含义,因此需要使用 &variable 来获取地址。

对于函数或数组,function_name(仅本身,不跟在括号后面)没有任何其他含义,因此将其解释为获取函数地址是没有问题的。

同样地,在反向操作中:普通指针需要显式地进行解引用,但指向函数的指针不需要(再次强调,因为没有其他合理的解释),因此给定一个指向函数的指针:

int (*func)(param_list);

以下两种写法是等价的,都会调用指向任何函数func的指针:
(*func)(params);

func(params);

2
似乎有一点不同,对于 int a[10],(*a)[5] 不是有效的调用。 - mmirzadeh

1

fun&fun 是完全相同的(除了 sizeof(f) 是非法的)。 a&a 相同直到指针运算: a + 10 == &a + 1,因为 10*sizeof(*a) == sizeof(a) (其中 sizeof(*a) == sizeof(int))。


1

基本上,由于函数名称“已知”为函数,因此“&”不是严格必要的。对于数组,这种行为是相同的。请记住,函数本身不是变量,因此有时它的行为会与您的预期有所不同。如果您有K&R的第二版,可以查看第5.11节关于指向函数的指针,或者查看参考手册的结尾部分。

部分A7.1指针生成:如果表达式或子表达式的类型为“T的数组”,其中T是某种类型,则表达式的值为数组中第一个对象的指针,并且表达式的类型改为“T的指针”。如果表达式是一元“&”操作符的操作数,则不进行此转换,...类似地,“返回T的函数”的类型的表达式(当不用作“&”操作符的操作数时)将转换为“返回T的函数的指针”。

部分A7.4.2地址操作符:一元“&”操作符取其操作数的地址....结果是所引用的左值的对象或函数的指针。如果操作数的类型为T,则结果的类型为“指向T的指针”。

据我所知,对于C99来说也是一样的。

0
printf("fun1 = %p \t &foo = %p\n", fun1, foo);
在这里,您通过传递“按值传递”的函数指针调用了foo。 printf("fun2 = %p \t foo = %p\n", fun2, &foo);
在这里,您通过传递“按引用传递”的函数指针调用了“&foo”。
在两种情况下,您都只使用函数指针调用printf。
请记住,“foo”本身是一个“函数指针值”,而不是变量。
数组也是如此。int arr[10]被翻译为获取连续的10个整数块,并将第一个元素的地址存储到arr中。因此,arr也是一个指针。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接