考虑到指针内容繁多,这里将指针作为一个系列,从简入繁,带着没有研究过指针的朋友,一点一点深挖并掌握这C语言的精华。初步计划如下

此文为指针系列第一篇:

C语言精要总结-指针系列(一)

内存与地址

我们可以把内存看做一排连续的房间,每个房间(字节空间)都有一个房间号,房间号就是这个房间的地址,而且每个房间里都有八个位。

为了存储不同大小的值,多数时候我们要用连续几个房间来存储一个值,这时我们会用其中一个房间号来表示这一片连续的房间,至于这个房间号是第一个房间的房间号,还是最后一个房间的房间号,不同的机器有不同的规定。文中我们假设这个房间号是左起第一个房间的房间号,这样房间号(指针)其实就有了类型之分。

通过地址,计算机就可以操纵内存单元的内容,虽然在代码中,类似于*0x0048f93d = 'a';这样的表达式是合法的,但为了让代码看起来更友好,显然不能在代码里全部写数字地址。因此高级语言的编译器为我们实现了直接通过变量名来访问内存位置,当然硬件单元依然通过寻址来访问内存位置

指针变量

指针变量本身也是变量,也需要在内存中占用一定的存储单元,只是其存储的值是其他变量的内存地址,分配的位置也可以是跟基本类型变量是连续的。(下图中一个长方形并不代表一个字节),如图:

用代码来描述即是

 short shortVar = ;
int intVar = ;
short * p1 = & shortVar;
int * p2 = & intVar;

上面的语句给出了指针定义(type *)和初始化(= &var)的方法。这里定义了一个名叫p1指向short类型的指针变量,并用shortVar的地址来初始化;定义了一个名叫p2指向int类型的指针变量,并用intVar的地址来初始化。当然,像下面这样直接用一个地址值来初始化指针也是合法的

 int *p = (int *)0x0048f93d;

虽然这在我们看来是个地址值,但在编译器眼里,这是一个int类型的值,所以需要强制转换。这种写法,除非很明确这个地址时用来做什么的,否则不要这么做。

如果在定义一个指针变量时,还不确定用什么地址来初始化,则一定要初始化为NULL,这是一个空指针值,也是一个值为0的宏,它代表指针不指向任何位置。如果不给一个局部指针变量做任何初始化,它存储的将是一个不可预知的值,指向一个不可预知的位置,如果对这样一个指针变量进行操作,很容易引起异常中断。而对于全局变量,编译器会自动初始化为0。

解引用操作

解引用,又叫间接访问,即通过一个指针变量访问它所指向的地址的过程。这个解引用操作符便是单目操作符*。但注意对一个指针进行进行解引用,不一定是取值,也可能是写值,这取决于解引用表达式是作为左值(赋值符号左边)还是右值(赋值符号右边)。

例如对上述指针p1,p2进行解引用操作,如下代码

 printf("%d\n",* p1);    //
printf("%d\n",* p2); //
*p1 = ;
*p2 = ;
printf("%d\n",* p1); //
printf("%d\n",* p2); //

那么像下面这个表达式做了什么呢?

 *&shortVar = 1;

很显然,这是将1赋值给变量shortVar,根据右结合性,取地址(&)之后立即解引用(*),这其实多此一举,如果编译器不对这样的代码做优化,那将生成一些无意义的操作代码。

二级指针

二级指针,也叫指针的指针,也就是一个指向指针变量的指针。按照指针变量的定义方法(type * pVar),我们要定义一个指向整型指针变量的指针,应该像下面这样定义

 int *  * p2p = & p2;

没错,这就是定义一个指向整型指针的指针的定义方式。在内存中结构(假设分配的恰好是连续的)就如下图所示

很显然,二级指针变量依然也是一个指针变量,哪怕后面还有三级、四级指针变量,都始终是一个指针变量,对它进行解引用或者取地址,原理跟一级指针是一样的。比如对二级指针p2p进行一次解引用,将得到p2这个指针变量,再进行一次解引用将得到intVar这个变量,正如上图所示。

 printf("%d\n",**p2p);    //

二级指针跟二维数组名是有很大区别的,这会在后续的文章中指出。另外,如果对一个二级指针取地址,将得到一个三级地址,依次类推。

指针的大小

我们知道指针是用来存储地址值的,而分配给指针变量的空间,只用来存储地址值,而不会记录变量类型等信息,这跟普通变量是一样,它们被记录在编译器的符号表中。

