首页 /  技术专区  /  C 屏幕太窄?试试伸展一下吧 >

地址与指针

1. 存储空间、符号、地址

1.1 存储空间

1.1.1 什么是存储空间

就是程序代码和数据的存放空间,笼统可以分为如下两种情况。

没有运行时:

        存储在硬盘(外存)上,所以此时的存储空间为硬盘。

运行时:

        代码和数据存放在内存上,供cpu访问。

当然程序在运行时,还需要用到寄存器和cache,寄存器和cache同样也是存储器,它是用于缓存内存中的代码和数据,所有这些存储器(硬盘、内存、寄存器、cache)都是由单个字节堆叠而成,存储空间中的每个字节都有自己的地址,如果没有地址的话,是无法访问它们的,每个字节的地址就好比是每个字节的门牌号。

一般来说,当我们说存储器时,主要指的是内存。

1.1.2 程序运行时,cpu是如何访问内存中的代码和数据的

存储器中的每个字节都有地址,这个地址就是每个字节的门牌号,通过地址就能找到每一个字节并访问它,

访问权限有两种:

· 可读可写

        程序的“.data、.bss、堆、栈空间”就是可读可写的。

        变量空间要求是可读可写的,所以变量空间就开辟于这些中。

· 只读

        .rodata、.text为只读的。

        代码和常量只能被读,所以代码和常量就放在这两个中。


疑问:怎么没有“不可读不可写”和“只写”的权限呢?

答:两种权限没有意义

· 不可读不可写:这个存储空间没有任何作用

· 只写:写入数据的目的是为了读出,只写入不读出,这种权限的存储空间也没有任何意义


1.1.3 地址 与 指针

(1)地址是个啥

        地址就是一个数,这个数可以唯一标记每个字节的存在,就好比人的身份证一样,也是一个数,可以唯一的标记每个人的存在。

举个例子,如果内存大小为4G,那么内存的地址范围就是

0x00000000 ~ 0xFFFFFFFF:十六进制表示时的范围

            0      ~    4G-1 :十进制表示时的范围

0x00000000 ~ 0xFFFFFFFF这个地址,就是一些用来唯一标记字节空间的数。

(2)指针就是地址的另一个名字

        为什么地址也称为“指针”呢?

        通过某地址能够唯一的访问某个字节,所以地址唯一的指向了某字节,这就像“指针”一样具有指向作用,因此才被形象的称为了“指针”。

        这就好比我知道你们家的地址,然后就能找到你们家,此时就说“你们家的地址”是指向你们家的指针。

        所以记住了,指针就是地址,地址就是指针。

(3)指针与指针变量

        指针:地址。

        指针变量:存放地址(指针)的变量

int a;
int *p = &a;

&a:a的指针

a有四个字节,每个字节都有地址,但是只有第一个字节的地址才是a的指针,为什么是这样的,后面在解释。

p:放地址(指针)的指针变量

这里要注意一点,指针变量里面放了指针后,我们就说p指向a,但是我们自己应该清楚,具有指向作用的是p里面的指针(地址),而不是指针变量,指针变量只是放指针的篮子。

指针变量里面的指针发生变化后,这个指向就发生变化了,所以说对于指针变量来说,具有指向作用的是里面放的指针,不是指针变量本身。

不过以后为了称呼的方便,我们往往会将“指针变量”也简称为指针,所有平时称呼“指针”的时候,有可能指的地址,也可能指的是指针变量,就看说话的语境,不过在本章里面,为了表达更清晰,我会按照准确的名字来称呼。


1.1.4 符号与地址

(1)程序运行时,访问存储空间是核心动作

        1)CPU执行的代码从哪里来

        要访问.text所对应存储空间。

        2)CPU按照代码要求去加、减、乘、除、与、或、非运算数据时,数据从哪来

        (a)变量数据:需要访问.data、.bss、堆、栈空间。

        (b)常量数据:需要访问.text、.rodata的空间。

        3)计算后的结果

        得到这结果后,这个结果数据不管是拿去给USB输出,还是给LCD显示,还是给扬声器播放,还是控制机械手臂,都要先在.data、.bss、堆、栈中开辟变量空间,暂存这个结果数据,然后再将结果数据输出外设的寄存器、显存、声卡等,就可以控制USB、LCD、扬声器、机械手臂等外设工作了。

        其实寄存器、显存、声卡等同样也是存储器,也是通过地址去操作的,只不过这些事情往往都是由驱动程序去做的。

        对于应用程序俩说,主要访问的存储器是内存,而不是寄存器、显存、声卡这些玩意,这些是由驱动程序来访问的。

