1.对于变量、指针和指针变量的关系,大家可以看看下面的图:

指针的概念:

指针与指针变量:

大家要注意:

a.普通变量存储数值,指针变量存储地址,即指针

b.指针变量的数据类型决定了其寻址范围的大小

c.通过变量来访问变量的存储空间叫做直接访问;先获取其地址,再根据地址找到存储单元叫做间接访问。

d.只要是指针变量,无论是什么类型,在32位的CPU下,都占4个字节。

大家看看下面这个例子:

很简单,对于指针变量,刚刚已经说得很明确,都是4个字节(32位CPU下);而对于其他的普通变量,该是什么类型,就是什么类型,所以,打印:

大家要知道,int *p中的*仅仅是告诉编译器p是一个指针变量用来存储地址的,如:int a = 10;那么有a = *( &a );指针变量的数据类型本质上就是其指向对象的类型。

我们操作指针的时候要注意:

  1. 定义什么样的指针变量(保存什么样的变量地址)由一次操作的几个内存单元决定

  2. 要怎样获取合法的地址

  3. 怎样去读、写

2.在嵌入式开发中,对于ARM裸板操作,经常用到强制类型转换,例如:

# define  GPFCON  *( (volatile unsigned *)0x56000050 )

这个例子是将一个地址与GPFCON绑定,实现操作GPFCON就是操作0x56000050这个地址,我们一起来看看,这是怎样实现的!

首先,0x56000050是一个具体的十六进制数值,(volatile unsigned *)0x56000050,这条语句是将这个具体的数值转化成了一个无符号的地址,不过这个地址不能被编译器优化,最后,在前面还有一个星号*,表示解指针,说明取的是内容,那么这句话就可以这样理解了:在内存中有一块内存,地址(逻辑地址)是0x56000050,里面的内容是空的,然后,我们用GPFCON来给它定义一下,这样,操作GPFCON就是操作逻辑地址0x56000050了。

还是上面那个例子,我们来稍稍改动一下:

我们一起来分析一下这个打印结果:

大家先要了解CPU的存储模式,即字节序,也就是大家常说的大端小端模式,记住八个字:

小端模式:高高低低;大端模式:高低低高

小端模式下是高位存储在高地址,低位存储在低地址;大段模式下高位存储在低地址,低位存储在高地址。

而我们一般的英特尔CPU则是小端模式,至于为什么,以后会有解释。就是符合上面的高高低低原则。

所以对于0x1234,34是低位,12是高位,所以存储的顺序是34 12,这有两个字节,而char则是一个字节的,所以截取了前面的高字节34,所以打印0x34。而刚刚说了的指针变量在32位的cpu下,都是4个字节,所以,都可以打印出来,对于上面的加一操作,只要记住指针变量的加一是加的该指针变量内存单元的长度即可。

3.在用指针操作内存单元的时候,我们会经常发现一个很纠结的错误,那就是段错误:segment fault,是由于虚拟内存管理单元的异常所致,而该异常则通常是由于解引用一个未初始化或非法值的指针引起的。在gcc下,我们可以用GDB进行调试,调试的步骤如下:

执行命令:

$ ulimit –c             //查看有没有限制,如果结果为0,则有限制

$ ulimit –c unlimited     //去掉限制

$ ulimit –c             //再次查看,结果应该为unlimited

$ gcc –g xxx.c          // xxx.c表示c源文件,声称调试文件

$ ls                   // ls查看有没有生成调试用的core文件

$ ./a.out               //执行可执行文件,会报段错误

$ gdb a.out core        //用GDB调试,在调试命令中输入r,然后回车两次即可看到在哪儿发生了段错误。

在我们写代码的时候,经常会有人将“NULL”和“NUL”混淆,一个“L”的NULL用于结束一个ASCII字符码,两个“L”的NULL用于表示什么也指向,即空指针。

4.大家要区分好*p, *p++,(*p)++, *++p, ++*p这几个的不同:

*p++先取指针p指向的值,再将指针p自增1;

(*p)++先去指针p指向的值,再将该值自增1;

*++p先将指针p自增1(此时指向数组第二个元素),*操作再取出该值

++*p先取指针p指向的值,再将该值自增1

大家在用程序进行测试的时候,记得使用不同的指针变量,还是上次说的,指针进行自增或者自减运算,其地址都会改变。很简单,这里就不举例子了!

5.总结一下,我们在操作指针的时候要注意的问题:

a.指针变量存的是地址

b.指针变量的数据类型决定其寻址范围

c.指针变量的算术运算,加减常数指的是元素个数

d.注意指针的指向,一般初始化为NULL

e.定义什么样的指针变量由一次操作几个内存单元决定

f.指针变量相互赋值要类型匹配

g.注意指针的当前值。即++或者- -之后,地址会发生相应的改变

h.连续的地址相减的值为元素的个数

6.大家看看这个程序:

没有什么实际作用,仅仅是为了测试所用。打印:

由结果可以得出下面的结论:

a + i  <=>  &a[i];

a[i]  <=>  *(a + i)  <=> *(p + i)  <=>  p[ i];

注意,这只是针对于一维数组。至于二维数组,待会儿会专门讲解。

7.大家对于二维数组,是怎么理解的?看看下面的图:

二维数组的理解:

二维数组元素的引用:

对于二维数组a[3][4],其内部的存储结构图有:

我们要记住二维数组和指针的关系:

*行指针=列指针;&列指针=行指针

