考虑到指针内容繁多,这里将指针作为一个系列,从简入繁,带着没有研究过指针的朋友,一点一点深挖并掌握这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. NSTimer的精确度

    1.iOS中一般UI上面常用两种定时器 NSTimer和CADisplayLink,那么它们分别的精确度是如何呢? CADisplayLink 是用于帧刷新定时器,也就是和界面的刷新率保持一致,理想情 ...

  2. 【iOS】7.4 定位服务->2.1.3.1 定位 - 官方框架CoreLocation 功能1:地理定位

    本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正. 本文相关目录: ================== 所属文集:[iOS]07 设备工具 === ...

  3. shell 之时间戳

    vim 1.sh #/bin/bash##by cc read -p "Please input yourtime:" timea=$timeif [ $a != 0 ] then ...

  4. 用js控制css属性

    在用js控制css属性时,行内css属性可以任意控制,但若是在<style></style>中写的css属性,均不能用alert读取,但是赋值却有几种现象, 第一种:无法读取, ...

  5. vpn的实现原理

    由于公共IP的短缺,我们在组建局域网时,通常使用保留地址作为内部IP,(比如最常用的C类保留地址:192.168.0.0-192.168.255.255)这些地址是不会被互联网分配的,因此它们在互联网 ...

  6. 适用MySQL Migration Toolkit 1.0 将oracle迁移到mysql中遇到的问题

    这里主要说一下我在适用中碰到的问题,主要过程参考 http://www.cnblogs.com/duwenlei/p/3520759.html. 首先启动MySQLMigrationTool.exe ...

  7. Python中的内置函数__init__()的理解

    有点意思,本来我是学习java的.总所周知,java也有构造函数,而python在面向对象的概念中,也有构造函数.它就是 __init__(self) 方法. 其实类似于__init__()这种方法, ...

  8. 基于微软开发平台构建和使用私有NuGet托管库

    本篇blog包含使用TFS2017,VS2017等平台和工具搭建和使用NuGet库等基本过程,为团体提供更加自动化和高效的研发活动支持. 作为以产品线或者以专属业务为扩展的项目类型的软件研发团体,都会 ...

  9. box-shadow IE8兼容处理

    根据canisue(http://caniuse.com/#search=box-shadow),box-shadow兼容性如下图所示: 测试代码: <!DOCTYPE html> < ...

  10. CSS3 Columns:比table更好用的分列式布局方法

    CSS里一直有一个让我们头疼的问题,就是创建布局很麻烦.当然,有很多方式,有很多技术都可以创建各种布局,但我们总觉得CSS里应该提供一些新属性,让我们能更好的管理布局.幸运的是,CSS3里提供了一批新 ...