(2)符号 与 地址

        前面说过,都是通过地址来访问存储空间的,直接通过地址来访问的话很不人性化的,所以在高级语言里面,地址都被替换为了符号。

        比如我使用某个变空间,在高级语言里面,就通过变量名来访问,变量名就是一个符号,通过这个符号就可以操作这个变量,不必直接使用地址。

        所以在高级语言的程序里面,程序员基本只见符号,不见地址,正是由于这样的做法,使得高级语言的语法相对汇编来说,非常的人性化,因为直接操作地址的话,你需要了解硬件结构,特别是存储器的结构,否则你就不知道应该操作那个地址,这就很痛苦,但是变成符号后,程序员不用关心这些,只关心符号即可。

        编译后,符号虽然会被转化为地址,但是在高级语言的语法里面,符号并不直接等于地址,你不能直接当做地址来使用,在高级语言里面,符号会受到作用域的限制,缺少一定的灵活性。

        所以为了能够更加自由的操作存储空间,像c/c++这种高级语言,除了能够使用符号来操作外,它还允许直接使用地址来操作,使用地址来操作时,可以不受符号作用域的限制。

1)作用域受限的例子。

a.c

int fun1(void)
{
    int a = 200;
}

int fun2(void)
{
    int a;
    fun1(a);
}

fun1和fun2中a的作用域,只在自己函数中有效,所以通过修改fun1的a,对fun2中a不受影响。

a.c                     

int fun(void)          
{                       
    flag = 100;           
    fun1();              
}

b.c

static int flag = 0;

static int fun1()
{
    ...
}

flag和fun1的作用域只在b.c中,因此a.c中的fun函数无法引用。

疑问:作用域限制是好还是坏?

答:有好有坏。

        好处:防止相互干扰,比如防止命名冲突,防止相互篡改数据等

        缺点:作用域限制太严,会使得编程过于死板,不够灵活

        所以像c/c++为了更加的灵活,就加入直接的地址操作,这样就可以取一个折中,既可以受到作用域的限制,又可以保持灵活性,比如看下面的例子。


2)修改上面的例子

a.c

int fun1(int *a)
{
    *a = 200;
}

int fun2(void)
{
    int a;
    fun1(&a);
}

直接改fun1和fun2的a,相互不受影响,这个是作用域在发挥作用。

但是由于fun2将a的地址(指针)传给fun1的a,所以在fun1中通过*a就可以读写fun2中a,又可以修改fun2中的a,如此就有了相当的灵活性。

3)再举一个例子

a.c                     

int fun(int *flag, void (*fun1p)())     
{                       
    *flag = 100;  //引用flag         
    fun1p(); //调用fun1               
}		

b.c

static int flag = 0;

static int fun1()
{
    ...
}

static int fun2()
{
    fun(&flag, fun1);
}

flag和fun1是a.c本地的,在a.c中时无法直接调用的,但是通过fun(&flag, funp)将flag和fun1的地址传递给a.c中的fun后,fun通过地址依然能够引用flag和fun1。

疑问:为什么不将static直接改为extern?

答:这样当然也能解决问题,而且一般情况也都是使用这种方式来解决的,但是当工程代码写的复杂后,必须对于函数全局

全局变量的作用域进行限制,这样可以有更好封闭性,特别当代码需要进行逻辑上的分层时,那么不同层必须需要有相当的封闭性,在封闭之后层与层之间的对接,就需要靠这种方式来实现。

当然对接函数必须是extern的,比如,如果例子中连fun函数都是static的,那就完全隔离了。

疑问:封闭性的好处?

        · 防止命名冲突

        · 防止胡乱引用

        · 防止数据篡改

这就好比为了防止两个区域相互干扰,那就需要建立围栏加以限制,但是不能完全封死,为了让区域间还能沟通,所以还得留下一道门。

在上面的例子中,void (*fun1p)()中fun1p是函数指针变量,有关函数指针变量,本章后面再详介绍。

4)java/c#等语言中有指针吗?

在java和c#等高级语言中,并不允许在代码中直接操作地址,只能使用符号来操作,虽然没了指针后,灵活性降低了,但是也同时规避了指针所带来的风险,因为指针不受作用域限制,直接通过地址操作空间,因此存在潜在的危害,没有了指针,这种风险自然也就没有了,不过代价就是灵活性降低了。



0/200
图片验证码