百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术流 > 正文

你所不知道的C语言:指针篇(函数指针)

citgpt 2024-07-10 14:44 13 浏览 0 评论

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’’.

你所不知道的C语言:指针篇(函数指针)

=>> * 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 *`

相关推荐

js中arguments详解

一、简介了解arguments这个对象之前先来认识一下javascript的一些功能:其实Javascript并没有重载函数的功能,但是Arguments对象能够模拟重载。Javascrip中每个函数...

firewall-cmd 常用命令

目录firewalldzone说明firewallzone内容说明firewall-cmd常用参数firewall-cmd常用命令常用命令 回到顶部firewalldzone...

epel-release 是什么

EPEL-release(ExtraPackagesforEnterpriseLinux)是一个软件仓库,它为企业级Linux发行版(如CentOS、RHEL等)提供额外的软件包。以下是关于E...

FullGC详解  什么是 JVM 的 GC
FullGC详解 什么是 JVM 的 GC

前言:背景:一、什么是JVM的GC?JVM(JavaVirtualMachine)。JVM是Java程序的虚拟机,是一种实现Java语言的解...

2024-10-26 08:50 citgpt

使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
跨域(CrossOrigin)

1.介绍  1)跨域问题:跨域问题是在网络中,当一个网络的运行脚本(通常时JavaScript)试图访问另一个网络的资源时,如果这两个网络的端口、协议和域名不一致时就会出现跨域问题。    通俗讲...

微服务架构和分布式架构的区别

1、含义不同微服务架构:微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并...

深入理解与应用CSS clip-path 属性
深入理解与应用CSS clip-path 属性

clip-pathclip-path是什么clip-path 是一个CSS属性,允许开发者创建一个剪切区域,从而决定元素的哪些部分可见,哪些部分会被隐...

2024-10-25 11:51 citgpt

HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
Request.ServerVariables 大全

Request.ServerVariables("Url")返回服务器地址Request.ServerVariables("Path_Info")客户端提供的路...

python操作Kafka

目录一、python操作kafka1.python使用kafka生产者2.python使用kafka消费者3.使用docker中的kafka二、python操作kafka细...

Runtime.getRuntime().exec详解

Runtime.getRuntime().exec详解概述Runtime.getRuntime().exec用于调用外部可执行程序或系统命令,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。...

promise.all详解 promise.all是干什么的
promise.all详解 promise.all是干什么的

promise.all详解promise.all中所有的请求成功了,走.then(),在.then()中能得到一个数组,数组中是每个请求resolve抛出的结果...

2024-10-24 16:21 citgpt

Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解

取消回复欢迎 发表评论: