C语言指针加1的原理,真的很难吗

2023-05-16

问题背景

最近有小伙伴对于C语言中指针的运算有点疑问:指针变量加1之后,到底向后偏移了几个字节呢?示例代码如下,这段代码运行在32位CPU平台上:

#include<stdio.h>

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

int main()
{
	char buffer[512] = {0};
	struct tree *t_ptr = NULL;
	char *t_ptr_new = NULL;
	char *tmp_ptr = 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的哪个位置?

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

结构体

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[3];

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

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

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

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

理解指针

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

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

示例代码中,

struct tree *t_ptr = NULL;

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

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

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

同理,示例代码中,t_ptr_newtm_ptr为指向char类型的指针,并赋初始值NULL。

NULL指针

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

数组的名字

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

char buffer[512];

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

指针转换

通过类型转换,可以将指针从一种类型转换为另一种形式,改变的只是它的类型,值是不会改变的。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);

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

指针运算

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字节)。

综上分析

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

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]。

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

t_ptr_new point to buffer[9]

扫码关注,获取更多精彩内容

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言指针加1的原理,真的很难吗 的相关文章

  • Python爬虫(4)获得所有Top250部电影的信息并存入数据库

    上次我们完成了单页电影的获取并保存到了Excel文件中 xff0c 不知道小伙伴们都完成了没 xff1f 有没有把Top250部电影都保存下来的 xff1f 在编写这些代码过程中遇到什么问题了没 xff1f 如果遇到但是没有解决 xff0c
  • c#笔记-模式匹配

    模式匹配 模式匹配可以判断一个值的类型和内容 可以判断嵌套的属性 xff0c 但只能和常量进行比较 模式匹配使用is表达式 xff0c 或是在switch选择 xff0c 和switch表达式的分支块中启用 模式匹配使用专有的关键字或运算符
  • typeScript+egg.js+node.js后台项目搭建(一)

    typeScript egg js node js后台项目搭建 一 1 安装node js 地址 https nodejs org en 下载安装后 打开控制台cmd 输入 node v 在安装ts 可以参考typeScript中文官网 n
  • CGroup 介绍、应用实例及原理描述(已发表于IBM开发者论坛)

    插播小广告 xff0c 本人的 大话 Java性能优化 一书已经在亚马逊 京东 当当 天猫出售 xff0c 提前谢谢大家支持 原文请查看 xff1a http www ibm com developerworks cn linux 1506
  • python + celery简例

    在网上找了半天 xff0c 也没找到完整的例子 xff0c 自己写吧 1 一个队列 自定义10个优先级 xff0c 修改默认celery队列名称 1 testcelery py from celery import Celery impor
  • java+selenium获取动态下拉列表元素

    做自动化的时候 xff0c 遇到这么一个闹心问题 xff1a 研发用html里的 lt div input gt 方式 xff0c 所以无法使用select获取列表元素 原本使用Robot也可以定位 xff0c 但是headless模式 x
  • Redis安装和配置

    网上有海量的Redis文章 xff0c 写的都很详细 这里就是简单记录一下自己查aof问题过程中遇到的问题 xff0c 主要是aof文件所在目录在redis conf里的位置 1 在ubuntu16上安装Redis sudo apt get
  • mysql 主从部署

    在ubuntu 16上 xff0c 配置mysql 主从服务器 查看mysql主从命令 show variables like 39 server id 39 show variables like 39 log bin 39 show m
  • 编写的windows程序,崩溃时产生crash dump文件的办法 .

    一 引言 dump文件是C 43 43 程序发生异常时 xff0c 保存当时程序运行状态的文件 xff0c 是调试异常程序重要的方法 xff0c 所以程序崩溃时 xff0c 除了日志文件 xff0c dump文件便成了我们查找错误的最后一根
  • 网络性能测试工具iperf详细使用图文教程

    Iperf是一个网络性能测试工具 Iperf可以测试TCP和UDP带宽质量 Iperf可以测量最大TCP带宽 xff0c 具有多种参数和UDP特性 Iperf可以报告带宽 xff0c 延迟抖动和数据包丢失 利用Iperf这一特性 xff0c
  • 使用Klockwork进行代码分析简单操作流程

    前一段时间公司试用了一下klockwork公司的klockwork代码静态分析软件 xff0c 我所在项目组进行了试点 xff0c 试用后感觉不错 xff0c 有几大亮点 xff1a 1 xff09 对代码进行静态分析 xff0c 无需改动
  • C++特性:多态、重写

    说一下多态 多态就是不同的继承类对象 xff0c 针对同一消息做出不同的响应 xff0c 父类的指针指向或者绑定到子类的对象 xff0c 使得父类指针呈现多种不同的表现方式 要实现多态 xff0c 首先父类需要有一个virtual修饰的虚方
  • Jmeter(三)-简单的HTTP请求(参数化)

    xfeff xfeff 首先建立一个线程组 xff08 Thread Group xff09 xff0c 为什么所有的请求都要加入线程组这个组件呢 xff1f 不加不行吗 xff1f 答案当然是不行的 因为jmeter的所有任务都必须由线程
  • no suitable driver found for jdbc:mysql//localhost:3306/..

    xfeff xfeff 出现这样的情况 xff0c 一般有四种原因 xff1a 一 xff1a 连接URL格式出现了问题 Connection conn 61 DriverManager getConnection 34 jdbc mysq
  • java模拟键盘按键

    xfeff xfeff come from http bbs 51cto com thread 1097189 1 html 功能描述 1 打开一个记事本 2 最大化 3 模拟按键操作 现 贴出 源码 xff1a 预览源代码 打印 001i
  • Android手机通过USB数据线共享Linux电脑网络

    这里要讲述的是手机通过usb数据线共享电脑 xff08 linux系统 xff09 的网络来自由上网 通过USB数据线将手机与电脑相连 再分别在电脑和手机上虚拟出一个网络接口用于网络通信 这很类似于VPN与虚拟机上网的原理 好处是不论台式还
  • CentOS时间的查看与修改

    http www centoscn com CentOS help 2014 0805 3430 html 1 查看 修改Linux时区与时间 一 linux时区的查看与修改 1 xff0c 查看当前时区 date R 2 xff0c 修改
  • 关于"undefined reference to" 问题的原因和解决办法

    最近在Linux下编程发现一个诡异的现象 xff0c 就是在链接一个静态库的时候总是报错 xff0c 类似下面这样的错误 xff1a text 43 0x13 undefined reference to 96 func 39 关于unde
  • 跟南桑学汇编

    1 语言 1 1 机器语言 人和人沟通的桥梁 xff1a 语言 人与计算机打交道 gt 学习计算机的语言 gt 什么是机器语言 1 2 汇编语言 这些复杂的机器语言的简化 gt 助记符 xff1a 汇编语言 gt 人能够理解的语言转换成为机
  • 深度剖析Josephus ring(约瑟夫环)C语言版

    深度剖析Josephus ring xff08 约瑟夫环 xff09 C语言版 鉴于C语言更适合展示算法的底层设计 xff0c 并且便于读者的研究与思考 xff0c 故而小编使用C语言来展示约瑟夫环的精巧与奥妙 Hello xff01 xf

随机推荐