深入理解C/C++数组和指针

2023-05-16

版权所有,转载请注明出处,谢谢!
http://blog.csdn.net/walkinginthewind/article/details/7044380

C语言中数组和指针是一种很特别的关系,首先本质上肯定是不同的,本文从各个角度论述数组和指针。

一、数组与指针的关系
数组和指针是两种不同的类型,数组具有确定数量的元素,而指针只是一个标量值。数组可以在某些情况下转换为指针,当数组名在表达式中使用时,编译器会把数组名转换为一个指针常量,是数组中的第一个元素的地址,类型就是数组元素的地址类型,如:
int a[5]={0,1,2,3,4}; 
数组名a若出现在表达式中,如int *p=a;那么它就转换为第一个元素的地址,等价于int *p=&a[0];
再来一个:
int aa[2][5]={0,1,2,3,4,   
                      5,6,7,8,9};
数组名aa若出现在表达式中,如int (*p)[5]=aa;那么它就转换为第一个元素的地址,等价于int (*p)[5]=&aa[0]; 
但是int (*p)[5]=aa[0]; 这个就不对了,根据规则我们推一下就很明了了,aa[0]的类型是int [5],是一个元素数量为5的整型数组,就算转化,那么转化成的是数组(int [5])中第一个元素的地址&aa[0][0],类型是 int *。所以,要么是int (*p)[5]=aa;要么是int (*p)[5]=&aa[0];
只有在两种场合下,数组名并不用指针常量来表示--就是当数组名作为sizeof操作符或单目操作符&的操作数时,sizeof返回整个数组的长度,使用的是它的类型信息,而不是地址信息,不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是指向某个指针常量值的指针。
如对数组a,&a表示的是指向数组a的指针,类型是int (*) [5],所以int *p=&a;是不对的,因为右边是一个整形数组的指针int (*)[5],而p是一个整形指针int *;
数组的sizeof问题会在下面中仔细讨论。
二、数组与指针的下标引用
int a[5]={0,1,2,3,4}; 
如a[3],用下标来访问数组a中的第三个元素,那么下标的本质是什么?本质就是这样的一个表达式:*(a+3),当然表达式中必须含有有效的数组名或指针变量。
其实a[3]和3[a]是等价的,因为他们被翻译成相同的表达式(顶多顺序不同而已),都是访问的数组a中的元素3。
指针当然也能用下标的形式了,如:int *p=a; 那么p[3]就是*(p+3),等同于3[p](不要邪恶。。。3P,3P),同样访问数组a中的元素3。
根据这一规则,我们还能写出更奇怪的表达式,如:
int aa[2][5]={0,1,2,3,4,
                      5,6,7,8,9};
1[aa][2],这个看起来很别扭,首先 1[aa],就是*(1+aa),那么1[aa][2]就是*(*(1+aa)+2),也就是aa[1][2]。
1[2][aa],这个就不对了,因为前半部分1[2]是不符合要求的。
当然在实际中使用这样的表达式是没有意义的,除非就是不想让人很容易的看懂你的代码。
三、数组与指针的定义和声明
数组和指针的定义与声明必须保持一致,不能一个地方定义的是数组,然后再另一个地方声明为指针。
首先我们解释一下数组名的下标引用和指针的下标应用,它们是不完全相同的,从访问的方式来讲。
int a[5]={0,1,2,3,4};
int *p=a;
对于a[3]和p[3]都会解析成*(a+3)和*(p+3),但是实质是不一样的。
首先对于a[3],也就是*(a+3):
(1)把数组名a代表的数组首地址和3相加,得到要访问数据的地址,这里注意,数组名a直接被编译成数组的首地址;
(2)访问这个地址,取出数据。
对于p[3],也就是*(p+3):
(1)从p代表的地址单元里取出内容,也就是数组首地址,指针名p代表的是指针变量的存储地址,变量的地址单元里存放的才是数组的首地址;
(2)把取出的数组首地址和3相加,得到要访问的数据的地址;
(3)访问这个地址,取出数据。
下面给出一个例子来说明若定义和声明不一致带来的问题:
设test1.cpp中有如下定义:
char s[]="abcdefg";
test2.cpp中有如下声明:
extern char *s;
显然编译是没有问题的。
那么在test2.cpp中引用s[i]结果怎样呢?如s[3],是‘d’吗?好像是吧
下面我们对test2.cpp中的s[3]进行分析:
s的地址当然是由test1.cpp中的定义决定了,因为在定义时才分配内存空间的;
我们根据上面给出的指针下标引用的步骤进行计算
(1)从s代表的地址单元的取出内容(4个字节),这里实际上是数组s中的前4个元素,这个值是“abcd”,也就是16进制64636261h,到这一步应该就能看出来问题了;
(2)然后把取出的首地址和3相加,得到要访问的数据的地址64636261h+3,这个地址是未分配未定义的;
(3)取地址64636261h+3的内容,这个地址单元是未定义的,访问就会出错。

