嵌入式笔记C语言(高级)
-
调试
1.printf打印信息,来确定是否执行,或值是否正确
2.gdb
执行时,按照自己的需要可以查看到程序执行过程中的任意信息,且能按照我们的需要的方式进行执行
让程序一步一步进行执行,跟踪程序的过程。比如:可以让程序没有执行的情况下,停留在某条语句,查看比如变量,内存内容,查看到程序到底执行了哪些代码,可以监控到程序的每个执行细节
调试工具:gdb
windows:windbg
macox:lldb
linux:gdb
gdb:可以调试代码:C、C++、go、object-c、opencl等
gdb -v
sudo apt install gdb
要使用gdb进行调试,有一个前提:在编译程序时,要保存的必要的调试信息(比如第几行是什么代码)
要使用gdb,在编译时 加上选项 -g
gdb调试:
1.打开调试功能
输入:gdb 程序文件名 (编译后的可执行程序)
如:gdb a.out 同时加载a.out程序进行调试
2.退出gdb调试功能:输入:q
3.gdb调试命令
命令 | 作用 |
---|---|
b(break) | 设置断点:在源代码的指定的某一行设置断点(让程序执行到当前的断点位置就暂停执行) |
r(run) | 执行被调试的程序,其会自动执行到第一个断点处(如果没有断点就执行到程序结束位置),就暂停执行(一般用于开始执行,或重新运行) |
c(coutinue) | 当程序在某一个断点暂停时,使用这个命令就继续执行,直到下一个断点或程序结束。 |
p(print) xxx | xxx:变量、表示打印当前这个变量的值。 |
n(next) | 另程序执行一行代码即单步执行,执行当前行的代码。 |
s(step) | 执行这步的代码,通常也是执行一行,但是如果这一行是函数,则会进入函数执行。 |
info xx | 查看信息、例如断点和局部变量:info b info lacals |
x x1,x2 | 查看指定内存内容:x1、查看空间的大小、x2内存地址 |
l(list) | 查看源代码的内容 |
watch xxx | xxx:变量或者表达式、要监控的变量或者表达式 |
delete xx | xx断点号、删除某个断点 |
gdb提供了图形化的调试方式TUI、需要curses库的支持
ctrl+x+a
按照gdb命令来操作
设置断点后,如果只是不启用断点disable断点号 启动断点号
指针(重点)
程序的执行都在CPU中执行,如果在磁盘上的程序需要把程序的指令拿到CPU中进行执行。在计算机存在一个存储设备,这个设备用于存储当前执行的程序-----内存指针
指针概念
介绍:
在程序中,定义一个变量(int a =1),将数据1存储到变量a中,这个变量就会存储这个执行程序所占用的内存空间的某个位置。
在程序执行的所有内容都存储在内存中,在内存设计时,每一个字节存储单元,都分配了一个编号,这个编号叫做地址。
在计算机中就是靠变量的地址去访问变量中的数据
存储数据,就需要存储的内存地址
定义一个变量要占用内存空间,内存空间由地址来区别,所以变量名只是表示不同空间--地址
内存空间的编号------地址
1.内存地址
在编写源程序,编译得到二进制可执行程序存储在硬盘中(静态),当程序执行时,就变成动态过程
在程序中表示数据,通过一个变量名来定义变量,在内存中使用空间存储数据,变量名就表示空间的数据
定义变量后,变量存储在内存当中,操作系统将很大的内存区域划分为一个一个小空间(存储单元),每一个小空间为一个字节,即1B=8bit
对每个存储单元通过地址编号来管理,内存的完整空间就是有这一个一个的字节连续组成,又通过地址来区别每个存储单元。
32位电脑表示CPU有32位地址线与内存相连。
把变量就是对一块内存空间的表示,泳衣个名字来表示一段空间,相当于就是这段空间的别名
.指针变量
内存地址也是一个数据,那么不也可以用一个变量存储地址这个数据?
指针就是存储另一个变量内存地址的一种数据类型,即指针的内容就是另一个变量的内存地址
指针本身也是一个变量,所以指针也是有自己的地址,但是这个变量有点特殊,存放的是另一个变量的地址
地址没有类型,只有地址中存储的内存数据有类型。
指针2个层此次
-
首先指针
指针变量的定义:
在定义指针时,存储变量地址,指针存储地址后,怎么知道存储的地址中到底是什么类型数据,如:p=0x10地址,怎么知道0x10地址中的数据是什么类型,所以在定义指针时,最好同时在定义的时候进行初始化。
数据类型* 指针名;
数据类型:是指,这个指针变量指向的数据类型,即指针变量存储地址,由于地址没有类型,指针到底存储的是哪种类型变量的地址
*:在定义指针变量时,表示这是定义的指针变量;在或去指针所指向变量值时使用
间接取值符(解引用):
*:获取指针(地址)所指向哪个对应空间的值(按照指向类型),叫做解引用。获取指向空间的值
获取指针变量所指向的空间的数据:
*指针:取指针变量存储的地址,对应空间的值。
以什么样的数据类型来使用:
访问的方式会按照指针的类型去访问
5.指针的运算
指针的运算,对指针变量(指针变量存储的数据(即地址))进行运算
px表示指针变量
+:
px+n:表示指针px想地址增大方向移动n个数据大小
如:
int* p;
p+1=>p+Sizeof(int);
p+5=>p+Sizeof(int)*5
-:px-n:表示指针px想向地址减小方向移动n个数据大小。
px1-px2:指针减指针,指两个地址间间隔多少数据。
如:
double * px1=0x10,px2=0x20;
px2-px1=(0x20-0x10)/sizeof(double)=0x10/b=2
*(乘法):px*n-------错误---指针没有乘法也没有除法。
++:同变量用法相同
-- :同变量用法相同
指针与数组:
数组:
在内存中是一段连续的空间,在这段空间中每个元素占用对应的大小,元素与元素之间相邻的
访问数组就可以使用指针
a[0] 和a[1]数据在存储时是连续的,相隔一个数据大小,
指针p=&a[0];
p+1=&a[1];==>*(p+1)=a[1];
数组,只要知道数组的第一个元素的地址(指针)就可以通过地址遍历整个数组。
数组的第一个元素的地址----------数组首地址(数组起始地址)
数组名代表了数组的首地址
数组名+n:移动n个数组元素大小的地址
*(地址):取出地址中的数据(取出数组元素)
例如:
*数组名=========》取出a[0]的地址中的元素,也就是a[0]。
*(数组名+n)可以偏移到数组其他地址访问其他位置变量
指针与字符数组
对于字符数组如果按照整体操作就是字符串操作方式,只需要字符数组首地址,就可以操作一连串字符;
指针:存储地址,指针存储字符数组首地址这个指针可以当做数组名使用。操作字符串,指针就可代表数组名(数组首地址)
char buf[10] char *p=buf//p代表数组首地址 scanf("%s",p)//输入字符串到buf中
在程序中只要定义常量字符串,则常量字符串表示字符串的首地址
char *p ="常量字符串"
p就是存储了这个常量字符串首地址
注意:
在字符数组中存储字符串时,请在字符串最后一个字符结束之后位置加'\0'
指针与二维数组
二维数组:数组的每一个元素也是一个数组,我们就把这种数组叫做二维数组。
二维数组的定义:
一维类型 二维数组名[二维元素个数][一维元素个数]
二维数组中的每一个一维数组,是一组数组的集合
二维数组使用:
1.二维数组的数组元素
二维数组名[下标]-----------:二维数组的数组元素是一维数组
2.二维数组的数组元素,即一维数组;一维数组的数组元素:
二维数组名[下标][一维数组下标]--------:二维数组的数组元素是一维数组,一维数组的数组元素
对二维数组的操作,只能是基本数据类型操作,即二维数组的元素,一维数组,一维数组的元素操作
二维数组初始化:
1.指定二维数组大小
一维数组元素类型 数组名[二维数组元素大小][一维数组元素大小]={
值
}
例如:
buf2[3][3]={
3,3,3,
4,4,4,
5,5,5
}//其中 buf[0] [0]=3 buf0][1]=3一共九个元素一维数组3个,二维数组3个,没有初始化的值全部为0;
buf2[3][3]={{1,2,3,},{1,3,4},{1,2,4}}
2.不指定二维数组大小
一维数组元素类型 数组名[][一维数组大小]={值}
一维数大小要写
例如:
int buf[] [3]={1,1,2,31,4,14};没有指定二维数组大小,但是每一个原酸是一维数组有3个int类型数据
int buf [] [3]={{},{},{}}
二维数组的指针用法:
int a[2][3];
二位数组名,数组名是整个数组的首地址(数组中的第0个元素的地址):a==&a[0],表示第0个元素这个一维数组的地址。
a[0];
是第0个元素,又是一维数组,a[0]是一维数组的数组名,一维数组数组名、表示一维数组首地址。(一维数组中的第0个元素的地址),一维数组中的元素是普通类型(本例中为:int ),即普通成员的地址
a[0]==&a[0][0];
当a+1:表示移动二维数组的元素(一维数组)的大小地址
当a[0]+1:表示移动一维数组的元素(普通数据)的大小的地址
数组指针:
是一个指针,存储地址,用于存储整个数组的地址,指针的类型为数组的地址类型,指向整个数组
定义:
数组元素类型 (*指针名)[数组大小];
如
定义指针变量存储包含10个元素的int类型数组的地址
int (*p)[10];
指针数组与多级指针
指针数组:
指针数组是一个数组,里面存放的数组的每一个元素是一个指针变量。
指针指向类型 数组名[数组大小]
指针类型:指针指向类型--------->是一个指针,存储指向类型的地址
如:
int * arr[5];
a数组有5个元素,每个元素都是指针,指针:存储int类型的的地址
多级指针:
指针变量是存储地址IDE,但是由于指针变量本身也要占用空间,所以指针变量也会有地址,如果有另外的指针变量存储的是这个指针变量的地址,把另外的指针变量叫做二级指针。
一级指针变量存普通数据类型的地址,
二级指针变量存一级指针变量的地址,
三级指针变量存二级指针变量的地址。
指针在函数中的使用
函数的形式参数为指针类型
返回类型 函数名(指向类型 *指针名)
参数为指针(地址),可以在函数中修改地址对应空间的值,直接访问对应的内存空间,而参数为变量,传递的变量的值,不会修改到变量。
如果函数的形式参数为数组类型,就会退化为指针类型 ,为地址首地址对应类型的指针
如:
int a[10]---------------->int *p
int b[2][3]------------->int (*P)[3]
int *a[10]----------------->int * s*p
函数的返回值为指针类型
函数1结果为一个数据地址
函数指针
函数在使用是是存储在内存中,所以函数在调用时也是找到函数的地址然后执行函数的内容
函数名就是函数地址
函数指针:
用于存储函数地址类型的指针变量
由于函数的返回值,参数列表的类型都各不相同,函数指针指正存储一种函数的地址
函数指针定义:
返回值类型(* 函数指针名)(参数列表);
函数还真使用:------调用函数指针对用的函数
函数指针名(实际参数);
空指针、野指针、万能指针
野指针:
指针存储的地址不明确(指向的空间不清楚---没有分配不能使用),这个地址定义的空间是否能够在程序中具有操作不清楚。
空指针:
指针记录的地址是0x0这个地址就叫做空指针,系统规定0x0这个地址不允许任何程序进行访问
NULL===0x0
万能指针:
不管是什么类型的指针,指针都是用来存储地址,内存地址用多少位来表示,已经规定好了。
每个地址都是相同的位数
即32位计算机为32bit==4B
64位计算机为64bit==8B
void :空类型 ,不存在这个对用的类型大小
void * :空指针类型,指针类型,大小为4B/8B
void *就叫做万能指针(指向任意类型的地址),可以存储任意类型的地址(指针值),但是不能取*
取对应类型的值。
由于是万能指针,可以存储任意类型的地址,也可以把任意类型的地址赋值给另一个任意类型的
地址
关键字
变量定义
-
unsigned
表示是无符号数据类型(>0),最高位也是数据位
unsigned 数据类型 变量名;
-
signed
表示是有符号数据类型,默认添加
singed 数据类型 变量名;
存储类型关键字
-
auto:创建局部变量时使用,表示自动存储管理(自动分配,自动释放),表示在栈区存储局部变量,如果局部变量不加其他的存储类型,默认添加auto
auto 数据类型 变量名
-
static :静态数据类型,存储在静态区(全局区),
static 修饰的是局部变量,就表示局部变量不是以栈(auto)方式存储,变量的生命周期不再是语句块结束而结束,二是整个程序结束才结束,作用域不变,还是在语句块中。
如果遇到多次创建同一个静态变量,不会重复定义初始化,使用之前创建的静态变量
-
第二次循环不会执行static int语句。
static修饰全局变量:全局变量只能在本文件使用
static 数据类型 变量名;
static 修饰函数:函数只能在本文件内使用
-
register:寄存器存储、表示把数据存储到cpu内部的存储单元中,在执行程序时,入股都能够存储到cpu中就存储进去,在执行过程中,cpu的资源已经占用完了,当做普通的auto 类型存储。
-
register 数据类型 变量名;
-
extern:用于提供一个全局变量的引用,在其他文件中定义了一个变量,现在使用其他文件定义的变量,会把当前的变量名指向一个之前定义过的存储位置。
extern 数据类型 变量名;
-
注意:如果全部变量或者静态变量没有进行初始化(由于存储在数据段),自动把没有进行初始化的变量初始化为零,auto局部变量没有进行初始化,则是之前的随机值,不会进行初始化
常量关键字:
-
const :修饰变量 ,则表示该变量不能修改,只能访问(只能获取值,不能修改值)
const 数据类型 变量名;
一般使用const 修饰都会完成初始化(定义时赋值);
const 修饰指针:
数据类型 * const 指针变量名;
指针变量不允许修改,不能赋值为其他变量的地址,但是可以修改地址里的值。
数据类型 const *指针变量名/(const 数据类型 * 指针变量名)
指针变量允许修改,能赋值为其他变量的地址,但是不可以修改地址里的值。
数据类型 const *const 指针变量名(const 数据类型 * const 变量名)
两个都不能修改
动态内存
由自己申请内存空间来使用(申请大小由自己决定)存储变量,访问变量;自己进行释放空间的使用方式,叫做动态内存
动态内存的区域--------堆区---------有由程序员自己管理
动态内存的申请与释放是通过调用库函数来实现
#include<stdlib.h>--------对应库函数 //申请空间函数 void * malloc(unsigned int size); {申请空间:在对空间中申请指定size字节大小的空间用于使用,成功会把申请的空间首地址返回 return 地址;//本函数返回一个地址 } void free(void *ptr)//释放指定地址ptr这个位置对应的空间#include<stdlib.h>--------对应库函数 //申请空间函数 void * malloc(unsigned int size); {申请空间:在对空间中申请指定size字节大小的空间用于使用,成功会把申请的空间首地址返回 return 地址;//本函数返回一个地址 } void free(void *ptr)//释放指定地址ptr这个位置对应的空间
-
申请空间
指针变量 = malloc (字节大小);
注意:如果你要按照某种类型去使用申请的空间,那你的指针变量就应该是对应的类型,如:按照整型去访问这个空间,指针应该是int 类型的地址
*指针变量 :就是访问对应空间
如:
int* p=malloc(20)
*p--------访问4个字节,按照整数
*(p+1)--访问p后的4个字节,按照整数
例如申请一段空间给int类型的10个数
int* p=malloc(10*sizeof(int)); int i = 0; if(p==NULL) { printf("申请失败\n"); } else { for(i = 0;i < 10;i++) { *(p+i)=i; printf("%d ",*(p+i)); } free(p);
构造类型(自定义类型)
在使用中有些数据的表示不能够用单一的数据类型就能表示出来,在C语言中就赋予了类型的扩展性,只能按照C语言允许的格式去添加,这种格式就叫做构造类型。
1.结构体
设计一种类型,这种类型由多个部分构成,每个部分只表示一个信息(内容),共同组合表示一个完整的信息内容,一个新的类型。
类型声明:----------说明有一个新类型(不表示有这个类型变量)
struct 结构体名-------(新的类型名)
{
数据类型1 成员名1
数据类型2 成员名2
数据类型3 成员名3
...
};
定义结构体变量:
struct 结构体名 变量名;
初始化:
struct 结构体名 变量名 ={值};
-
-
使用结构体变量:结构体变量是一个整体,包含多个成员类型
需要单独对结构体变量中的成员进行操作
结构体变量名.成员名 -------访问结构体成员
-
memcpy----------拷贝。
使用结构体指针访问变量内容:
结构指针:struct 结构体名 * 指针名;
如:
struct people * p;
p=&p1;
指针访问结构体成员:
指针名->成员名;p->age;p->name;...
结构体类型的大小:
结构体变量的大小由结构体类型决定;
从0偏移开始存储成员
偏移位置除以成员大小如果能整出就说明可以存储到这个偏移位置,如果不能整除,则偏移位置移动到能整出位置,进行存储元素。
结构体的总大小能够整出最大的成员。
指针名
2.共用体(联合体)
与结构体使用方式相同,表示含义也相同,只是共用体是所有的成员共用同一段内存空间。
定义:
union 联合体名
{
数据类型1 成员名1;
数据类型2 成员名2;
...
};
只是成员1,2,3...共用一个空间,每个成员没有单独空间,空间为最大成员的空间
在使用共用体时,同一时间只能使用一个成员,这个成员使用完,才能使用其他成员
3.枚举
有一些类型的数据取值范围只能是某些值,这种数据类型,限制了数据的取值范围,把能够表示的值一一列举出来,这种类型变量就只能从列举的值中取值。
定义:
enmu 枚举名
{
成员1,
成员2,
成员3,
成员4,
......
};