当前位置: 首页 > news >正文

1、【C语言】【进阶】数组,指针与退化

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除

背景

前两天逛博客,看到一篇描述 C 语言指针的文章
【C语言进阶】指针面试题详解(2)
里面的题目还蛮有意思,简单评论了两句,发现自己的想法也有某些漏洞,这里展开巩固一下

题目

题目如下:

#include <stdio.h>int main(void) {int a[5] = {1, 2,3, 4, 5};int* ptr = (int*)(&a + 1);printf("%d, %d\n",*(a + 1), *(ptr - 1));return 0;
}

数组,指针和数组指针

在分析题目之前,先额外看下 a 和 &a 的区别,这是一个非常经典的 C 语言问题,涉及到数组和指针的本质区别

#include <stdio.h>int main(void) {int a[5] = {1, 2, 3, 4, 5};printf("a: %p\n&a:%p\n", a, &a);return 0;
}

从类型上看:

  • a 是一个数组名,在大多数表达式中(除了 sizeof(a)、&a 等情况),a 会自动退化为指向数组第一个元素的指针,此时 a 被当作是 int* 类型,指向 a[0]
  • 而 &a 是对整个数组取地址,它的类型是 int (*)[5],即指向一个包含 5 个 int 的数组的指针

虽然它们的类型不同,但它们的数值地址是相同的,因为不管是指向第一个元素,还是指向一个包含数组的指针,这个数组和数组中的第一个元素的起始地址是一样的

编译后运行,可以看到两个数值地址是一样的
在这里插入图片描述

再做一个变体,打印它们的类型

#include <stdio.h>int main(void) {int a[5] = {1, 2, 3, 4, 5};printf("a: %ld\n&a:%ld\n", sizeof(a), sizeof(&a));return 0;
}

编译后运行

在这里插入图片描述
可以看到,sizeof(a) 占了 20 个字节,这里的环境为 x86_64,int 类型大小为 4 字节,4 * 5 = 20 字节,需注意,这里 sizeof(a) 中的 a 并没有退化成指针,而 &a 很明确,它就是一个指针,而且是指向一个包含数组的指针,这里简称数组指针,指针类型在 x86_64 环境中占 8 个字节,所以这里 sizeof(&a) 占了 8 个字节

C 标准中的定义

关于大多数情况下, a 退化成指针,而 sizeof(a),&a 中的 a 仍保留数组属性,这一点在 C 标准中是有规定的,C11 标准 ISO/IEC 9899:201x 中对这里的描述如下
在这里插入图片描述
有几个关键点:

  • 大多数情况下,如果一个变量是数组类型(比如上面的例子 int a[5]),这个数组类型 a 会被自动退化成指向这个数组中第一个元素的指针,比如 int a[5] 中的 a,在表达式中会变成 &a[0],也就是一个 int* 类型的指针
  • 但也有例外情况,比如在 sizeof,_Alignof,&,或者字符串数组初始化的时候,此时这个数组类型不会退化成指针
  • 数组类型退化成指针后,不能出现在赋值语句的左边,比如不能写 a = b; 这样的赋值语句,数组也不能这样直接赋值

这就解释了上面那些现象,下面再额外讲下字符串数组初始化的这种情况

举个例子,比如下面这样的一个字符串数组

char str[] = "hello";

在这个例子中,str 是一个数组类型,而不是指针,在它初始化时,数组类型 str 不会退化成指针,而是作为数组完整地接收了字符串常量 hello 的内容,和上面这个例子类似的,比如还有下面这样的结构体类型,int l类型等等。

typedef struct {int a;int b;
} MyStruct;MyStrcut myStruct[] = {{1,2},{3,4}};
int data[] = {1, 2, 3, 4, 5};

当然如果是下面这样的就不行了,下面这种是指针赋值

char* str = "hello";

回归题目

有了上面的基础,再回过头看这个题目

#include <stdio.h>int main(void) {int a[5] = {1, 2, 3, 4, 5};int* ptr = (int*)(&a + 1);printf("%d, %d\n",*(a + 1), *(ptr - 1));return 0;
}
  • 很明显,&a 作为数组类型,&a + 1 这里的 1 代表的是一个 int (*)[5] 的数组类型大小,所以 ptr 指向的是 a 所在内存地址往后 5 个 int 类型大小的地址,也就是 &a[4] 后面的那个位置
  • 而 a 作为 int 类型的指针,指向了数组中的第一个元素,a + 1 这里的 1 代表的是一个int 类型大小,所以 *(a + 1),取的是数组中第二个元素的值
  • 同样,ptr 作为 int 类型的指针,指向了数组中最后一个元素后面的地址,ptr - 1 这里的 1 同样代表的是一个 int 类型大小,所以 *(ptr - 1) 取的是数组中最后一个元素的值

今天就到这里,这个系列下篇 blog 继续探索 C 语言

http://www.lryc.cn/news/606836.html

相关文章:

  • 函数fdopendir的用法
  • [vue3 echarts] echarts 动态数据更新 setInterval
  • 深度学习(鱼书)day08--误差反向传播(后三节)
  • 轻钢屋顶电动排烟窗(工业用)
  • ansible.cfg 配置文件的常见配置项及其说明
  • SQL中的HAVING用法
  • MySQL--组从复制的详解及功能演练
  • 从O(n²)到O(n log n):深度剖析快速排序的内存优化与cache-friendly实现
  • 高级11-Java日志管理:使用Log4j与SLF4J
  • Oracle EBS 缺少adcfgclone.pl文件
  • 电商前端Nginx访问日志收集分析实战
  • 汇川ITS7100E触摸屏交互界面开发(一)调试事项说明
  • 25电赛e题杂乱环境稳定识别矩形框(附源码)
  • Vue3 Vue3中的响应式原理
  • StarRocks vs. Trino
  • 九联UNT403HS_海思MV320处理器_安卓9-优盘强刷刷机包
  • 嵌入式 Linux 深度解析:架构、原理与工程实践(增强版)
  • 企业级LLM智能引擎 的完整解决方案,整合了 SpringAI框架、RAG技术、模型控制平台(MCP)和实时搜索,提供从架构设计到代码实现的全面指南:
  • cloudflare worker + Cloudflare AI Gateway
  • 如何在不依赖 Office 的情况下转换 PDF 为可编辑文档
  • python中appium
  • K8S周期性备份etcd数据实战案例
  • 精通分类:解析Scikit-learn中的KNN、朴素贝叶斯与决策树(含随机森林)
  • 应用药品注册证识别技术,为医药行业的合规、高效与创新发展提供核心驱动力
  • 智能图书馆管理系统开发实战系列(四):后端C++ DLL开发与模块化设计
  • Dify版本升级实操
  • 体育直播系统搭建:核心数据详细接入指南
  • 网络编程-加密算法
  • CCleaner是否被过誉了?C盘满了怎么办?用ccleaner清除C盘垃圾,3款电脑系统磁盘清理和优化软件
  • 应用Builder模式在C++中进行复杂对象构建