第3章“程序的机器级表示”:理解指针

news/2025/3/1 5:48:44/

指针是 C 语言的一个重要特色。它们提供一种统一方式,能够远程访问数据结构。

指针基本的概念其实非常简单,下面的代码说明了许多这样的概念:

struct str { /* Example Structure */int t;char v;
};union uni { /* Example Union */int t;char v;
} u;int g = 15;void fun(int *xp)
{void (*f)(int*) = fun; /* f is a function pointer *//* Allocate structure on stack */struct str s= {1, 'a'}; /* Initialize structure *//* Allocate union from heap */union uni *up = (union uni *) malloc(sizeof(union uni));/* Locally declared array */int *ip[2] = {xp, &g};up->v = s.v + 1;printf("ip = %p, *ip = %p, **ip = %d\n", ip, *ip, **ip);printf("ip+1 = %p, ip[1] = %p, *ip[1] = %d\n", ip+1, ip[1], *ip[1]);printf("&s.v = %p, s.v = '%c'\n", &s.v, s.v);prinf("&up->v = %p, up->v = '%c'\n", &up->v, up->v);printf("f = %p\n", f);if (--(*xp) > 0) f(xp); /* Recursive call of fun */
}int test()
{int x = 2;fun(&x);return x;
}
  • 每个指针都有一个类型。这个类型表明指针指向的对象是哪一类的。示例代码中,看到如下的一些指针类型:
    在这里插入图片描述
    注意,该表中,既指明了指针本身的类型,也指出了它所指向的对象的类型。通常,如果对象类型为 T T T,则指针的类型为 T ∗ T* T。特殊的 void * 类型代表通用指针。例如,malloc 函数返回一个通用指针,然后它再被强制类型转换成一个有类型的指针(第21行)。

  • 每个指针都有一个值。这个值是某个指定类型的对象的地址。特殊的 NULL(0) 值表示该指针没有指向任何地方。

  • 指针是用 & 运算符创建的。这个运算符可应用到任何 lvalue 类的 C 表达式上,即可以出现在赋值语句左边的表达式,这样的例子包括变量以及结构、联合和数组的元素。示例代码中,看到这个操作符应用到全局变量 g 上(第24行),应用到结构元素 s.v 上(第32行),应用到联合元素 up->v 上(第33行),以及应用到局部变量 x 上(第42行)。

  • * 操作符用于指针的间接引用。其结果是一个值,它的类型与该指针的类型相关。看到间接引用应用到 ip*ip 上(第 29 行),应用到 ip[1] 上(第31行),以及应用到 xp 上(第 35 行)。此外,表达式 up->v(第 33 行)既间接引用了指针 up,同时还选取了域 v

  • 数组与指针是紧密联系的。可以引用一个数组的名字(但是不能修改),就好像它是一个指针变量一样。数组引用(如 a[3])与指针运算和间接引用(如 *(a+3)) 有一样的效果。可以在 29 行看到这一点,打印出数组 ip 的指针值,并用 *ip 引用它的第一项(元素0)。

  • 指针也可以指向函数。这提供了一个很强大的存储(storing)和传递代码引用的功能,这些代码可以被程序的某个其他部分调用。看看变量 f (第 15 行),它被声明为一个指向函数的变量,该函数以一个 int * 作为参数,并返回 void。赋值语句使 f 指向 fun。在后面使用 f(第 36 行)时,是在进行递归调用。

函数指针

函数指针声明的语法对于程序员新手是难以理解的。对于这样一个声明:

void (*f)(int *);

要从里(从 “f” 开始)往外读。因此,看到像 “(*f)” 表明的那样,f 是一个指针。像 “(*f)(int *)” 表明的那样,它是一个指针,指向一个以一个 int * 作为参数的函数。最后,它是一个指向一个以 int * 作为参数并返回 void 的函数的指针。

*f 两边的括号是必须的,因为否则声明

void *f(int *);

就要读成

(void *) f(int *);

也就是,它会被解释成一个函数原型,声明了一个函数f,它以一个 int * 作为参数并返回一个 void *

代码中包含很多对 prinf 的调用,打印出一些指针(用指令 %p)和值。在执行时,产生下面这样的输出:

在这里插入图片描述
可以看到,这个函数执行了两次——第一次是从 test 中直接调用(第 42 行),而第二次是间接的递归调用(第 36 行)。可以看出,打印出来的指针值都对应于地址。那些从 0xbfffef 开始的指针指向栈中的位置,而其他的是全局存储的一部分(0x804965c),或是可执行代码的一部分(0x8048414) ,或是堆中的位置(0x8049760 和 0x8049770)。