既然指针变量自身的空间只存地址,那么不管什么类型的指针,它们占用的空间大小应该是一样的,那究竟应该分配多大的空间?这取决于CPU最大寻址地址的大小。为了保证指针变量能存下最大的寻址地址,应该给指针变量分配足以存储最大寻址地址的大小。

在32位CPU上,CPU最大寻址空间为2的32次方(4G),因此要存下最大的32位的地址值,需要为指针变量分配4个字节的空间,而在64位CPU中,为了能寻到2的64次方的内存空间,需要为指针变量分配8个字节的空间。

因此,编译器也充分考虑了这个问题。它可以控制分配的指针变量的空间大小。用VS在写Console Application时,默认编译的是32位 Console Application,这是为了保证程序的兼容性,以保证程序一定可以在32位和64位机器上运行,此时,vs编译器默为指针变量分配4个字节的空间。但是本人的笔记本是支持64位寻址的CPU,因此,本人用gcc version 5.1.0 (tdm64-1)编译出来的程序,指针变量分配了8个字节的空间。

后续文章中,如不明确指出,我们认为指针变量占4个字节的空间。

指针类型强制转换

在看怎么定义二级指针时,有读者可能考虑,为什么不能这样定义

 (int *)  * p2p = & p2;

乍一看可能没什么不对,但实际上,这个表达式并不是定义一个变量,而是在执行一个非法的赋值操作。假如前面已经定义过p2p这样的一个二级指针,这个表达式还真会做一些事情:

  1. 解引用p2p得到一个一级指针tmp
  2. 将一级指针tmp强制转换为一个指向int 类型的指针
  3. 取p2指针的地址(一个二级指针)赋值给一级指针tmp(注意是赋值给一级指针本身,而不是一级指针指向的变量)

显然这是不能执行成功的。但是它却告诉我们,指针是可以强制转换的。

但对指针类型强制转换,和普通数据类型会有些不一样:对指针类型强制转换,不会改变指针变量本身空间的大小及空间内存储的地址值,而只会修改符号表中的指针类型及其指向类型占用空间的大小值(为指针运算做准备)。

一起来图解一下下面这段代码

 // int a = 0x12345678;
// return *(char*)(&a) == (char) a;
int a = 0x12345678;
int * pa = &a ;
char * pch = (char *) pa;
char ch = (char) a;
printf("%x\n",*pch);
printf("%x\n",ch);

假如程序出现的变量按如下方式分配

对指针强制转换之后,pch存储的地址值跟pa存储的地址值时一样的,但是他们在编译器符号表中的类型是不一致的,因此指向的空间大小是不一样的,pa指向整个变量a,而pch指向变量a的第一个低字节。ch变量毫无疑问存储的将是78,因为对一个基本数据强制转换,只会取数据的低位。

很显然,如果按照图中所示,程序的第7行第8行将输出12和78。但实际上在本人的笔记本上,两次都是输出78。

这其实就是很经典的大端存储和小端存储的判别。如果按照图中所示,其实变量a是按大端模式存储(即低地址存高位)。而如果按照小端模式存储,则应该低地址存低数据位,如下图。

而上面那段代码,就是用来检测计算机是按大端存储还是按小端存储的,很显然,本人的笔记本按小端存储。

用这个程序想说明的是,对一个指针进行调整级别的强制转换再解引用,可能会引起一些兼容性问题,因为这取决于系统实现。

另外在程序中我们会经常看到void * 类型的指针,这样的指针主要是为了写通用的代码,你可以将任意类型的指针强制转换为void* 类型的指针,在之后要解引用的时候,再强制转换回正确的指针类型进行解引用。例如我们常见的c语言库函数qsort中的:int comparator ( const void * elem1, const void * elem2 );。

