C语言指针加1引发的思考

混说Linux 2023-01-19 10:00

1.  问题背景

最近有小伙伴对于 C 语言中指针的运算有点疑问:指针变量加 1 之后,到底向后偏移了几个字节呢?

示例代码如下,这段代码运行在32位CPU平台上:

#include

#pragma pack(1)
struct tree
{
  int height;
  int age;
  char tag;
};
#pragma pack() 

int main()
{
  char buffer[512];
  char *tmp_ptr = NULL;
  struct tree *t_ptr = NULL;
  char *t_ptr_new = NULL; 

  tmp_ptr = buffer;
  t_ptr = (struct tree *) tmp_ptr;
  t_ptr_new = (char *)(t_ptr + 1);

  printf("t_ptr_new point to buffer[%ld]\n", t_ptr_new - tmp_ptr);
  
  return 0;
}

请问,指针变量 t_ptr_new  指向数组 buffer 的哪个位置?

如果能快速得出答案,恭喜你,已经掌握指针算术运算的原理,以及结构体占用空间大小的计算方法。如果不能,也不要气馁,正好可以将这部分欠缺的知识补充上。下面,让我们来逐步揭开它的内幕。

2.  结构体

C 语言中 struct 声明创建一个数据类型(结构体),能将不同类型的对象聚合到一个对象中,用名字来引用结构体的各个组成部分。结构体的所有组成部分都存放在一段连续的内存中。指向结构的指针就是结构体第一个成员的地址。

示例中结构体类型定义:

#pragma pack(1)
struct tree
{
 int height;
 int age;
 char tag;
};
#pragma pack()

结构体内部有三个成员变量,其中两个为 int 型,一个 char 型。编译器按照成员列表顺序挨个给每个成员分配内存。此结构体占用的内存空间是多少个字节呢?

height 和 age 各占用4个字节,tag 占用 1 个字节。那结构体占用的空间就是 9 个字节呗。是这样吗?

让我们先来了解一个概念:数据对齐

数据对齐

许多计算机系统对基本的数据类型的合法地址做了一些限制。要求某种类型对象的地址必须是某个值(通常为2、4、8)的倍数。对齐原则是:任何占用 K 字节空间大小的基本对象,其地址必须是 K 的倍数。

由此,编译器可能需要在结构体成员内存的分配中插入间隙,保证每个结构成员都满足它的对齐要求。或者需要在结构体的末尾加入填充,从而使得结构体数组中的每个元素都会满足它的对齐要求。

本例中,结构体的首地址满足 4 字节对齐(第一个成员类型为 int)要求后,height、age、tag 三个成员均满足对齐原则。不过要考虑下面的声明:

Struct tree a[4];

如果分配 9 个字节,就不能满足数组 a 的每个元素的对齐要求。

假设数组的起始地址为 x,则每个元素的地址分别为 x、x+9、x+18、x+27,有三个元素不满足对齐原则。由此,编译器会为结构 tree 分配 12 个字节,最后 3 个字节是补充的空间(浪费的空间)。

pragma pack()

注意编译指令,#pragma pack(1) 和 #pragma pack()

pragma pack 的主要作用就是改变编译器的内存对齐方式。

在不使用这条指令的情况下,编译器采取默认方式对齐。这两条编译预处理指令,使得在这之间定义的结构体按照 1 字节方式对齐。在本例中,使用这两条指令的效果是,编译器不会在结构体尾部填充空间了

结构体大小

最终,这个结构体占用的内存空间大小为 9 个字节。

3.  理解指针

指针定义

每个指针都对应一个类型。这个类型表明该指针指向的是哪一类对象。指针的类型不是机器码中的一部分,而是C语言提供的一种抽象,帮助程序员避免寻址错误。

每个指针都有一个值。这个值是某个指定类型的对象的地址。

示例代码中

struct tree *t_ptr = NULL;

这语句是什么意思呢?其含义为:定义一个指针变量 t_ptr 并赋予了初值 NULL。

详细解释:星号 “*” 说明标识符 t_ptr为 “一个指向…的指针”;struct tree 为类型说明符;可知,t_ptr 为指向结构体 tree 类型的指针。

指针的类型由指向对象的数据类型和星号 “*” 组合起来表示。例如,指针 t_ptr 的指针类型为 “struct tree *”。

示例代码中,t_ptr_new 和 tm_ptr 为指向 char 类型的指针,并赋初始值NULL。

NULL 指针

C语言标准中定义了 NULL 指针,作为一种特殊的指针变量,其指向的内容为空(即不指向任何东西)。将其赋值给某个指针变量,表示该指针目前并未指向任何东西。

