当前位置: 首页 > 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.dtcms.com/a/310498.html

相关文章:

  • 【Node.js安装注意事项】-安装路径不能有空格
  • Go 语言中 ​10 个高频实用写法
  • C语言:20250801学习(构造类型)
  • C++___快速入门(下)(引用)
  • Linux基础 -- 内核快速向用户态共享内核变量方案之ctl_table
  • 大模型学习思路推荐!
  • 基于K近邻的缺失值填补:原理、步骤与实战解析
  • Winform 中实现控件与数据的绑定,一方改变另一方同步改变。
  • 【Onvif从零实践】02、Onvif 测试工具(ONVIF Device Test Tool)的 安装、使用 教程
  • C++入门自学Day4-- c++类与对象(友元)
  • JavaScript语法树简介:AST/CST/词法/语法分析/ESTree/生成工具
  • 水果忍者经典版:离线版,永久无限制!!
  • IPD数字化的困难与解法
  • 如何在 VMware Workstation 虚拟机中利用 Nvidia 显卡的硬件加速功能
  • 利用 AI 在 iPhone 上实现 App 文本情绪价值评估(下)
  • 浅谈低代码平台涉及的一些技术选型
  • 【BUUCTF系列】[ACTF2020 新生赛]Exec 1
  • 用 Ubuntu 22.04 (Jammy) 的 MongoDB 源
  • Skia-如何渲染文本(上)
  • Android中页面生命周期变化
  • 多人命题系统
  • Qt 开发自动化测试框架搭建
  • 【Open3D】基础操作之三维变换
  • Nginx跨域问题与 MIME 类型错误深度排错指南:解决 MIME type of “application/octet-stream“ 报错
  • 【LeetCode刷题指南】--单值二叉树,相同的树
  • 《人形机器人的觉醒:技术革命与碳基未来》——类人关节设计:柔性驱动革命之液压人工肌肉
  • python中appium
  • 在PyCharm中将现有Gitee项目重新上传为全新项目
  • WordPress 前端显示英文,后台显示中文的设置
  • CH7216A USB Type C上的 DisplayPort 转 HDMI 2.0 转换器【CH7216A-BF】