下面给出分析的代码(可只需观察有注释的部分):

#include<iostream>         
using namespace std;
extern void test();
char s[]="abcdefg";
int main()
{
002E13A0  push        ebp  
002E13A1  mov         ebp,esp  
002E13A3  sub         esp,0D8h  
002E13A9  push        ebx  
002E13AA  push        esi  
002E13AB  push        edi  
002E13AC  lea         edi,[ebp+FFFFFF28h]  
002E13B2  mov         ecx,36h  
002E13B7  mov         eax,0CCCCCCCCh  
002E13BC  rep stos    dword ptr es:[edi]  
	char ch;
	int i=3;
002E13BE  mov         dword ptr [ebp-14h],3  
	ch = s[i];
002E13C5  mov         eax,dword ptr [ebp-14h]  
002E13C8  mov         cl,byte ptr [eax+011F7000h]  /* s直接翻译成数组首地址和i(eax)相加,得到操作数地址,然后作为byte ptr类型取内容,传给cl */
002E13CE  mov         byte ptr [ebp-5],cl          /* cl的内容传给ch(ebp-5) */
	test();
002E13D1  call        002E1073  
	return 0;
002E13D6  xor         eax,eax  
}
002E13D8  pop         edi  
002E13D9  pop         esi  
002E13DA  pop         ebx  
002E13DB  add         esp,0D8h  
002E13E1  cmp         ebp,esp  
002E13E3  call        002E113B  
002E13E8  mov         esp,ebp  
002E13EA  pop         ebp  
002E13EB  ret  

test2.cpp // 运行错误

extern char *s;
void test()
{
011F1470  push        ebp  
011F1471  mov         ebp,esp  
011F1473  sub         esp,0D8h  
011F1479  push        ebx  
011F147A  push        esi  
011F147B  push        edi  
011F147C  lea         edi,[ebp+FFFFFF28h]  
011F1482  mov         ecx,36h  
011F1487  mov         eax,0CCCCCCCCh  
011F148C  rep stos    dword ptr es:[edi]  
	char ch;
	int i=3;
011F148E  mov         dword ptr [ebp-14h],3  
	ch=s[i];
011F1495  mov         eax,dword ptr ds:[011F7000h]  /* ds没有影响,因为windows中所有的段基址都为0,取011F7000h单元的内容,这里是数组中前四个字节(指针是四个字节)组成的整数,也就是64636261h,也就是这里,把s所指的单元计算成了64636261h */
011F149A  add         eax,dword ptr [ebp-14h]       /* 然后把地址和i相加,也就是64636261h+3,这个地址是未分配定义的,访问当然会出错 */ 
011F149D  mov         cl,byte ptr [eax]             /* 访问错误 */
011F149F  mov         byte ptr [ebp-5],cl  
	return;
}
011F14A2  pop         edi  
011F14A3  pop         esi  
011F14A4  pop         ebx  
011F14A5  mov         esp,ebp  
011F14A7  pop         ebp  
011F14A8  ret  
若test2.cpp中这样声明:
extern char s[];
这样就正确了,因为声明和定义一致,访问就没问题了。
所以千万不要简单的认为数组名与指针是一样的,否则会吃大亏,数组的定义和声明千万要保持一致性。
四、数组和指针的sizeof问题
数组的sizeof就是数组的元素个数*元素大小,而指针的sizeof全都是一样,都是地址类型,32位机器是4个字节。
下面给出一些例子:
测试程序:
#include<iostream>                        
using namespace std;
int main()
{
	int a[6][8]={0};
	int (*p)[8];
	p=&a[0];    
	int (*pp)[6][8]; 
	pp=&a;

	cout<<sizeof(a)<<endl;        // 192
	cout<<sizeof(*a)<<endl;       // 32
	cout<<sizeof(&a)<<endl;       // 4
	cout<<sizeof(a[0])<<endl;     // 32
	cout<<sizeof(*a[0])<<endl;    // 4
	cout<<sizeof(&a[0])<<endl;    // 4
	cout<<sizeof(a[0][0])<<endl;  // 4
	cout<<sizeof(&a[0][0])<<endl; // 4
	cout<<sizeof(p)<<endl;        // 4
	cout<<sizeof(*p)<<endl;       // 32
	cout<<sizeof(&p)<<endl;       // 4
	cout<<sizeof(pp)<<endl;       // 4
	cout<<sizeof(*pp)<<endl;      // 192
	cout<<sizeof(&pp)<<endl;      // 4 

	system("pause");
	return 0;
}
VS2010在32位windows7下的运行结果(VC6.0不符合标准):
192
32
4
32
4
4
4
4
4
32
4
4
192
4