数组的名字

一个数组的名字也是一种指针,但这个指针的值是不能改变的。这种指针永远指向数组中的第一个元素,其指向的类型为数组元素的数据类型。

示例代码:

char buffer[512];

数组名字 buffer 为指向 char 数据类型的指针,它指向数组的首个元素 buffer[0]。

4.  指针转换

通过类型转换,可以将指针从一种类型转换为另一种形式,改变的只是它的类型,值是不会改变的。

C语言中的类型转换有两种:隐式类型转换和强制类型转换。

示例代码:

t_ptr_new = (char *)(t_ptr + 1);

通过 “(char *)” 强制将 struct tree * 类型的指针转换为 char * 类型,并将其赋值给一个 char * 类型的指针。如果去掉 “(char *)”,在编译过程中,编译器会根据 “=” 左侧变量的类型自动进行转换,但会产生告警信息。告警信息如下:

example.c: In function ‘main’:
example.c:21:12: warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]
t_ptr_new = (t_ptr + 1);

本例中用强制类型转换,一方面是为了消除编译过程产生的警告,另一方面是为了使程序便于理解。

5.  指针运算

C语言的指针运算有两种形式

第一种:指针 ± 整数

这种计算出来的值,会根据该指针指向的某种数据类型的大小进行伸缩。例如,指针的值为 x,指向的数据类型大小为 L,整数为 n,则计算出来的结果值为 x + n * L

示例代码,

t_ptr_new = (char *)(t_ptr + 1);

此表达式等价于(a_ptr 符号在此处是为了便于理解而添加):

a_ptr = (t_ptr + 1);
t_ptr_new = (char *)a_ptr;

指针 t_ptr 加 1(t_ptr + 1)的结果,会根据数据类型 struct tree 的大小进行增加。假设指针 t_ptr 的值为 x(即地址值为 x),而结构体类型 tree 的大小为 9 字节,则  t_ptr + 1  的值为 x+9。然后,将此结果进行强制类型转换后,赋值给指针变量 t_ptr_new。

第二种:指针 – 指针

只有当两个指针都指向同一个数组中的元素时,计算才有意义。

减法运算的值是两个指针在内存中的距离(等于两个地址之差除以该元素数据类型的大小)。两个指针相减的结果的类型是 ptrdiff_t,它是一种有符号整数类型。

如果两个指针值(地址值)的差值为 12 字节,每个元素占用 4 个字节,则两个指针相减得到的结果将是 3(两个指针的差值 12 将除以每个元素的长度 4)。

示例代码

printf("t_ptr_new point to buffer[%ld]\n", t_ptr_new - tmp_ptr);

由以上分析,两个指针相减(t_ptr_new - tmp_ptr),地址差值为 9 字节,而数组中每个元素的大小为 1 字节(char类型数据),则指针相减得到结果为 9(9字节/1字节)。

6.   综上分析

有了以上分析的基础,让我们看看最终答案是如何得出的。

tmp_ptr = buffer;

tmp_ptr 指针指向数组 buffer 的第 0 个元素,即 buffer[0]。

t_ptr = (struct tree *) tmp_ptr;

将指针tmp_ptr强制转换为  struct tree *  类型的指针后,赋值给指针变量  t_ptr。

t_ptr_new = (char *)(t_ptr + 1);

这个表达式是问题的关键。t_ptr + 1 运算得到的结果指针指向下一个结构体 tree 元素,而结构体占用的空间大小为9个字节,因此指针加 1 后,实际偏移了 9 个字节。经过强制类型转换后,赋值给指针 t_ptr_new。

printf("t_ptr_new point to buffer[%ld]\n", t_ptr_new - tmp_ptr);

t_ptr_new - tmp_ptr 运算得到结果是 9。由于 tmp_ptr 指向数组的第 0 个元素buffer[0],则 t_ptr_new 指向数组的第 9 个元素buffer[9]。

最终答案

指针加 1 后,偏移 9 个字节;t_ptr_new指向buffer数组的第 9 个元素。打印输出结果如下:

t_ptr_new point to buffer[9]





往期推荐

又度过了蠢货的一年!

C语言令人抓狂的一面——全局变量

看完还不会指针,锤自己!

EEPROM 和 flash 这样讲,早就懂了!

来都来了,点个 在看 再走吧~~~



混说Linux 百度研发工程师,分享Linux干货,和大家一起学习!
评论
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 173浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 45浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 170浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 75浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 145浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 80浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 119浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 127浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 104浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 68浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 55浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 87浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 85浏览
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 100浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 180浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