鱼C论坛

 找回密码
 立即注册
查看: 4368|回复: 16

[技术交流] c指针及其使用详解

[复制链接]
发表于 2021-8-6 14:03:59 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 万千只cnm 于 2021-8-6 14:03 编辑

                                                                                               c指针及其使用详解

众所周知 c语言学习过程当中 指针一直是个重点和难点 ,此帖谈一下我看完《c与指针》等书之后的见解;
希望能帮到大家,内容有不对的欢迎大家指正
目录:
         一: 内存中数据是如何存储的
                  二:指针是如何寻址的
                  三:数组                                         

                         :特性
                         :数组(名)与指针区别
                  四:类型
                           :数组名的类型
                           :指针类型
                           :类型相融
                  五:指针运算
                  六:复杂指针类型/声明
                  七:函数指针/指针作为参时
                  八:附:左右值区别(操作符 不懂的童鞋先看这
      
                  
                                                               

要想彻底理解指针及其概念 ,就得弄清楚数据如何存储的 ,这里涉及到底层的一些知识
推荐一下小甲鱼讲的王爽版本的汇编 :
https://www.bilibili.com/video/BV1Rs411c7HG?from=search&seid=5397185164506602840
(满满年代感

1
  cpu只认识二进制  不管是数据,还是要执行的代码(也是数据),在内存中就是一连串的二进制数字
  CPU和内存之间需要读取或存储数组,之间就需要一个沟通的桥梁,这个桥梁就叫做地址总线(总线有三种:地址/数据/控制)。

示意图

示意图

那为什么叫地址总线而不叫数据总线?这是因为CPU在读取内存中的数据时的第一步是先找到数据的地址,有了地址才能把数据的具体位置找到,然后通过数据总线传输。



就像在商店里买东西,先得找到物品,才能付钱买



2
    内存中最小的储存单元是一个字节,可以把这一个个字节看成一个个房间,在这个房间中能储存数据,这些房子会整整齐齐地排成一排。

地址与内容

地址与内容

这个时候就有问题了,CPU应该怎么确定数据是储存在哪个房间呢?这个时候就要靠地址了,可以把这个地址看成房间的号码,显而易见的是每一个房间的号码是必须要是不同的

如8086能寻 1MB的 范围地址(段地址加偏移量,类似下标法寻址法)如debug程序中:

debug

debug


   
如上图最左边 073F:01bd  就是地址(073f是段地址 冒号右边就是偏移地址) ,右边就是地址中所储存的数据(以16进制显示,两个为一字节)


  而我们的数据就是存储在这些地址中,对这些地址中的数据进行操作以达到程序的目的  
  但跟c语言和指针有什么关系呢?
                                                                  




1

  我们都知道源代码是经过编译器和连接器 才能成为可执行文件方能运行

前面说了cpu是通过地址来寻找所需内容的,当然不认识变量名 ,这里就要提一下符号表:
https://blog.csdn.net/qq_30120725/article/details/47064035

一个变量名其实就相当于一个符号,代表一个具体的地址位置
注意:代表一个位置(地址),地址里存放的数据(内容)与地址本身不同

编译时自动转换为内存中相应的具体地址。



举个列子 我们先写一个源程序

源代码

源代码




生成汇编程序:

汇编程序

汇编程序




可以看到并没有出现a这个变量名。

而是在栈段中操作,有同学就要问了,这也看不到地址呀?

众所周知 自动变量就是储存在栈中,栈当然也是内存中的地址啦,栈中数据通过ss sp两寄存器来对数据进行操作 ,相当于数组通过下标来寻址

这个例子中a就在栈顶以下8个内存单元 ,把666这个值“赋值”给对应位置(地址
movl        $666, -8(%rbp)                //地址为栈顶以下八个内存单元
2                   当我们定义一个变量  
int b = 666;
我们只知道 b的值为666 怎么知道b变量的地址呢 ?
------- 在c语言中 我们可以用 &操作服来获取变量本身的地址


        重点来了,当我们定义一个指针变量并初始化
int *p = &b;
我们把取了整型变量b的本身地址(不是里面的值
就可以说   
指向整型类型的指针p  指向了整型变量b

3
综上
  任何变量都有其本身的地址,还有其里面的内容(除放在register中的变量


假设 变量b的地址为:0x123  指针变量p的地址为0x678
p指向b时:

指针里的内容

指针里的内容
(纯手画
   可以看到 0x678这个地址当中的内容 是0x123,也就是a本身的地址,而不是a的值(内容)666


那我们如何通过指针 获取指向变量里的值呢? 当然是通过解引用

访问数据

访问数据

正如 :
 *p ; 
此时 *p  表达式的值就是 a里的内容整型值666了

我们无需知道指针本身的地址,只需要知道指针里存放的内容,也就是指针的指向 (如 0x678 对我们并不重要,只需要知道指向0x123
看到这大家有没有理解指针呢?

           
三(数组



c语言中 指针与数组密不可分,也很容易混淆

数组就是数组 ,指针就是指针,不可以混为一谈



1

数组的特性:

               数组里的元素类型是统一的

               数组是可以随机访问的,里面的元素也是匿名的

                   --- 可以通过(且只能) 首地址+偏移地址 来访问数组里的元素    如array【2】== *(array+2)

2

提供一种理解 多维数组/复杂数组的角度 :

    c语言的数组都是一维的,只不过元素可以是任意类型

如:
int array[2][3];
      可以理解为: array是一个拥有两个元素的一维数组   里面元素的类型 是一个拥有三个int型元素的的数组

3

数组与指针的区别:

                   不同:

      a: 数组在定义时就分配好所有空间 ,而指针只分配了本身的空间
int arrar [10];   //<span style="background-color: rgb(255, 255, 255);">分配好了  10 *sizeof(int) 字节的空间</span>
         指针只为自身分配空间,而不会为指向的对象分配空间
          b: 数组 :保存数据       指针:保存数据的地址
                 数组名就相当于一个符号,代表了首地址,里面元素线性排布


             c:取值的方式不同
                    符号(变量)本身的地址编译时就可以知道 ,而符号里的内容运行时才可以知道
                     指针:
                            1 .取本身地址里面的内容 (指针分配了自身的空间
                            2. 对值(内容)进行解引用

                      数组:
                               1. 直接对本身地址进行解引用
------------------
可能有同学要问了:啥意思呀有什么区别?
举个列子
    在一个源文件中
int array[10]={1,2,3};
另一个源文件中
extern int *array;
*array = 0;
编译器会认为是个指针
                         取array地址,访问里面内容 ----整型1     -->会认为整型1  是要访问的地址!
                           会将内存地址为 1的空间进行赋值 ,非常危险!!!!!

             d:数组名是不可修改的左值
                                         数组名能修改里面的元素不就找不到了

                 相同:


           表达式中数组名就是指针,因为最终代码都是具体地址

   如:
int array[10];
     int *p = array;
p[2];
array[2];
通过数组名和指针访问都是可行的 ,都是通过指针类型的运算获得元素的地址进行访问
-----
          这个结论对于牛角尖的同学来说确实有列外:就是把数组当作整体来看
              1 数组名作为sizeof 操作数时 ,此时是整个数组的大小
                     2 使用 &取数组的地址时

                     3 数组是一个字符串常量初始值

                                       

四(类型


所谓指针的类型-- 就是指向   ~~     的指针 ,可以构建非常复杂类型的指针
当然数组(名)也有类型

1
结论:
       数组名的类型就是    指向其元素类型的指针


 int array[2][3];  //一维数组 ,里面元素 是包含3个整型的数组
     int (*p1)[3];// 数组指针 
p1 = array;
此时可以赋值,类型相同  
array数组里的元素 是一个数组 -->   array的类型就是 指向数组的指针!


注: 变量作为左值和右值 所代表的意义不同(详看第十节


2
附: 两个指针类型是否相融(是否可以进行赋值
           要满足两点 :
                              1. 指针 指向的类型要相融  (一个指向char 一个指向int 当然不行
                              2. 等式左边指针 所指向的类型 必须具有右边指针 所指向类型的 全部限定符(const等


如:
#include <stdio.h>
void fun(const char **p){
    ;
}
int main(int argc,  char **argv) {
   
    fun(argv);
    return 0;
}
是否会发出警告呢,错在哪了 (不要上机测试)
欢迎评论


3
指针常量
      用一个字符串常量给指针赋值
char *p= "I love fish";
  字符串常量的值是一个地址!且放在静态存储区      ----->p指向的内容是不可以修改的,本身可以修改   

五(指针的运算



大部分类型变量都可以进行运算,指针也不列外

1
   先说一下“步长” :
          所谓步长,就是 指向类型的宽度(长度)     
int array[10];
            我们把array当作一个指针来看 --》 指向整型的指针
               这里一个步长就是sizeof int 字节

2
运算:
       加法运算(与整型) : 数组匿名访问 随机访问的基础  ,语法糖的原理


指针(数组名) +1  -->    地址+一个步长
int array[10]={0};
array[2];
这里 array[2] 就是  : *(array +2)  --》
                                                             1.取首地址
                                                      2.加上  2*sizeof(步长) ,这里就是地址+8(假设一个整型4字节)  指向后俩个元素
                                                      3.解引用进行访问
注:array也可以用指针替代       这也是数组为什么只能省略第一维的原因 :要知道元素步长 以通过运算访问
  
       减法(指针之间)
               指针减法需满足
                                    同一个数组之间  &&  指针类型相同  --> 返回的是其元素相隔的距离(会自动除以步长


-------------------
其他运算符(如加法) ,没有任何意义



六(复杂 指针/声明



指针与数组可以组合成非常恐怖的声明

但如果你看懂了上面几节 ,如指针数组 数组指针 等就会迎刃而解



复杂声明:

https://blog.csdn.net/wangweixaut061/article/details/6549768    
指针数组等:

https://blog.csdn.net/warrior_harlan/article/details/78556346

   总结小秘诀 :

                     1:复杂定义中 只要看见了 (*符号)  -->就是一个指针

                               2: 声明转化为表达式  的结果类型 就是声明的类型

int (*(*a)(int *))[5];
   (*符号)-->是个指针       (*(*a)(int *))[5]  表达式的值就是int 类型  ,这也是c语言中非常有趣的现象
     a到底是什么指针呢 欢迎评论
                            3 定义只需要 去掉符号,外面加一层括号--》 就是强制转换啦



int *a;
(int *) 就是强制转换时 目标类型


七(函数/数组/指针





1

函数调用时其实 也是通过函数指针来完成的

 int f(int );
    int (*pf)(int) = &f;
// int (*pf)(int) = f;  也可以
函数名在被使用时,总是由编译器转换为函数指针,指向内存中的代码块

int ans = f(25);
int ans = (*pf)(25);  //这三种都可以
int ans = pf(25);
这里 f被转换为指针


2

当我们向函数传数组时 :



char str[20]="fishc";
    printf("%p \n%s\n",str,str);

输出:

结果

结果


有同学就问了: 为什么一个标识符能打印两种不同结果呢 ?


----当数组作为函数参数时 都会被转换为指向元素的指针

   形参就是真的指针,而不是当作;

实参 char str[10][5];               匹配形参 char(*)[10];
       char *str[10];                                char **;

所以这就是

gets函数会溢出的原因 : 传数组,实际是指针 -->函数无法通过指针检查到数组边界!!!


八(左右值)




  所谓左右值 就是出现在 = 左右两边

左边叫左值,右边叫右值(废话      

https://fishc.com.cn/forum.php?mod=viewthread&tid=69833&extra=page%3D1%26filter%3Dtypeid%26typeid%3D571



1
能否作为左值 关键是:
                               符号能否代表一个具体位置(地址)


  如数组名,就可以当左值(不能修改的左值),代表数组的首地址
  如整型 666,不能当左值 ,只是个值(常量)!!




2
变量当左值与右值时 , 意义不一样
int a = 666;
int b;
b =a ;
整型变量a做右值 -->代表的是符号a地址当中的
------
a = 123;
a做左值  --> 此时符号a代表本身的位置(地址)  要把值123存到a代表的地址中
我们只关心a的地址 来存放数据

3
操作符 来判断左右值
  表达式是由许多操作符和符号组成的--》 一个表达式的值能否成为左右值 可以依据操作符


总结:
     
[]  下标访问
.     成员访问
->  指针成员访问
*   间接访问
这四个操作符的表达式的值 能做左值(代表一个位置

--------------------------------
  而其他操作符 如 : ++ * / 等
                                        结果(值)都是一个值!! 只能当右值












最后: 本人水平有限希望能帮助大家,有什么错误或不理解地方都可以提问,我都会回答
   也祝各位鱼友越来越牛,网站越办越好!




评分

参与人数 8荣誉 +31 鱼币 +31 贡献 +15 收起 理由
yangyiheng + 1 + 1 鱼C有你更精彩^_^
飞花落尽 + 5 + 3 + 3 感谢楼主无私奉献!
小伤口 + 5 + 5 感谢楼主无私奉献!
叼辣条闯世界 + 2 感谢楼主无私奉献!
大马强 + 5 + 5 + 3 感谢楼主无私奉献!
wxm23333 + 5 + 5 + 3 感谢楼主无私奉献!
justicepython + 5 + 5 + 3 楼主讲的很好!支持!
Daniel_Zhang + 5 + 5 + 3 感谢楼主无私奉献!

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2021-8-6 14:04:00 | 显示全部楼层
坏了 怎么没自动保存 咋办
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-8-6 14:04:01 | 显示全部楼层
心态崩了呜呜呜 都快写完了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-8-6 14:04:02 | 显示全部楼层
没保存重新写了一次 ,希望大家喜欢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-8-21 16:41:15 | 显示全部楼层
加油
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2021-8-21 16:48:14 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-9-9 15:09:05 From FishC Mobile | 显示全部楼层
写的真好,找时间好好研究一下
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-9 22:49:54 | 显示全部楼层
小伤口 发表于 2021-9-9 15:09
写的真好,找时间好好研究一下

谢谢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-9-18 22:42:17 | 显示全部楼层
写得非常好,一看就懂
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2021-9-18 22:55:33 | 显示全部楼层
yangyiheng 发表于 2021-9-18 22:42
写得非常好,一看就懂

能评个分嘛
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-9-19 07:43:29 | 显示全部楼层

抱歉,用户组太低,没办法评分
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-9-30 21:04:53 | 显示全部楼层
不错
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-10-31 15:57:15 | 显示全部楼层
666
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-10-31 15:57:46 | 显示全部楼层
会继续更吗?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-11-2 21:11:57 | 显示全部楼层
写的很好
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-24 19:13:16 | 显示全部楼层
祝你申精成功
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-8-24 22:38:43 | 显示全部楼层
看到申精贴,过来顶一把
不过确实讲的非常非常非常非常非常非常好!!!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-12-28 18:52

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表