下面对程序做逐一简单的解释:
(1) sizeof(a); a的定义为int a[6][8],类型是int [6][8],即元素个数为6*8的二维int型数组,它的大小就是6*8*sizeof(int),这里是192;
(2) sizeof(*a); *a这个表达式中数组名a被转换为指针,即数组第一个元素a[0]的地址,'*'得到这个地址所指的对象,也就是a[0],总的来说*a等价于*(&a[0]),a[0]的类型int [8],即大小为8的一维int型数组,它的大小就是8*sizeof(int),这里是32;
(3) sizeof(&a); '&'取a的地址,类型是int (*)[6][8],地址类型,这里大小是4;
(4) sizeof(a[0]); a[0]的类型int [8],即大小为8的一维int型数组,它的大小就是8*sizeof(int),这里是32;
(5) sizeof(*a[0]); *a[0]这个表达式中数组名a[0]被转换为指针,即数组的第一个元素a[0][0]的地址,'*'得到这个地址所指的元素,也就是a[0][0],总的来说*a[0]等价于*(&a[0][0]),a[0][0]的类型是int,它的大小就是sizeof(int),这里是4;
(6) sizeof(&a[0]); '&'取a[0]的地址,类型是int (*)[8],地址类型,这里大小是4;
(7) sizeof(a[0][0]); a[0][0]的类型是int,它的大小就是sizeof(int),这里是4;
(8) sizeof(&a[0][0]); '&'取a[0][0]的地址,类型是int *,地址类型,这里大小是4;
(9) sizeof(p); p的类型是int (*)[8],指向一个元素个数为8的int型数组,地址类型,这里大小是4;
(10)sizeof(*p); *p取得p所指的元素,类型是int [8],大小为8*sizeof(int),这里是32;
(11)sizeof(&p); '&'取p的地址,类型是int (**) [8],地址类型,这里大小是4;
(12)sizeof(pp); pp的类型是int (*)[6][8],指向一个大小为6*8的二维int型数组,地址类型,这里大小为4,
(13)sizeof(*pp); *pp取得pp所指的对象,类型是int [6][8],即元素个数为6*8的二维int型数组,它的大小就是6*8*sizeof(int),这里是192;
(14)sizeof(&pp); '&'取pp的地址,类型是int (**)[6][8],地址类型,这里大小是4;
五、数组作为函数参数
当数组作为函数参数传入时,数组退化为指针,类型是第一个元素的地址类型。“数组名被改写成一个指针参数”,这个规则并不是递归定义的。数组的数组会被改写为“数组的指针”,而不是“指针的指针”。
下面给出几个例子:
fun1(char s[10])
{
// s在函数内部实际的类型是char *;
}

fun2(char s[][10])
{
// s在函数内部的实际类型是char(*) [10],即char [10]数组的指针;
}

fun3(char *s[15])
{
// s在函数内部的实际类型是char **,字符型指针的指针;
}