数组ip 被初始化了两次——每次调用 fun 都初始化一次。第二次的值(0xbfffef68)小于第一次的值(0xbfffefa8),这是因为栈是向下增长的。不过,数组的内容两次都是一样的。数组元素 0(*ip) 是一个指向 test 栈帧中变量 x x x 的指针,元素 1 是一个指向全局变量 g g g 的指针。

可以看到,结构 s s s 也被初始化了两次,两次都是在栈中,而变量 up 指向的联合是在堆中分配的。

最后,变量 f f f 是一个指向函数 fun 的指针。在反汇编代码中,看到如下 fun 的初始化代码:

在这里插入图片描述
打印出来的指针 f f f 的值 0x8048414 就是 fun 的代码中第一条指令的地址。

向函数传递参数

其他语言(如Pascal)提供两种方式来向过程传递参数——传值(by value)和引用(by reference)。

传值是指调用者提供实际的参数值,而引用是指调用者提供一个指向该值的指针。

在 C 中,所有的参数都是传值的,但是可以通过显式地产生一个指向一个值的指针,并把该指针传递给过程,从而实现了引用参数的效果。函数 fun(&x) 中的参数 xp 就是这样的。第一次调用 fuc(&x) 时(第 42 行),给了一个函数对 test 中局部变量 x x x 的引用。每次调用 fun 时,这个变量都会减小,从而在两次调用之后,递归会停止。


http://www.ppmy.cn/news/333576.html

相关文章

【开发系统平台架构】嵌入式硬件系统架构

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言一、开发平台二、传感器系统1.无人驾驶车传感器配置2.内部传感器3.外部传感器4.手持控制器三、微控制器系统1.一个系统为什么要分控制器和处理器两…

《安富莱嵌入式周报》第210期:2021.04.26--2021.05.02

往期周报汇总地址:http://www.armbbs.cn/forum.php?modforumdisplay&fid12&filtertypeid&typeid104 目录 1、 Microchip的M7内核航空航天级芯片再添新成员SAMRH707 ,抗宇宙辐射 2、美信的AI单片机MAX78000与Aizip合作提供最低功耗的物联网…

Supervised classification of slightly bruised peaches with respect to the time after bruising by usi

利用高光谱成像技术对轻度损伤桃子损伤后时间的监督分类 Abstract 桃子对机械冲击和压缩造成的瘀伤很敏感。受损伤的桃子在损伤发生后容易发酵、腐烂或发霉,并感染其他非损伤桃子。适度的擦伤会阻碍消费者的购买欲望,而不是价格。对于早期损伤的桃子&a…

spring boot火车票售卖系统毕业设计代码211004

springboot火车票售卖系统 摘 要 信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对火车票售卖系统等…

应变/温度数据采集系统设计方案——德维创(DEWETRON)

本文主要介绍了如何根据既定传感器类型和现场的实际工况,如何设计多通道同步高速采集系统。 一、测试系统功能及用途 xx通道应变数据采集系统主要完成整个试验现场xx系统应变、电压或振动等参数的采集和记录。数据采集系统主要由采集设备、计算机以及标定源部分等组…

基于深度学习的RGBD深度图补全算法文章鉴赏

点击上方“计算机视觉工坊”,选择“星标” 干货第一时间送达 【GiantPandaCV导语】本文针对3维视觉中的深度图补全问题,介绍了一下近年基于深度学习的RGB-D深度图补全算法进展。深度图的质量对3维视觉至关重要,深度图的优劣极大地影响了后续的…

vectorvn1610报价_公司产品 - Vector - 系统仿测 - 产品中心 - 北京品创联拓科技有限公司...

一、公司产品线 Vector的产品线包括: 1)网络及分布式系统工具 网络及分布式控制系统的设计、开发及测试工具 2)开放式网络工具和组件 基于CAN的开放式协议的软件及服务 3)测量和标定工具 电控单元(ECU)的测量和标定工具 4)诊断工具 汽车工业内的通用型过程导向诊断软件 5)…

visual studio 2022,ADO.NET 实体数据模型添加 sqlite数据库对象

文章目录 前言前期环境博客github 文档解析文件安装说明文件下载省流版nuget环境配置成功标志sqlite连接测试 前言 我们知道ADO.NET 实体数据模型特别适合动态开发数据库。因为ADO.NET可以使用DB First 开发 我们在开发一个程序的时候,经常会动态更新数据库字段&a…