例如,对于上面的a[3][4]数组,如果我们想要访问a[2][2],则我们可以访问以下的地址,它们都是等效的:

&a[2][2],a[2]+2,a[0]+2*4+2,(int *)(a+2)+2,*(&a[0]+2)+2

我们也可以直接使用下面的值,也是等效的:

a[2][2],*(a+2)+2,*a+2*4+2

在二维数组a[3][4]中,若有int *p1 = a[0], *p2 = a[1], *p3 = a[2];我们可以用一个数组表示:int *p[3] = {a[0], a[1], a[2]};这种结构就是传说中的指针数组,大家这样理解:指针数组,重点是数组,只是里面的元素是指针罢了,可以这样看:int*  p[3] = {a[0, a[1], a[2]]};每个元素都是指针变量,而对应的p则是一个二级指针常量。

当然,与之对应的还有一个数组指针:int (*p)[3];数组指针,实质上还是一个指针,只不过指向了一个数组,p是一个数组指针,指向整个数组。

其实类似的还有很多,什么数组指针数组等等。它们的重点都是在最后,其实都不是很难,只是数组或者指针的一种组合而已。

8.下面的一个代码中有很多陷阱,等着我们跳,你会掉坑里吗?

# include <stdio.h>

# include <stdlib.h>

# include <string.h>

/*

程序首先申请一个char类型的指针str,并把str指向NULL(即str里存的是NULL的地址,*strNULL中的值为0),调用函数的过程中做了如下动作:

   1、申请一个char类型的指针p,

   2、把str的内容copy(传)到了p里(这是参数传递过程中系统所做的),

   3、为p指针申请了100个空间,

   4、返回Test函数.最后程序把字符串hello world拷贝到str指向的内存空间里.到这里错误出现了!str 的空间始终为 NULL 而并没有实际 的空间.深刻理解函数调用的第 2 步,将不难发现问题所在!(注意:传递 的参数和消除的参数)

*/

/*

void GetMem(char *p)

{

   printf("p = str = %p\n",p);

   p = (char *)malloc(100);

   strcpy(p,"hello world\n");

   printf(p);

   printf("p = %p\n",p);

}

void Test(void)

{

   char *str;

   GetMem(str);

//  str = (char *)malloc(100);

   strcpy(str,"hello!\n");

   printf("str = %p\n",str);

}

*/

/*

char *GetMem(void)

{

   char p[] = "hello world\n";

//  printf(p);

//  printf("%p",p);

   return p;

//  free(p);           //数组里面的内容已经被释放,

                          //只是返回一个地址,并没有内容

}

void Test()

{

   char *str = NULL;

   str = GetMem();

   printf(str);

//  printf("str = %p\n",str);

}

*/

/*

void GetMem(char **p, int n)

{

   *p = (char *)malloc(n);

}

void Test(void)     //内存泄漏,需要用free来释放内存空间

{

   char *str = NULL;

   GetMem(&str,100);

   strcpy(str,"hello\n");

   printf(str);

//  free(str);

}

*/

void Test(void)

{

   char *str = (char *)malloc(7);

   strcpy(str,"hello\n");

   free(str);

   printf(str);

   if(str != NULL)

   {

       strcpy(str,"world\n");

       printf(str);

   }

}

int main()

{

   Test();

   return 0;

}

/*

1、C中内存分为四个区

栈:用来存放函数的形参和函数内的局部变量。由编译器分配空间,在函数执行完后由编译器自动释放。

堆:用来存放由动态分配函数(如malloc)分配的空间。是由程序员自己手动分配的,并且必须由程序员使用free释放。如果忘记用free释放,会导致所分配的空间一直占着不放,导致内存泄露。堆,顺序随意。栈,后进先出(Last-In/First-Out)。

全局区(静态区):用来存放全局变量和静态变量。存在于程序的整个运行期间,是由编译器分配和释放的。

文字常量区:例如char *c =“123456”;则”123456”为文字常量,存放于文字常量区。也由编译器控制分配和释放。

程序代码区:用来存放程序的二进制代码。

2、内存分配方式

内存分配方式有三种:

1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整

个运行期间都存在。例如全局变量,static变量。

2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数

执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

3)从堆上分配,亦称动态内存分配。程序在运行的时候用mallocnew申请任意多少的内存,程序员自己负责在何时用freedelete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

3、常见的内存错误及其对策

发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。常见的内存错误及其对策如下:

1)内存分配未成功,却使用了它。

编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用mallocnew来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。

2)内存分配虽然成功,但是尚未初始化就引用它。

犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

3)内存分配成功并且已经初始化,但操作越过了内存的边界。

例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

4)忘记了释放内存,造成内存泄露。

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。动态内存的申请与释放必须配对,程序中mallocfree的使用次数一定要相同,否则肯定有错误(new/delete同理)。

【规则1】用mallocnew申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

【规则3】注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

【规则4】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。

【规则5】动态内存的申请与释放必须配对,防止内存泄漏。

【规则6】freedelete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

4、动态分配释放内存举例

malloc动态分配内存后一定要判断一下分配是否成功,判断指针的值是否为NULL。

内存分配成功后要对内存单元进行初始化。

内存分配成功且初始化后使用时别越界了。

内存使用完后要用free(p)释放,注意,释放后,p的值是不会变的,仍然是一个地址值,仍然指向那块内存区,只是这块内存区的值变成垃圾了。为了防止后面继续使用这块内存,应在free(p)后,立即p=NULL,这样后面如果要使用,判断p是否为NULL时就会判断出来。

*/