fun4(char(*s)[20])
{
// s在函数内部的实际类型不变,仍然是char(*) [20],即char [20]数组的指针;
}
以上可以简单的归纳为数组作为参数被改写为指向数组的第一个元素(这里的元素可以是数组)的指针。数组作为参数必须提供除了最左边一维以外的所有维长度。我们还要注意char s[][10]和char ** s作为函数参数是不一样的,因为函数内部指针的类型不一样的,尤其在进行指针加减运算以及sizeof运算时。

总结:
总结了这么多,应该对数组和指针有个较深入的理解了。这些问题的归根原因还是来自于指针问题,这也正是c语言的精华所在,不掌握这些根本不算掌握c语言,不过掌握了这些也不敢说就等于掌握了c语言:) 

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

深入理解C/C++数组和指针 的相关文章

  • 深入理解JS中的变量作用域

    在 JS 当中一个变量的作用域 xff08 scope xff09 是程序中定义这个变量的区域 变量分为两类 xff1a 全局 xff08 global xff09 的和局部的 其中全局变量的作用域是全局性的 xff0c 即在 JavaSc
  • 深入理解Express.js

    转载自 xff1a 深入理解Express js 本文针对那些对Node js有一定了解的读者 假设你已经知道如何运行Node代码 xff0c 使用npm安装依赖模块 但我保证 xff0c 你并不需要是这方面的专家 本文针对的是Expres
  • 深入理解Java中的String

    深入理解Java中的String 本篇转载自博客园 xff0c 原作者平凡希 xff0c 特此说明
  • 深入理解C/C++数组和指针

    版权所有 xff0c 转载请注明出处 xff0c 谢谢 xff01 http blog csdn net walkinginthewind article details 7044380 C语言中数组和指针是一种很特别的关系 xff0c 首
  • 深入理解HTTP协议

    目标 xff1a 掌握 http 原理 xff0c 重点掌握 http Request amp Response 格式掌握 http 中相关重点知识 xff0c 如请求方法 xff0c 属性 xff0c 状态码等使用 java socket
  • 深入理解Spring的@Order注解和Ordered接口

    前言 Spring的 64 Order注解或者Ordered接口大家都知道是控制顺序的 xff0c 那么它们到底是控制什么顺序的 xff1f 是控制Bean的注入顺序 xff0c 还是Bean的实例化顺序 xff0c 还是Bean的执行顺序
  • PRODUCT_COPY_FILES的深入理解,为何不能在Android.mk使用

    PRODUCT COPY FILES本质是和定义产品的AndroidProducts mk xff08 get all product makefiles来获取系统中所有AndroidProducts mk 文件路径 xff09 联系在一起
  • 深入理解Python中的if语句

    公众号 xff1a 尤而小屋 作者 xff1a Peter 编辑 xff1a Peter 大家好 xff0c 我是Peter 在生活中总是会听到这样的话 xff1a 如果我上课认真一点 xff0c 英语肯定可以及格如果我努力锻炼 xff0c
  • 深入理解MFC消息循环和消息泵的原理

    首先 xff0c 应该清楚MFC的消息循环 GetMessage PeekMessage xff0c 消息泵 CWinThread PumpMessage 和MFC的消息在窗口之间的路由是两件不同的事情 在MFC的应用程序中 应用程序类基于
  • 深入理解Spring两大特性:IoC和AOP

    Spring Boot 专栏 xff1a https blog csdn net dkbnull category 9278145 html Spring Cloud 专栏 xff1a https blog csdn net dkbnull
  • Dockerfile 深入理解

    Docker 专栏文章索引 x1f449 Docker 简介 x1f449 Docker 安装图文教程 x1f449 Docker 镜像概念及操作 x1f449 Dockerfile 深入理解 Dockerfile 深入理解 x1f680
  • CAN的ACK深入理解

    CAN信息发送成功后 xff0c 会有个应答间隙的 xff0c 在这个间隙内 xff0c 接收节点可以准备要回复的信息 xff0c 也就是把应答场填充为显性0 xff0c 在发送时其为隐性1 应答过程可能如下 xff1a 当信息传输到ACK
  • 深入理解Tomcat虚拟目录

    我们知道 xff0c Web网站中的内容 xff08 包括网页 xff0c 图片 xff0c 音频文件等 xff09 一般都存放在App的目录下 但随着网站内容的不断丰富 xff0c 用户需要把不同层次的内容组织成网站的子目录 我们通常的做
  • 深入理解Arrays.sort()

    翻译人员 铁锚 翻译日期 2013年11月16日 原文链接 Deep Understanding of Arrays sort T Comparator lt super T gt c Arrays sort T Comparator lt
  • [Adaptive Autosar]深入理解--Persistency

    目录 1 Per架构 2 Per初始化 3 Per错误处理 4 多进程访问 5 冗余存储 6 安装和更新per数据 7 Key Value 方式 8 file storage 方式 9 典型使用代码 Persistency模块对比 CP 中
  • 深入理解k8s中的service概念

    文章目录 service的概念kube proxy的作用kube proxy的三种模式Userspace Proxy ModeIptables Proxy ModeIPVS proxy mode service的概念 在k8s集群中 xff
  • PRODUCT_COPY_FILES的深入理解,为何不能在Android.mk使用

    PRODUCT COPY FILES本质是和定义产品的AndroidProducts mk xff08 get all product makefiles来获取系统中所有AndroidProducts mk 文件路径 xff09 联系在一起
  • 深入理解Golang中的Context包

    context Context是Go语言中独特的设计 xff0c 在其他编程语言中我们很少见到类似的概念 context Context深度支持Golang的高并发 1 Goroutine和Channel 在理解context包之前 xff
  • 深入理解Java虚拟机JVM

    JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java exe来完成 通过下面4步来完成JVM环境 1 创建JVM装载环境和配置 2 装载JVM dll 3 初始化JVM dll并挂界到JNIENV JNI调用接口 实例 4
  • 深入理解Kotlin无参构造函数

    Unsafe 创建实例 在java中 创建一个对象 其实主要就是3种方法 通过new 关键字来创建 这种是最常见的 通过反射构造方法来创建对象 这种也不少见 很多框架中都有使用 Unsafe类来创建实例 xff0c 这种情况非常少见 这里先

