gdb调试&C语言指针高级编程(5.7)
2015-05-07 20:22
204 查看
GCC编译器简介:
一、基本知识了解
GCC是由gun之父Stallman所开发的linux下的编译器。全程为GNU C Complier
(GNU c语言编译器)。这是一种早期的说法,早期的时候,它只是用来编译我们的C
语言,之后可以多个我们的多门语言都可以进行编译。例如, C,C++,JAVA,ObjectC等等。
GCC是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%。
二、Gcc所支持的源代码格式
GCC是一个交叉平台的编译器,目前支持几乎所有主流CPU处理器平台,它可以完成从C、C++、Objective C等源文件向运行在特定cpu硬件上的目标代码的转换,目前编译器所能支持的源程序的格式如下。
后缀格式 说明
.C C语言程序
.cc/cpp C++程序
.m Object-C的原始程序
.i预处理过的C语言程序
.s汇编语言的原始程序
.o目标文件
.a/.so 编译后的库文件
三、gcc编译的流程分析
用gcc编译过C语言的同学应该都知道,我们使用gcc编译可执行文件的时候,看起来似乎仅仅通过了编译这一步就完成了。具体流程如下图。
hello.c
运行结果:
上面的步骤看起来似乎很简单那。但是,事实上,我们GCC编译器工具由C语言转换成可执行文件的过程并不单单的就是一个编译的过程。而是要经过以下几个步骤。
预处理------>编译------>汇编-------->链接
预处理阶段:就是对我们的源代码进行编译前,对我们的头文件或者宏定义进行解析。
编译:这个阶段注意是检查代码的规范性,是否有语法错误等。以确定代码的实际要做的工作。在检查无误后,就把代码翻译成汇编代码。
汇编:这个阶段就是把我们的已经生成好了的汇编代码转换成我们的目标文件(二进制)。
链接:生成可执行代码。链接所需要的库文件。普通c程序默认到/usr/lib中寻找链接的库。
四、参数分析及原理详解
刚刚我们讲解的知识是从原理的角度来给大家说明了,我们gcc的编译流程。可能大家觉得还是比较的抽象。下面,我们来从实际的代码演示的角度来看看。首先,需要了解几个选项。
<1>常用编译选项
-E 预处理选型,预处理操作的时候使用
-S 编译选项,把预处理过的代码编译成汇编代码
-c 汇编选项,生成我们的目标文件
-o 把编译的内容,指定保存到指定输出的文件。
-g 生成gdb调试所需要的符号信息
<2>代码分析
预处理
编译
汇编
链接
下面,我们来看看我们的gdb调试
Gdb调试简介
<1>C语言错误的分类
我们平时写代码的时候,写错字或者碰到一些错误的问题,总是必不可少的。那么我们该如何解决它呢?首先,我们来看看我们常见的错误类型。
(1)C语法错误
错误信息∶文件source.c中第n行有语法错误(syntex errror)
语法错误对我们来说,是由于我们对C语言的不熟悉,或者打字速度太快,不经意之间产生的。这种错误一般编译器会来帮我们检测出来。相对来说,比较容易解决。
(2)头文件错误
错误信息∶找不到头文件head.h(Can not find include file head.h)
一般来说,是我们调用的函数,没有添加头文件。例如,写了printf,却没有调用stdio.h这个头文件。或者头文件书写错误。这种也是比较容易解决的。编译器一般也会帮忙查出来。
(3)逻辑错误
这种错误是大家常常遇到的。一般来说,编译器编译通过了,并且生成了可执行文件,但是,我们执行之后,常常会发现执行的结果并不是我们想要的。这类的错误一般来说,很难查出来。
<2>gdb调试
针对逻辑错误,我们聪明的程序员开发出了一个工具,叫做GDB调试工具。类似与VC中的debug调试工具。
具体用法详解图片gdb.jpg gdb1.jpg
gdb调试段错误:
1、把core文件显示出来
ulimit -c unlimited 把core文件设置为无限大
2、编译程序 gcc -g hello.c
3、gdb调试它 gdb a.out core
调试命令如下:
l 列出源文件内容,方便设置断点
start开始调试
n(next)下一步
s(step)单步,跳进函数
bt(backtrace)查看函数调用的栈帧
f(frame) 1 选择1号栈帧
i(info) locals 查看1号栈帧的局部变量(即函数的局部变量),即main函数的局部变量
附:什么是栈帧,从逻辑上讲,栈帧就是一个函数执行的环境:
函数参数、函数的局部变量、函数执行完后返回到哪里等等。
每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。
p(print) sum 查看sum变量的值
finish 跳出当前函数,回到main函数
set var sum=0 修改变量sum的值为0
b(break) 9 在第9行设置一个断点 参数也可以是函数名;如b fun_name
i breakpoints 显示已经设置的断点
或info b
c(continue) 表示连续运行,跳到下一个断点,如果没有下一断点,运行直到结束。
delete breakpoints 2 删除断点2
或d 2
delete breakpoints 删除所有的断点
display sum 每次停下来都显示sum的值
info display 查看所有跟踪变量
undisplay 编号: 取消对这个变量的跟踪
r 重新从程序开始连续执行
二、C语言指针详解
(1)我们写程序的时候,做的最多的事情是干什么?
1、data--->ram
2、ram--->data
(2)C语言中提供内存分配的方法
<1>定义变量
a.定义局部变量--->函数调用结束的时候,内存会被释放掉
b.定义全局变量--->程序调用结束的时候,我们的程序会被释放掉。
<2>手动分配
(3)C语言运行时在内存中的分布
详见 黑板《内存分布图》
<3>C语言中变量的分配
(1)普通变量
作用:保存我们的数据
(2)指针变量
作用:保存我们的地址
<4>C语言中定义变量的方法
<1>普通变量
存储类型 数据类型 变量名;
<2>指针变量
存储类型 数据类型 *变量名;
存储类型:auto ,static ,resigter,extern
数据类型:
1.基本数据类型
整型:小整型或者字符型(char) ,短整型(short),整型(int ),long
浮点型:float,double
2.构造数据类型
数组,结构体,共用体,枚举
思考:我们的基本类型和我们构造类型有什么区别?
我们的基本数据类型大小是固定的,而我们构造类型懂得大小是由我们程序员自己决定的。
(5)C语言中对内存的读写
<1>通过内存标识符号(变量名)
int data;
data = 50; //写操作
printf("data = %d\n",data); //读操作
<2>通过内存地址来进行读写
&
int data;
data的地址为 &data
//写操作
*(&data) = 50;
printf("data = %d\n",*(&data)); //读操作
练习:
1、定义两个变量int a = 20,int b = 10;
定义两个指针p,q分别指向a,b
要求用指针变量实现C语言程序的加、减、乘、除
2、定义两个变量int a = 10,int b = 20;
定义两个指针p,q分别指向a,b,要求用p,q来实现a,b值的交换。
大家看看有几种方法。
二、C语言中特殊指针的分类
1、野指针:指向“垃圾”内存的指针
野指针形成的原因:没有指向有效的内存。
2、空指针/0/NULL
空指针就是一个被赋值为0的指针。linux系统中不允许我们普通用户访问。
3、void指针
void的字面意思是“无类型”,void *则为“无类型指针",void *可以指向任何类型的数据。
常用于接收任意类型的指针。
关于a和&a的详解:
一、前言
关于a和&a,有很多同学,不是很理解它的含义,为了帮助同学们理解,本人写了这个文档,帮助同学们加深理解。希望能帮助大家理解数组。
二、详解
<1>a地址详解
这里我们先看看数组名代表的是什么,这个概念可能大家有所误解,认为数组名代表的就是数组的地址,当然,数组名代表的是一个地址,但是关键是,通过这个地址,我们关注的是它能取得多大空间的数据的值。例如对于一个char 类型的地址,我们能够取得一个字节的值,对于一个int型的地址,我们能够取得4个字节的值。好了,下面我来谈谈类型的理解。
<1>C语言中的类型
char a ;====> a是char类型
short b; ====> b 是short类型
int c; ======> c是int类型
char *p; =====>p是int *类型
Short *p;=====>p是short *类型
Int *c ======> c是 int *类型
因此,我们可以得出一个结论:把变量名去掉,剩下的就是类型。
因此,我们的数组就可以说是一个特殊的类型。以int类型的数组为例,来说说我们
的a和&a.
例如:int a[4];
众所周知,a是一个地址。其实,a的本来的面目应该是a + 0,只不过,这个0我们常常
省略了,*(a + 0)表示什么?,这个就是代表我们取得第一个元素的数据,也就是a[0].
因此,我们可以理解为a代表了首元素的地址。也就是a[0]的地址。因此,我们得出了一个
结论:数组的数组名,代表了数组首元素的首地址。
int a[4]数组中,a[0]的数据类型为int 类型,它的地址就应该用int *来保存。
故a的类型应该用int *类型来保存。
<2>&a地址详解
理解了a,我们再来理解&a的话,相对来说就比较容易了。刚刚说了,我们变量名去掉,剩下的就是我们的类型。还是以int 类型的数组为例子来讲解。
例如:
char a; ===>&a表示的是我们的地址。&a +1一次移动一个char类型的
大小
short a; ===>&a表示的是我们的地址。&a +1一次移动一个short类型
的大小
int a; ===>&a表示的是我们的地址。&a +1一次移动一个int类型的
大小
同理:int a[4] ===>数组的类型为int [4];&a表示的就是我们int [4]的,整个变量的地址。把
Int [4]看成一个整体,&a + 1一次移动懂得大小为整个数组的大小,也就是20个字
节。&a可以理解成数组真正的地址。
通过上面的分析,&a就好理解了,他就是变量a的地址,这个变量a的作用范围是四个int空间的数据,也就是如果将&a赋给某个变量p,p的类型应该是:int (*)[4];也就是指向包含4个int数据的数组的指针,这个叫做数组指针。
再来看看a+1和&a+1的区别,a+1表示的是数组第一个元素地址,&a+1表示的是跨过a数组的下一个地址。
三、C语言中的一级指针变量
1、一级指针变量
本质:用来保存我们的地址。
定义的方法:
数据类型 * 变量名;
int *p;
思考:如何通过指针变量对一块内存进行读写操作?
int data = 10;
int *p = &data;
//写操作
*p = 40;
//读操作
printf("%d\n",*p);
规律:在32位的机器上,所有指针变量都是4个字节的大小
思考:C语言为什么要设计不同类型的指针变量?
C语言中不同类型的指针变量寻址的范围不一样。
练习:int a[5] = {1,2,3,4,5};
int *p;
要求p指向我们数组,求数组所有数据元素的和。
练习:我们定义一个数组unsigned char buf[10];
要求通过位移的方式,让我们的buf中依次存放了0x0,0x11,0x22,x033,...,0x99
buf[0]--->0x00
buf[1]--->0x11
然后依次从数组把每个数据给取出来,做累加和。0x00 + 0x11 + 0x22+。。+0x99
思考: 0====>0x00
1====>0x11
2====>0x22
0x11转化成二进制========》00010001
0x22转化成二进制========》00100010
四、C语言中的多级指针
设计规则:
一级指针变量用来保存我们普通变量的地址。
二级指针变量用来保存我们一级变量的地址。
三级指针变量用来保存我们二级变量的地址。
.....
结论:在32位机器中,多级指针变量的寻址范围都是一个地址的大小,4byte
五、一级指针和数组
1、一维数组:变量的集合,多个变量是连续存放的。
(1)定义
数据类型 数组名[数组元素的个数];
例如:int a[5];
<1>数组名的含义:数组的首地址,地址常量,不能修改
<2>表示数组中的元素
数组名[0],...数组名[数组元素的个数 - 1]
*(a + 0)
<3>如何获得数组的首地址
a.数组名
b.&数组名[0]
2、指针和数组
int a[3];
int *p = a;
通知指针访问数组的每一个元素:
a[0] <====>*a<======>*p <=======>p[0]
a[1] <====>*(a + 1)<======>*(p + 1) <=======>p[1]
区别:
p是指针变量可以修改,a为地址常量,不能修改
例如:p++ 正确
a ++ 错误
练习:
1.定义1个10个元素的整型数组,分别存放{1,3,5,7,8,2,4,6,8,10}
2.定义两个指针变量p_max,p_min,要求用这两个指针变量分别保存最大值和最小值
3.输出数组内容和最大值,最小值
六、指针数组和数组指针
指针数组:本质是一个数组,但是里面存放的数据全是指针类型。
即数组的元素全是指针。
例如: int *a[4];
思考:我们如何保存指针数组的首地址?
我们定义二级指针变量来保持指针数组的首地址。
经验:一般我们在指针数组ude时候,会在最后一个元素上写上NULL。
例如:int *p_arry = {addr1,addr2,..,NULL};
数组指针(行指针): 首先它是一个指针,它指向一个数组。
例如:
int (*a)[10];
指针数组和数组指针详解:
<1>指针数组
(1)简介
简介:本质是一个数组,但是里面存放的数据都是指针,即数组元素都是指针。它是
“存储指针的数组”的简称。
(2)内容格式
数据类型 * 数组名[元素个数];
例如:
int *a[4];
“[]"的优先级比我们的"*"的优先级高,所以[]与我们的4先结合,形成了我们的数组的定义,
数组名叫做a,我们平时定义数组的格式如下:
int b[10];
------>数组名b
------>数组里面元素的类型 int 类型
而指针数组
int * a[10];
---->数组名a
---->数组里面存储的元素的类型是int *型
那么我们清楚了,这是a一个数组,只不过是数组中包含了10个指向int类型数据的指针,及指针数组。
思考:如何保存指针数组的首地址
定义二级指针变量[实际上等价于保存的是第一个一级指针变量的地址]
经验:一般会在指针数组的最后一个元素写上NULL
int *p_arry[] = {addr1,addr2,...,NULL};
<2>数组指针
数组指针:首先它是一个指针,它指向一个数组。它是“指向数组的指针”的简称。
例如:int (*a)[10]
“()”的优先级与我们的"[]"一样,但是结合规律是从左到右,故我们的"*"与我们的a先结合形成了我们的指针的定义。指针的变量名叫做a,
我们平时定义指针的格式如下:
int *p;
------->指针名p
------->指针p的数据类型 int *;(就是把我们的的变量名,给去掉剩下的就是指针的类型)
------->指向了一个int类型的数据。
而我们的数组指针 int (*a)[10];
-------->指针名a
-------->指针a的数据类型int *[10](注:这种类型不存在,为了方便理解才写,这里没有数组名,是一个匿名数组)
-------->指向了含有10个int类型的数组。
那么我们清楚了,首先a是一个指针,只不过他指向一个包含10个int类型数据的数组,及数组指针。
如下图:
数组指针p2,一次整个数组的大小,即移动10个元素的大小。
<3>代码详解
test1.c
运行结果:
test2.c
运行结果:
内存图解分析:
方法1:
a[i] 直接通过 %s 打印 0x11223344地址的内容。
方法2:
q为二级指针,
*q<===>a[i]
方法3:
直接通过*q输出
作业:
int *p = NULL;
char **q = NULL;
char *a[] = {"nanjing","shanghai","wuhan",NULL};
int i = 0;
p = a[0];
q = a;
要求通过指针数组,一级指针,二级指针的方式来输出以上元素。
要求输出的结果为 nanjing shanghai wuhan
一、基本知识了解
GCC是由gun之父Stallman所开发的linux下的编译器。全程为GNU C Complier
(GNU c语言编译器)。这是一种早期的说法,早期的时候,它只是用来编译我们的C
语言,之后可以多个我们的多门语言都可以进行编译。例如, C,C++,JAVA,ObjectC等等。
GCC是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%。
二、Gcc所支持的源代码格式
GCC是一个交叉平台的编译器,目前支持几乎所有主流CPU处理器平台,它可以完成从C、C++、Objective C等源文件向运行在特定cpu硬件上的目标代码的转换,目前编译器所能支持的源程序的格式如下。
后缀格式 说明
.C C语言程序
.cc/cpp C++程序
.m Object-C的原始程序
.i预处理过的C语言程序
.s汇编语言的原始程序
.o目标文件
.a/.so 编译后的库文件
三、gcc编译的流程分析
用gcc编译过C语言的同学应该都知道,我们使用gcc编译可执行文件的时候,看起来似乎仅仅通过了编译这一步就完成了。具体流程如下图。
hello.c
运行结果:
上面的步骤看起来似乎很简单那。但是,事实上,我们GCC编译器工具由C语言转换成可执行文件的过程并不单单的就是一个编译的过程。而是要经过以下几个步骤。
预处理------>编译------>汇编-------->链接
预处理阶段:就是对我们的源代码进行编译前,对我们的头文件或者宏定义进行解析。
编译:这个阶段注意是检查代码的规范性,是否有语法错误等。以确定代码的实际要做的工作。在检查无误后,就把代码翻译成汇编代码。
汇编:这个阶段就是把我们的已经生成好了的汇编代码转换成我们的目标文件(二进制)。
链接:生成可执行代码。链接所需要的库文件。普通c程序默认到/usr/lib中寻找链接的库。
四、参数分析及原理详解
刚刚我们讲解的知识是从原理的角度来给大家说明了,我们gcc的编译流程。可能大家觉得还是比较的抽象。下面,我们来从实际的代码演示的角度来看看。首先,需要了解几个选项。
<1>常用编译选项
-E 预处理选型,预处理操作的时候使用
-S 编译选项,把预处理过的代码编译成汇编代码
-c 汇编选项,生成我们的目标文件
-o 把编译的内容,指定保存到指定输出的文件。
-g 生成gdb调试所需要的符号信息
<2>代码分析
预处理
编译
汇编
链接
下面,我们来看看我们的gdb调试
Gdb调试简介
<1>C语言错误的分类
我们平时写代码的时候,写错字或者碰到一些错误的问题,总是必不可少的。那么我们该如何解决它呢?首先,我们来看看我们常见的错误类型。
(1)C语法错误
错误信息∶文件source.c中第n行有语法错误(syntex errror)
语法错误对我们来说,是由于我们对C语言的不熟悉,或者打字速度太快,不经意之间产生的。这种错误一般编译器会来帮我们检测出来。相对来说,比较容易解决。
(2)头文件错误
错误信息∶找不到头文件head.h(Can not find include file head.h)
一般来说,是我们调用的函数,没有添加头文件。例如,写了printf,却没有调用stdio.h这个头文件。或者头文件书写错误。这种也是比较容易解决的。编译器一般也会帮忙查出来。
(3)逻辑错误
这种错误是大家常常遇到的。一般来说,编译器编译通过了,并且生成了可执行文件,但是,我们执行之后,常常会发现执行的结果并不是我们想要的。这类的错误一般来说,很难查出来。
<2>gdb调试
针对逻辑错误,我们聪明的程序员开发出了一个工具,叫做GDB调试工具。类似与VC中的debug调试工具。
具体用法详解图片gdb.jpg gdb1.jpg
gdb调试段错误:
1、把core文件显示出来
ulimit -c unlimited 把core文件设置为无限大
2、编译程序 gcc -g hello.c
3、gdb调试它 gdb a.out core
调试命令如下:
l 列出源文件内容,方便设置断点
start开始调试
n(next)下一步
s(step)单步,跳进函数
bt(backtrace)查看函数调用的栈帧
f(frame) 1 选择1号栈帧
i(info) locals 查看1号栈帧的局部变量(即函数的局部变量),即main函数的局部变量
附:什么是栈帧,从逻辑上讲,栈帧就是一个函数执行的环境:
函数参数、函数的局部变量、函数执行完后返回到哪里等等。
每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。
p(print) sum 查看sum变量的值
finish 跳出当前函数,回到main函数
set var sum=0 修改变量sum的值为0
b(break) 9 在第9行设置一个断点 参数也可以是函数名;如b fun_name
i breakpoints 显示已经设置的断点
或info b
c(continue) 表示连续运行,跳到下一个断点,如果没有下一断点,运行直到结束。
delete breakpoints 2 删除断点2
或d 2
delete breakpoints 删除所有的断点
display sum 每次停下来都显示sum的值
info display 查看所有跟踪变量
undisplay 编号: 取消对这个变量的跟踪
r 重新从程序开始连续执行
二、C语言指针详解
(1)我们写程序的时候,做的最多的事情是干什么?
1、data--->ram
2、ram--->data
(2)C语言中提供内存分配的方法
<1>定义变量
a.定义局部变量--->函数调用结束的时候,内存会被释放掉
b.定义全局变量--->程序调用结束的时候,我们的程序会被释放掉。
<2>手动分配
(3)C语言运行时在内存中的分布
详见 黑板《内存分布图》
<3>C语言中变量的分配
(1)普通变量
作用:保存我们的数据
(2)指针变量
作用:保存我们的地址
<4>C语言中定义变量的方法
<1>普通变量
存储类型 数据类型 变量名;
<2>指针变量
存储类型 数据类型 *变量名;
存储类型:auto ,static ,resigter,extern
数据类型:
1.基本数据类型
整型:小整型或者字符型(char) ,短整型(short),整型(int ),long
浮点型:float,double
2.构造数据类型
数组,结构体,共用体,枚举
思考:我们的基本类型和我们构造类型有什么区别?
我们的基本数据类型大小是固定的,而我们构造类型懂得大小是由我们程序员自己决定的。
(5)C语言中对内存的读写
<1>通过内存标识符号(变量名)
int data;
data = 50; //写操作
printf("data = %d\n",data); //读操作
<2>通过内存地址来进行读写
&
int data;
data的地址为 &data
//写操作
*(&data) = 50;
printf("data = %d\n",*(&data)); //读操作
练习:
1、定义两个变量int a = 20,int b = 10;
定义两个指针p,q分别指向a,b
要求用指针变量实现C语言程序的加、减、乘、除
#include <stdio.h> int main() { int a = 20,b = 10; int sum = 0; int *p = &a; int *q = &b; sum = *p + *q; printf("*p + *q = %d\n",sum); sum = *p - *q; printf("*p - *q = %d\n",sum); sum = *p * *q; printf("*p * *q = %d\n",sum); sum = *p / *q; printf("*p / *q = %d\n",sum); return 0; }
2、定义两个变量int a = 10,int b = 20;
定义两个指针p,q分别指向a,b,要求用p,q来实现a,b值的交换。
大家看看有几种方法。
#include <stdio.h> int main() { int a = 10,b = 20; int *p = &a; int *q = &b; int temp = 0; printf("a : %d b : %d\n",a,b); temp = *p; *p = *q; *q = temp; printf("a : %d b : %d\n",a,b); a = 10; b = 20; *p ^= *q; // *p = *p ^ *q *q ^= *p; // *q = *q ^ *p ===> *q = *q ^ ( *p ^ *q) ==>*q = *p; *p ^= *q; // *p = *p ^ *q ===> *p = *p ^ (*q ^ *p) ==>*p = *q; printf("a : %d b : %d\n",a,b); a = 10; b = 20; *p = *p - *q; *q = *p + *q; //*q = (*p - *q) + *q ===>*q = *p *p = *q - *p; //*p = *p - (*p - *q) ===>*p = *q; printf("a : %d b : %d\n",a,b); return 0; }
二、C语言中特殊指针的分类
1、野指针:指向“垃圾”内存的指针
野指针形成的原因:没有指向有效的内存。
2、空指针/0/NULL
空指针就是一个被赋值为0的指针。linux系统中不允许我们普通用户访问。
3、void指针
void的字面意思是“无类型”,void *则为“无类型指针",void *可以指向任何类型的数据。
常用于接收任意类型的指针。
关于a和&a的详解:
一、前言
关于a和&a,有很多同学,不是很理解它的含义,为了帮助同学们理解,本人写了这个文档,帮助同学们加深理解。希望能帮助大家理解数组。
二、详解
<1>a地址详解
这里我们先看看数组名代表的是什么,这个概念可能大家有所误解,认为数组名代表的就是数组的地址,当然,数组名代表的是一个地址,但是关键是,通过这个地址,我们关注的是它能取得多大空间的数据的值。例如对于一个char 类型的地址,我们能够取得一个字节的值,对于一个int型的地址,我们能够取得4个字节的值。好了,下面我来谈谈类型的理解。
<1>C语言中的类型
char a ;====> a是char类型
short b; ====> b 是short类型
int c; ======> c是int类型
char *p; =====>p是int *类型
Short *p;=====>p是short *类型
Int *c ======> c是 int *类型
因此,我们可以得出一个结论:把变量名去掉,剩下的就是类型。
因此,我们的数组就可以说是一个特殊的类型。以int类型的数组为例,来说说我们
的a和&a.
例如:int a[4];
众所周知,a是一个地址。其实,a的本来的面目应该是a + 0,只不过,这个0我们常常
省略了,*(a + 0)表示什么?,这个就是代表我们取得第一个元素的数据,也就是a[0].
因此,我们可以理解为a代表了首元素的地址。也就是a[0]的地址。因此,我们得出了一个
结论:数组的数组名,代表了数组首元素的首地址。
int a[4]数组中,a[0]的数据类型为int 类型,它的地址就应该用int *来保存。
故a的类型应该用int *类型来保存。
<2>&a地址详解
理解了a,我们再来理解&a的话,相对来说就比较容易了。刚刚说了,我们变量名去掉,剩下的就是我们的类型。还是以int 类型的数组为例子来讲解。
例如:
char a; ===>&a表示的是我们的地址。&a +1一次移动一个char类型的
大小
short a; ===>&a表示的是我们的地址。&a +1一次移动一个short类型
的大小
int a; ===>&a表示的是我们的地址。&a +1一次移动一个int类型的
大小
同理:int a[4] ===>数组的类型为int [4];&a表示的就是我们int [4]的,整个变量的地址。把
Int [4]看成一个整体,&a + 1一次移动懂得大小为整个数组的大小,也就是20个字
节。&a可以理解成数组真正的地址。
通过上面的分析,&a就好理解了,他就是变量a的地址,这个变量a的作用范围是四个int空间的数据,也就是如果将&a赋给某个变量p,p的类型应该是:int (*)[4];也就是指向包含4个int数据的数组的指针,这个叫做数组指针。
再来看看a+1和&a+1的区别,a+1表示的是数组第一个元素地址,&a+1表示的是跨过a数组的下一个地址。
三、C语言中的一级指针变量
1、一级指针变量
本质:用来保存我们的地址。
定义的方法:
数据类型 * 变量名;
int *p;
思考:如何通过指针变量对一块内存进行读写操作?
int data = 10;
int *p = &data;
//写操作
*p = 40;
//读操作
printf("%d\n",*p);
规律:在32位的机器上,所有指针变量都是4个字节的大小
思考:C语言为什么要设计不同类型的指针变量?
C语言中不同类型的指针变量寻址的范围不一样。
练习:int a[5] = {1,2,3,4,5};
int *p;
要求p指向我们数组,求数组所有数据元素的和。
#include <stdio.h> int main() { int a[5] = {1,2,3,4,5}; int *p = a; int i = 0,sum = 0; for(i = 0;i < sizeof(a) / sizeof(a[0]);i++) { //方法1: /*sum += *p; */ /*p++;*/ //方法2: sum += *(p + i); } //思考:这两种方法有什么区别? printf("sum : %d\n",sum); }
练习:我们定义一个数组unsigned char buf[10];
要求通过位移的方式,让我们的buf中依次存放了0x0,0x11,0x22,x033,...,0x99
buf[0]--->0x00
buf[1]--->0x11
然后依次从数组把每个数据给取出来,做累加和。0x00 + 0x11 + 0x22+。。+0x99
思考: 0====>0x00
1====>0x11
2====>0x22
0x11转化成二进制========》00010001
0x22转化成二进制========》00100010
#include <stdio.h> #define MAX 10 int main() { int i = 0; int sum = 0; unsigned char temp = 0; unsigned char buf[MAX] = {0}; for(i = 0;i < MAX;i++) { buf[i] = (i << 4) | i; printf("%#x ",buf[i]); } putchar('\n'); for(i = 0;i < MAX;i++) { sum += buf[i]; } printf("sum : %#x\n",sum); //要求将buf的内容,逆置 //buf : 0x99,0x88,0x77,0x00 //buf[0] ====>0x99 //buf[1] ====>0x88 unsigned char *head = NULL; unsigned char *tail = NULL; head = &buf[0]; tail = &buf[9]; while(head < tail) { temp = *head; *head = *tail; *tail = temp; head++; tail--; } for(i = 0;i < MAX;i++) { printf("%#x ",buf[i]); } putchar('\n'); return 0; }
四、C语言中的多级指针
设计规则:
一级指针变量用来保存我们普通变量的地址。
二级指针变量用来保存我们一级变量的地址。
三级指针变量用来保存我们二级变量的地址。
.....
结论:在32位机器中,多级指针变量的寻址范围都是一个地址的大小,4byte
五、一级指针和数组
1、一维数组:变量的集合,多个变量是连续存放的。
(1)定义
数据类型 数组名[数组元素的个数];
例如:int a[5];
<1>数组名的含义:数组的首地址,地址常量,不能修改
<2>表示数组中的元素
数组名[0],...数组名[数组元素的个数 - 1]
*(a + 0)
<3>如何获得数组的首地址
a.数组名
b.&数组名[0]
#include <stdio.h> int main() { char a[5]; char *p = a; /*scanf("%s",p);*/ /*scanf("%4[^\n]",p); //这里的4表示只取前4个数据,^\n表示直到遇到'\n'为止,你可以一直输入*/ /*p[4] = '\0';*/ while((*p ++ = getchar()) != '\n' && p < a + 5); p--; *p = '\0'; p = a; printf("buf : %s\n",p); return 0; }
2、指针和数组
int a[3];
int *p = a;
通知指针访问数组的每一个元素:
a[0] <====>*a<======>*p <=======>p[0]
a[1] <====>*(a + 1)<======>*(p + 1) <=======>p[1]
区别:
p是指针变量可以修改,a为地址常量,不能修改
例如:p++ 正确
a ++ 错误
练习:
1.定义1个10个元素的整型数组,分别存放{1,3,5,7,8,2,4,6,8,10}
2.定义两个指针变量p_max,p_min,要求用这两个指针变量分别保存最大值和最小值
3.输出数组内容和最大值,最小值
#include <stdio.h> int main() { int a[10] = {1,3,5,7,9,2,4,6,8,10}; int *p_max = NULL; int *p_min = NULL; int i = 0; p_max = p_min = a; for(i = 0;i < 10;i++) { if(a[i] > *p_max) p_max = &a[i]; // *p_max = a[i]; if(a[i] < *p_min) p_min = &a[i]; } printf("max : %d min : %d\n",*p_max,*p_min); return 0; }
六、指针数组和数组指针
指针数组:本质是一个数组,但是里面存放的数据全是指针类型。
即数组的元素全是指针。
例如: int *a[4];
思考:我们如何保存指针数组的首地址?
我们定义二级指针变量来保持指针数组的首地址。
经验:一般我们在指针数组ude时候,会在最后一个元素上写上NULL。
例如:int *p_arry = {addr1,addr2,..,NULL};
数组指针(行指针): 首先它是一个指针,它指向一个数组。
例如:
int (*a)[10];
指针数组和数组指针详解:
<1>指针数组
(1)简介
简介:本质是一个数组,但是里面存放的数据都是指针,即数组元素都是指针。它是
“存储指针的数组”的简称。
(2)内容格式
数据类型 * 数组名[元素个数];
例如:
int *a[4];
“[]"的优先级比我们的"*"的优先级高,所以[]与我们的4先结合,形成了我们的数组的定义,
数组名叫做a,我们平时定义数组的格式如下:
int b[10];
------>数组名b
------>数组里面元素的类型 int 类型
而指针数组
int * a[10];
---->数组名a
---->数组里面存储的元素的类型是int *型
那么我们清楚了,这是a一个数组,只不过是数组中包含了10个指向int类型数据的指针,及指针数组。
思考:如何保存指针数组的首地址
定义二级指针变量[实际上等价于保存的是第一个一级指针变量的地址]
经验:一般会在指针数组的最后一个元素写上NULL
int *p_arry[] = {addr1,addr2,...,NULL};
<2>数组指针
数组指针:首先它是一个指针,它指向一个数组。它是“指向数组的指针”的简称。
例如:int (*a)[10]
“()”的优先级与我们的"[]"一样,但是结合规律是从左到右,故我们的"*"与我们的a先结合形成了我们的指针的定义。指针的变量名叫做a,
我们平时定义指针的格式如下:
int *p;
------->指针名p
------->指针p的数据类型 int *;(就是把我们的的变量名,给去掉剩下的就是指针的类型)
------->指向了一个int类型的数据。
而我们的数组指针 int (*a)[10];
-------->指针名a
-------->指针a的数据类型int *[10](注:这种类型不存在,为了方便理解才写,这里没有数组名,是一个匿名数组)
-------->指向了含有10个int类型的数组。
那么我们清楚了,首先a是一个指针,只不过他指向一个包含10个int类型数据的数组,及数组指针。
如下图:
数组指针p2,一次整个数组的大小,即移动10个元素的大小。
<3>代码详解
test1.c
运行结果:
test2.c
运行结果:
内存图解分析:
方法1:
a[i] 直接通过 %s 打印 0x11223344地址的内容。
方法2:
q为二级指针,
*q<===>a[i]
方法3:
直接通过*q输出
作业:
int *p = NULL;
char **q = NULL;
char *a[] = {"nanjing","shanghai","wuhan",NULL};
int i = 0;
p = a[0];
q = a;
要求通过指针数组,一级指针,二级指针的方式来输出以上元素。
要求输出的结果为 nanjing shanghai wuhan
#include <stdio.h> int main() { char *p = NULL; char **q = NULL; char *a[] = {"nanjing","zhejiang","wuhan",NULL}; int i = 0; p = a[0]; q = a; for(q = a;*q != NULL;q++) { printf("a[%d] : %s\n",i,a[i]); i++; p = *q; printf("p : %s\n",p); printf("*q : %s\n",*q); } return 0; }
相关文章推荐
- Linux下C语言环境编程(gdb调试指针)
- Linux 下 C 语言编程 GDB 调试
- Linux高级编程--04.GDB调试程序(入门概述)
- Linux高级编程--04.GDB调试程序(设置断点)
- Linux高级编程--04.GDB调试程序(查看数据)
- FILE__,__LINE__,FUNCTION__实现代码跟踪调试(linux下c语言编程 )
- 泛型(高级指针)指针编程
- linux下C语言编程1-gdb调试程序简介
- windows下用eclipse+goclipse插件+gdb搭建go语言开发调试环境
- 【打基础】高级语言程序设计·厦大出版社 课后习题个人记录3
- [Linux高级编程]工具&库
- C语言 命令行参数 函数指针 gdb调试
- windows下用eclipse+goclipse插件+gdb搭建go语言开发调试环境
- 嵌入式C语言高级编程技巧 之 温故知新的指针
- unix环境高级编程-5.7-每行一次I/O
- gdb 调试高级命令
- iOS开发之c语言基础-高级指针
- gdb调试时No symbol "var" defined in current context,局部变量不可查看问题
- __FILE__,__LINE__,FUNCTION__实现代码跟踪调试(linux下c语言编程 )(转)
- 初学者编程入门:C语言指针使用方法