Function Pointer
int main() { return (********puts)("Hello"); }
为何可以运行?
- C99 [ 6.3.2.1 ] A function designator is an expression that has function type
=>> Except when it is the operand of the sizeof operator or the unary & operator, a function designator with type ‘‘function returning type’’ is converted to an expression that has type ‘‘pointer to function returning type’’.
=>> * is Right associative operator
- C99 [6.5.1 ] It is an lvalue, a function designator, or avoid expression if the unparenthesized expression is, respectively, an lvalue, a function designator, or avoid expression.
- C99 [6.5.3.2-4] The unary * operator denotes indirection. If the operand points to a function, the result is a function designator
补充说明:C语言标准规定,函数指示符(function designator,即函数名字)既不是左值,也不是右值。
除了作为sizeof或取地址&的操作数,函数指示符在表达式中自动转换为函数指针类型右值。因此通过一个函数指针调用所指的函数,不需要在函数指针前加取值或反引用(*)运算符
- 程序示例
#include <stdio.h>
// fptr is a pointer, point to a function
void (*fptr) ();
// an empty function
void test() {
;
}
int main() {
fptr = test;
printf("test: %x, fptr: %x\n", test, fptr);
return 0;
}
- 根据 6.3.2.1
void (*fptr)();
void test();
fptr = test;
test: void (), 会被转成 function pointer: void (*) ()
fptr is a pointer to function with returning type,
- 根据 6.5.3.2
(*fptr) is a function designator, a function designator will be converted to pointer.
type of (*fptr): void ()
我们可以利用 gdb 去查看这些数值,搭配 print 指令
(gdb) print test
$1 = {void ()} 0x400526 <test>
(gdb) print test
$2 = {void ()} 0x400526 <test>
(gdb) print fptr
$3 = (void (*)()) 0x400526 <test>
(gdb) print (*fptr)
$4 = {void ()} 0x400526 <test>
(gdb) print (**fptr)
$5 = {void ()} 0x400526 <test>
test 是一个 function designator ,因为不是搭配 &, sizeof 使用,所以会被转成为 pointer to function
(*fptr) 是一個 function pointer 搭配 * (dereference, indirection) operator 使用,则它的结果会被转成为一个 function designator
所以 (**fptr) 要拆成两个部分: (* ( *fptr) ), 里面的 *fptr 是个 function designator, 但是还是会被转成一个 pointer to function,我们可注记为 fptr。
又,外面又有一个 * operator, ( *fptr’ 是個 function designator, 最后还是会被转化为 pointer to function
但是,0x400526 这个数值是怎么来的呢?
我们可以使用以下指令观察:
$ gdb -o fp -g fp.c ` & ` objdump -d fp `
参考输出里面的一段:
0000000000400526 <test>:
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
40052a: 90 nop
40052b: 5d pop %rbp
40052c: c3 retq
这个数值其实是可执行文件反编译后的函数入口,但是这个数值并非是在实际内存中的数值,而是虚拟存储器的地址。
由于puts 的 function prototype 是 int puts(const char *s),因此每次经过 * operator 运算后得到的结果是仍然是 int。所以,* 的数目不會影响结果。最后return 的值是根据 s 的长度加上 ’\n’。而这个例子 return 给 main 的值是 6。
Address and indirection operators
对应到 C99/C11 规范 [ 6.5.3.2 ],& 所能操作的 operand 只能是:
- function designator - 基本上就是 function name
- [] or * 的操作结果:跟这两个作用时,基本上就是相消
* - operand 本身
[] - & 会消失,而 [] 会被转换成只剩 + (注:原本 [] 会是 + 搭配 *)
例如: &(a[5]) == a + 5
- 一个指向非 bit-field or register storage-class specifier 的 object 的 lvalue
bit-field:一种在 struct 或 union 中使用用来节省内存空间的object;
特別的用途:沒有名称的 bit-field 可以做为padding
除了遇到 [] 或 * 外,使用 & 的结果基本上都是得到 pointer to the object 或是 function 的 address
考虑以下程序:
char str[123];
为何 str == &str 呢?
- 实际上左右两边的类型是不一样的,只是值相同。
- 左边的是 pointer to char:char *
规范中表示:除非遇到 sizeof 或是 & 之外,array of type (在这就是指 str) 都会被直接解释成 pointer to type (在這就是 pointer to char),而这个type 是根根据 array 的第一個元素来
Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. (C99 6.3.2.1)
- 右边的则是 pointer to an array: char (*)[123]
上面提到:遇到 & 时,str 不会被解读为pointer to type,而是作为原本的 object,在这就是 array object,而 address of array object 也就是这个 array object 的起始地址,当然也就会跟第一个元素的地址相同
除了用值相同來解释外,规范在提到 equality operators 时,也有说到类似情景
Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function (C99 6.5.9)
针对指针的修饰(qualifier)
- 指针本身不可变更 (Constant pointer to variable): const 在 * 之后
char * const pContent;
- 指针所指向的內容不可变更 (Pointer to constant): const 在 * 之前
const char * pContent;
char const * pContent;
- 两者都不可变更
const char * const pContent;
Linus Torvalds 亲自教你 C 語言
- array argument 的正确使用时机
Are there any cases of multi-dimensional arrays? Because those actually have semantic meaning outside of sizeof(), just in things like adding offsets. Eg something like
int fn(int a[][10])
- ends up being equivalent to something like
int fn(int (*a)[10])
and "a+1" is actually 40 bytes ahead of "a", so it does not act like an "int *". (And I might have screwed that up mightily - C multidimensional arrays and the conversions to pointers are really easy to get confused about. Which is why I hope we don’t have them)
- 艺术与核心
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
`char *`