随机推荐

  • Java泛型--泛型应用--泛型接口、泛型方法、泛型数组、泛型嵌套

    1 泛型接口 1 1泛型接口的基本概念 1 2泛型接口实现的两种方式 定义子类 xff1a 在子类的定义上也声明泛型类型 interface Info lt T gt 在接口上定义泛型 public T getVar 定义抽象方法 xff0
  • Linux下调试段错误的方法[Segmentation Fault]--GDB

    原文 1 段错误是什么 xff1f 段错误是指访问的内存超出了系统给这个程序所设定的内存空间 xff0c 例如访问了不存在的内存地址 访问了系统保护的内存地址 访问了只读的内存地址等等情况 A segmentation fault ofte
  • linux驱动开发--copy_to_user 、copy_from_user函数实现内核空间数据与用户空间数据的相互访问

    设备读操作 如果该操作为空 xff0c 将使得read系统调用返回负EINVAL失败 xff0c 正常返回实际读取的字节数 ssize t read struct file filp char user buf size t count l
  • 函数中的形式参数和实际参数

    1 举例 xff1a 使用函数交换两个整形变量的值 运行结果 xff1a 分析 xff1a c语言中实际参数和形式参数之间采用值传递的方式来传递数据 在被调函数中 xff0c 使用的是实际参数的一个拷贝数据 我们在swap函数中交换了a和b
  • Linux 线程挂起与唤醒功能 实例

    pthread cond wait 多线程的条件变量 条件变量是利用线程间共享的 全局变量进行同步的一种机制 xff0c 主要包括两个动作 xff1a 一个线程等待 34 条件变量的条件成立 34 而挂起 xff1b 另一个线程使 34 条
  • PnP 单目相机位姿估计(二):solvePnP利用二维码求解相机世界坐标

    前言原理简介输入参数准备 1 objectPoints特征点世界坐标2 imagePoints特征点在摄像头下的像素点坐标3cameraMatrixdistCoeffs内参矩阵和畸变矩阵 相机世界坐标的求解 1求世界坐标中的点在相机坐标系下
  • Linux下socket编程,附带tcp例子

    1 网络中进程之间如何通信 xff1f 本地的进程间通信 xff08 IPC xff09 有很多种方式 xff0c 但可以总结为下面4类 xff1a 消息传递 xff08 管道 FIFO 消息队列 xff09 同步 xff08 互斥量 条件
  • 程序员加班到深夜,你经历过没?

    我看到了自己的影子啊 虽然自己非科班出身 xff0c 学历也不高吧 xff0c 但是自认为还是很努力的 xff0c 但是为什么现在的工资水平却跟应届生差不多呢 xff1f xff08 xff09 仔细想想 xff0c 自己毕业3年了 xff
  • 【C/C++学院】(16)QT版:幸运大抽奖

    程序效果 xff1a ifndef DIALOG H define DIALOG H include lt QDialog gt include lt QLabel gt include lt QPushButton gt include
  • 【Python基础】--Pickle/函数默认参数/函数的参数*args/Bytes<=>str/32-64bit/bytes对象

    Pickle gt gt gt import pickle gt gt gt my list 61 1 2 3 39 haha 39 39 and 39 39 or 39 gt gt gt pickle file 61 open 39 my
  • Windows平台python操作串口示例,可以加工下,改写成方便的测试软件

    在 windows中 xff0c 使用 Python 进行串口编程需要安装一个 Serial 模块 pyserial xff1a 下载地址 https pypi python org pypi pyserial下载完成后得到一个 pyser
  • 告别csdn一年了

    原本坚持了4年的学习 xff0c 整理笔记 xff0c 在csdn平台上进行发表 xff0c 记录 同朋友们互动 xff0c 探讨进行学习 xff0c 自己也在不断地成长 今天再次进入博客页面 xff0c 发现界面来了个大改版 xff0c
  • php视频课程

    php视频课程 xff1a 下载地址 xff1a http php itcast cn php video shtml 注 xff1a 此系列视频 xff0c 韩顺平主讲 1 php入门到精通教程 2 第二版mysql视频教程 进行中 3
  • pixhawk ulg转csv

    ulg是目前最新版px4固件生成的log格式 xff0c 下载最新版的flightplot即可对内部数据进行预览分析 xff0c flightplot中支持部分函数和运算符操作 xff0c 但对带 数据的操作不支持 xff0c 如需要对某些
  • 将Kinetic中的Gazebo7升级为Gazebo9

    将Kinetic中的Gazebo7升级为Gazebo9 一 查看所有gazebo7的相关包二 卸载当前已安装的gazebo相关包三 添加源四 安装新版本gazebo五 安装gazebo ros pkgs六 后记 官方教程 http gaze
  • 你真的了解串口 (Serial)吗?

    一 串口的定义 串口 xff0c 全称串行通信接口或串行通讯接口 xff0c 是一种常用于电子设备间通讯的全双工扩展接口 xff1b 串行通信 xff0c 串口通讯的技术基础 xff0c 指一位一位地按顺序传送数据 其特点是线路简单 xff
  • PnP 单目相机位姿估计(三):二维码角点检测

    解PnP问题时用二维码的好处二维码识别的流程代码最后 IDE xff1a visual studio 2013 使用库 xff1a Eigen opencv2 4 9 文档版本 xff1a 1 0 解PnP问题时 xff0c 用二维码的好处
  • 2014年计算机求职总结--面试篇

    又一年实习招聘陆续开始了 xff0c 这里分享一下我在2013年实习招聘和秋季招聘中的一些面试经历 xff0c 希望能对找工作的同学有所帮助 2013年面试过的公司有蘑菇街 网易游戏 阿里巴巴 腾讯 百度 大众点评 人人网 雅虎 xff08
  • 用位运算实现两个整数的加减乘除运算

    位运算的思想可以应用到很多地方 xff0c 这里简单的总结一下用位运算来实现整数的四则运算 1 整数加法 int Add int a int b for int i 61 1 i i lt lt 61 1 if b amp i for in
  • 深入理解C/C++数组和指针

    版权所有 xff0c 转载请注明出处 xff0c 谢谢 xff01 http blog csdn net walkinginthewind article details 7044380 C语言中数组和指针是一种很特别的关系 xff0c 首