C-关于指针

c和指针的关系十分密切,所以在本文,我们会详细的谈谈指针。这边我会结合<<c与指针>>这本书的内容来介绍它。

一.内存与地址

计算机的内存可以看作是一条长街上的一排房屋。每座房子都可以容纳数据,并通过一个房号来标识。这个比喻颇为有用,但也存在局限性。计算机的内存由数以亿万计的位(bit)组成,每个位可以容纳值0或1。由于一个位所能表示的范围太有限,所以单独的位用处不大,通常许多位组成一个单位,这样就可以存储范围较大的值。这里有一幅图,展示了现实机器的一些内存情况。

这里每个位置的方块都被称为字节(byte),每个字节都包含了存储一个字符所需要的位数,在许多现代机器上,每个字节包含8个位,可以表示无符号值0~255,或有符号值-128~127,上面这张图没有显示出这部分内容,但内存中的每个位置总是包含着一些值。每个字节通过地址来标识,如上图方框上面的数字所示。

为了存储更大的值,我们把两个或多个字节合在一起作为一个更大的存储单位。例如,许多机器以字位单位存储,每个字通常由2个或者4个字节组成。下面这张图所标识的内存位置与上图相同,但这次它以4个字节的字来表示。

由于它们包含了更多的位,每个字可以容纳的无符号整数的范围更大了(0~4294967295(2^32-1))。

注意,尽管一个字包含了4个字节,它仍然只有一个地址。至于它的地址是从最左边那个字节的位置还是最右边字节的位置开始,不同的机器有不同的规定(大小端)。另一个需要注意的是边界对齐(boundary alignment),在要求边界对齐的机器上,整型值存储的起始位置只能是某些特定的字节,通常是2或4的倍数。但这些问题是硬件设计师的事情,它们很少影响写程序的,废话有点多了,总之,我们通常只对两件事情感兴趣:

内存中的每个位置都有一个独一无二的地址标识

内存中的每个位置都包含一个值

地址与内容

这里有另外一个例子,这次它显示了内存中5个字的内容。

这里显示了5个整数,每个都位于自己的字中。如果你记住了一个值的存储地址,你以后可以根据这个地址取得这个值。

但是要记住所有这些地址可太笨拙了,所以高级语言提供的特性之一就是通过名字(变量名)而不是地址来访问内存的位置。下面这张图与上图相同,但这次使用名字来代替。

当然,这里的名字就是我们所称的变量。有一点非常重要,你必须记住,名字与内存位置之间的关联不是硬件所提供的,它是由编译器为我们实现的。所以这些变量其实是给了我们一种更方便的方法去记住地址,实际上硬件仍然可以通过地址访问内存位置。

二、值与类型

现在我们看下上图存储于这些位置的值。头两个位置存储的是整数112和-1。第三个位置存储的是一个非常大的整数,第四五个位置存的也是整数。下面是这些变量的声明:

在这些声明中,变量a和b确实用于存储整型值。但是c所存储的是浮点值。但上图中c的值却是一个整数。那么c到底是整数还是浮点数呢?

答案是该变量包含了一序列内容为0或者1的位。这个01序列可以被解释为整数,也可以被解释为浮点数,这取决于它们被使用的方式。如果使用的是整型算术指令,这个值就是整数,如果用的是浮点指令,它就是个浮点数。

这个事实引出了一个重要的结论:不能简单地通过检查一个值的位来判断它的类型,为了判断值的类型(以及它所表达的值),你必须观察程序中这个值的使用方式。考虑下面这个以二进制01表示的32位值:

下面是这些位可能被解释的许多情况中的几种。这些值都是从一个基于Motorola68000的处理器得到的。如果换个系统,使用不同的数据格式和指令,对这些位的解释将有所不同。

这里,一个单一的值可以被解释为5种不同的类型。显然,值的类型并非值本身所固有的一种特性,而是取决于它的解析方式。因此,为了得到正确答案,对值进行正确的使用是非常重要的。

当然,编译器会帮助我们避免这些错误。如果我们把变量c声明为float型变量,那么当程序访问它时,编译器就会产生浮点型指令。如果我们以某种对float类型而言不适当的方式访问该变量时,编译器就会发出错误或者警告信息。现在看来非常明显,图中所标明的值是具有误导性质的,因为它显示了c的整型表示方式,事实上真正的浮点值是3.14。

三、指针变量的内容

话题转回指针,看看变量d和e的声明。它们被声明位指针,并用其他变量的地址予以初始化。指针的初始化是用&(用在这边称为取地址符)操作符完成的,它用于产生操作数的内存地址。