C语言精要总结-指针系列(一)的更多相关文章

  1. C语言精要总结-指针系列(二)

    此文为指针系列第二篇: C语言精要总结-指针系列(一) C语言精要总结-指针系列(二) 指针运算 前面提到过指针的解引用运算,除此之外,指针还能进行部分算数运算.关系运算 指针能进行的有意义的算术运算 ...

  2. 【C++自我精讲】基础系列一 指针与引用

    [C++自我精讲]基础系列一 指针与引用   一 前言   指针.引用.指针与引用区别. 二 指针   变量:代码中常常通过定义变量来申请并命名存储空间,并通过变量的名字来使用这段存储空间. //变量 ...

  3. 【C++自我精讲】基础系列二 const

    [C++自我精讲]基础系列二 const 0 前言 分三部分:const用法.const和#define比较.const作用. 1 const用法 const常量:const可以用来定义常量,不可改变 ...

  4. 【C++自我精讲】基础系列四 static

    [C++自我精讲]基础系列四 static 0 前言 变量的存储类型:存储类型按变量的生存期划分,分动态存储方式和静态存储方式. 1)动态存储方式的变量,生存期为变量所在的作用域.即程序运行到此变量时 ...

  5. 【C++自我精讲】基础系列六 PIMPL模式

    [C++自我精讲]基础系列六 PIMPL模式 0 前言 很实用的一种基础模式. 1 PIMPL解释 PIMPL(Private Implementation 或 Pointer to Implemen ...

  6. Atitit java方法引用(Method References) 与c#委托与脚本语言js的函数指针

    Atitit java方法引用(Method References) 与c#委托与脚本语言js的函数指针   1.1. java方法引用(Method References) 与c#委托与脚本语言js ...

  7. 【转载】C/C++语言void及void指针深层探索

    C/C++语言void及void指针深层探索 1.概述许多初学者对C/C++语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误.本文将对void关键字的深刻含义进行解说,并详述vo ...

  8. 函数指针玩得不熟,就不要自称为C语言高手(函数指针是解耦对象关系的最佳利器,还有signal)

    记得刚开始工作时,一位高手告诉我说,longjmp和setjmp玩得不熟,就不要自称为C语言高手.当时我半信半疑,为了让自己向高手方向迈进,还是花了一点时间去学习longjmp和setjmp的用法.后 ...

  9. C语言中的函数指针

    C语言中的函数指针 函数指针的概念:   函数指针是一个指向位于代码段的函数代码的指针. 函数指针的使用: #include<stdio.h> typedef struct (*fun_t ...

随机推荐

  1. SQL语句流程函数

    本人因为今天用到了流程函数,顿时感觉语法生疏啊,为了防止以后忘记,故写此篇!!! 流程函数是MySQL相对常用的一类函数, 用户可以使用这类函数在一个SQL语句中实现条件选择, 这样能够提高效率. 下 ...

  2. Bootsrap 的 Carousel

    一.简介 Carousel 就是指轮播图,这里 有完整的代码例子.它可以很简单的就构造出来,结构如下: div.carousel.slide[data-ride="carousel" ...

  3. SQL Server中的Merge关键字 更新表数据

    简介 Merge关键字是一个神奇的DML关键字.它在SQL Server 2008被引入,它能将Insert,Update,Delete简单的并为一句.MSDN对于Merge的解释非常的短小精悍:”根 ...

  4. CSS中的剪裁和遮罩

    剪裁和遮罩都是用来隐藏元素的一些部分.显示其他部分的.当然了,这两者还是有区别的.区别主要在于这几方面:他们能做的东西,不同的语法,涉及到的不同技术,是新的还是旧的,以及浏览器支持的差异. 但不幸的是 ...

  5. 【已解决】Windows下 MySQL大小写敏感 解决方案及分析

    Windows下 MySQL大小写敏感配置 zoerywzhou@163.com http://www.cnblogs.com/swje/ 作者:Zhouwan 2017-3-27 最近在window ...

  6. [麻雀虽小]记 简易Markdown阅读器 开发全过程

    [麻雀虽小]记 简易Markdown阅读器 开发全过程 [TOC] 序言 项目地址: https://github.com/didikee/MDReader 测试文章地址: 2017 Android ...

  7. python之字符串

    字符串与文本操作 字符串: Python 2和Python 3最大的差别就在于字符串 Python 2中字符串是byte的有序序列 Python 3中字符串是unicode的有序序列 字符串是不可变的 ...

  8. 更换gitlab公网IP,引发的故障。

    gitlab更换公网IP地址,导致gitlab非常的很卡,并且ssh方式添加的远程仓库是无法git pull 或者git push,只有是添加http方式的可以正常git pull和git push ...

  9. 老李分享:走读unittest源码

    老李分享:走读unittest源码   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.poptest测试开发工程师就业培训感兴趣 ...

  10. WebApp框架

    我所知道的webapp开发框架,欢迎补充, Framework7包含ios和material两种主题风格并且有vue版和react版, vue发现一个vue-material, react有一款mat ...