d和e的内容是地址而不是整型或浮点型数值。事实上,结合上面几幅图可以容易地看出,d的内容和a的存储地址一致,而e的内容与c的存储一致,这也正是我们对这两个指针进行初始化时所期望的结果。区分变量d的地址(112)和它所存储的内容(100)是非常重要的,同时也必须意识到100这个数值用于标识其他位置(是某个内存位置的地址)。在这一点上,本文一开始房屋这个比喻不是很行得通,因为房子的内容不太可能是其他房子的地址。

在下一步之前,先看一下涉及这些变量的表达式。仔细考虑这些声明,​

a、b、c、d、e的值分别是什么呢?

前3个很显然:a的值是112,b的值是-1,c的值是3.14。指针变量其实也很容易,d的值是100,e的值是108。如果你认为d和e的值分别是112和3.14,那么你就犯了一个极为常见的错误。d和e被声明为指针并不会改变这些表达式的求值方式:一个变量的值就是分配给这个变量的内存位置所存储的数值。如果你简单地认为由于d和e是指针,所以它们可以自动获得存储于位置100和108的值,那你就错了。变量的值就是分配给该变量的内存位置所存储的数值,即使是指针变量这点也不会变。

四、间接访问操作符

通过一个指针访问它所指向的地址的过程称为间接访问(indirection)或解引用指针(dereferencing pointer)。这个用于执行间接访问的操作符是单目操作符*。这里有一些例子,它们使用了前面小节里的一些声明。

d的值是100,当我们对d使用间接访问操作符时,它表示访问内存位置100并查看那里的值。因此,*d的右值是112,即位置100的内容,它的左值是位置100本身。

注意上表各个表达式的类型:d是一个指向整型的指针,对它进行解引用操作将产生一个整型值。类似,对float*进行间接访问将产生一个float型的值。

正常情况下,我们并不知道编译器为每个变量所选择的存储位置,所以我们实现无法预测它们的地址。这样,当我们绘制内存中的指针图时,用实际数值表示地址是不方便的。所以绝大部分书籍改用箭头代替,如下所示:

但是,这种记法可能会引起误解,因为箭头可以使你误以为执行了间接访问操作,但事实上,它不一定会执行这个操作。例如根据上图,你能推断表达式d的值是什么?

如果你的答案是112,那么你就被这个箭头误导了。正确答案是a的地址,而不是它的内容。这个箭头似乎会把你的注意力吸引到a上。要使你的思维不受箭头影响是不容易的,这也是问题所在:除非存在间接引用操作符,否侧不要被箭头所误导。

五、未初始化和非法的指针

下面这个代码段说明了一个极为常见的错误:

这个声明创建了一个名为a的指针变量,后面那条赋值语句把12存储在a所指向的内存位置。

警告:

但是a究竟指向哪里呢?我们声明了这个变量,但从未对它进行初始化,所以我们没有办法预测12这个值将存储于声明地方,从这一点来看,指针变量和其他变量并无区别。如果变量是静态的,则它会被初始化为0;如果变量是自动的,它根本不会被初始化。无论是哪种情况,声明一个整型的指针都不会“创建”(确切来说是分配)用于存储整型值的内存空间。

所以,如果程序执行这个赋值操作,会发生什么情况呢?如果你运气好,a的初始值会是一个非法地址,这样赋值语句将会出错,从而终止程序。在UNIX系统上,这个错误被称为“段违例(segmention violation)”或“内存错误(memory fault)”。它提示程序试图访问一个并未分配给该程序的内存位置。在一台运行Windows的PC上,对未初始化或非法指针进行间接访问操作是一般保护性异常(General Protection Exception)的根源之一。

对于那些要求整数必须存储于特定边界的机器而言,如果这种类型的数据在内存中的存储地址在错误的边界上,那么对于这个地址进行访问时将会产生一个错误。这种错误在UNIX系统中被称为“总线错误(bus error)”。

一个更为严重的情况是:这个指针偶尔可能包含一个合法的地址。接下来的事很简单:

位于那个位置的值被修改,虽然你无意去修改它,像这种类型的错误非常难以捉摸,因为引发错误的代码可能与原先用于操作那个值的代码完全不相干,所以在你对指针进行间接访问之前,必须非常小心,确保它们已被初始化。

未完待续...

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,423评论 3 44
  • C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程...
    小辰带你看世界阅读 933评论 0 6
  • 第十章 指针 1. 地址指针的基本概念: 在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为...
    坚持到底v2阅读 1,057评论 2 3
  • 指针 在了解什么是指针之前,我们需要先搞清楚数据在内存中是如何存储的,又是如何读取的。 如果在程序中定义一个变量,...
    Longshihua阅读 983评论 0 4
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32