前言
今天是2021年的第六天,本来打算把makefile也更新的,结果没来及。下次一定!
三连即可提高学习效率0.0
🧑🏻作者简介:一个学嵌入式的年轻人
✨联系方式:2201891280(QQ)
📔源码地址:https://gitee.com/xingleigao/study_qianrushi
⏳全文大约阅读时间: 60min
文章目录
GCC和GDB
GCC编译器
GNU工具
- 编译工具:把一个源程序编译为一个可执行程序
- 调试工具:能对执行程序进行源码或汇编级调试
- 软件工程工具:用于协助多人开发或大型软件项目的管理,如make、CVS、Subvision
- 其他工具:用于把多个目标文件链接成可执行文件的链接器,或者用作格式转换的工具。
GCC工具
- 全称为GNU CC ,GNU项目中符合ANSI C标准的编译系统
- 编译如C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多种语言
- GCC是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%
- 一个交叉平台编译器 ,适合在嵌入式领域的开发编译
所支持的后缀
- .c C原始程序
- .C/.cc/.cxx C++原始程序
- .m Objective-C原始程序
- .i 已经过预处理的C原始程序
- .ii 已经过预处理的C++原始程序
- .s/.S 汇编语言原始程序
- .h 预处理文件(头文件)
- .o 目标文件
- .a/.so 编译后的库文件
编译器的主要组件
- 分析器: 分析器将源语言程序代码转换为汇编语言。因为要从一种格式转换为另一种格式(C到汇编),所以分析器需要知道目标机器的汇编语言。
- 汇编器: 汇编器将汇编语言代码转换为CPU可以执行字节码。
- 链接器: 链接器将汇编器生成的单独的目标文件组合成可执行的应用程序。链接器需要知道这种目标格式以便工作。
- 标准C库: 核心的C函数都有一个主要的C库来提供。如果在应用程序中用到了C库中的函数,这个库就会通过链接器和源代码连接来生成最终的可执行程序。
GCC的基本用法和选项
Gcc最基本的用法是∶
gcc [options] [filenames]
- -c,只编译,不连接成为可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件。
- -o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。
- -g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。
- -O,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。
- -O2,比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。
- -I dirname,将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数。
- -L dirname,将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是在链接过程中使用的参数。
GCC编译过程
GDB调试工具
首先使用gcc对test.c进行编译,注意一定要加上选项‘-g’
gcc -g test.c -o test gdb test
调试信息:
命令 功能 (gdb) l 查看文件 (gdb) b 6 设置断点 (gdb) info 6 查看断点 (gdb) r 运行代码 (gdb) p n 查看变量值 (gdb) n 、(gdb)s 单步运行 (gdb) c 恢复运行(断点继续) (gdb) help [command] 帮助 (gdb) set args 设置传入参数 注意的点:
- 在gcc编译选项中一定要加入‘-g’。
- 只有在代码处于“运行”或“暂停”状态时才能查看变量值。
- 设置断点后程序在指定行之前停止
条件编译和结构体
条件编译
编译器根据条件的真假决定是否编译相关的代码**(非常常用)**
常见的条件编译有两种方法:
- 根据宏是否定义,其语法如下:
#ifdef <macro>
……
#else
……
#endif- 根据宏的值,其语法如下:
#if <macro>
……
#else
……
#endif
结构体
在实际的处理对象中,有许多信息是由多个不同类型的数据组合在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体。此时,就要用到一种新的构造类型数据——结构体(structure),简称结构。
定义一个结构体类型的一般形式为:
struct 结构体名 { 数据类型 成员名1; 数据类型 成员名2; ... 数据类型 成员名n; };
举个例子:
定义一个职工worker结构体如下:struct worker { long number; char name[20]; char sex; int age; // age是成员名 float salary; char address[80]; }; //注意分号不能省略 int age = 10; //age是变量名
特点:
- 结构体类型是用户自行构造的。
- 它由若干不同的基本数据类型的数据构成。
- 它属于C语言的一种数据类型,与整型、实型相当。因此,定义它时不分配空间,只有用它定义变量时才分配空间。
定义结构体变量方法
1.先定义结构体类型再定义变量名
这是C语言中定义结构体类型变量最常见的方式struct 结构体名 { 成员列表; }; struct 结构体名 变量名;
举个例子:
truct worker { long number; char name[20]; char sex; int age; float salary; char address[80]; char phone[20]; }; struct worker worker1,worker2;
2.在定义类型的同时定义变量
struct 结构体名 { 成员列表; }变量名;
3.直接定义结构类型变量
其一般形式为:struct //没有结构体名 { 成员列表; }变量名
结构体的大小
使用sizeof来求
sizeof(struct worker) sizeof(worker1)
补充一个知识点,就是结构体的大小求解中的两个原则:
- 不但结构体的成员有有效对齐值,结构体本身也有对齐值,这主要是考虑结构体的数组,对于结构体或者类,要将其补齐为其有效对齐值的整数倍。结构体的有效对齐值是其最大数据成员的自身对齐值;
- 存放成员的起始地址必须是该成员有效对齐值的整数倍。
对于每个对齐值有如下定义:
- 自身对齐值: 数据类型本身的对齐值,例如char类型的自身对齐值是1,short类型是2;
- 指定对齐值: 编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4;(整个结构体的长度需要是默认对齐的整数倍)
- 有效对齐值: 自身对齐值和指定对齐值中较小的那个。
举个例子:
struct ep{ char a; int b; char c; char d; };
首先a在第一个字节,而b因为自身长度为4,所以其实地址在低四个字节,占据四个字节此时长度为8个字节,两个char再占用两个字节,所以数据域长度为
10
个字节,由于ep自身的对齐值为4,所以最终应该为12
个字节。
结构体变量的使用形式
结构体变量的成员用一般形式表示:
结构体变量名.成员名
例如:worker1.number;worker1.name;worker1.sex;
在定义了结构体变量后,就可以用不同的赋值方法对结构体变量的每个成员赋值。例如:strcpy(worker1.name,”Zhang San”); worker1.age=26; strcpy(worker1.phone,”1234567”); worker1.sex=’m’;
结构体变量的初始化
struct 结构体名 变量名={初始数据表};
或者:struct 结构体名 { 成员列表; }变量名={初始数据表}
结构体数组
具有相同结构体类型的结构体变量也可以组成数组,称它们为结构体数组。结构体数组的每一个数组元素都是结构体类型的数据,它们都分别包括各个成员(分量)项。
定义
定义结构体数组的方法和定义结构体变量的方法相仿,只需说明其为数组即可。与上面的结构体的定义类似。
举个例子//第一种 struct { char name[20]; char sex; int age; char addr[20]; }stu[3]; //第二种 struct student { char name[20]; char sex; int age; char addr[20]; }stu[3]; //第三种 struct student { char name[20]; char sex; int age; char addr[20]; }; struct student stu[3];
初始化
struct 结构体名 { 成员列表; };
struct 结构体名 数组名[元素个数]={初始数据表};
使用
1.引用某一元素中的成员。
stu[1].name
2.一个元素赋给另外一个student1=stu[0]; stu[0]=stu[1]; stu[1]=student1;
注意:字符串类型的不能直接等于赋值!!但是结构体可以直接赋值,虽然里面有字符串类型。
结构体指针
定义:
struct 结构体名 *结构指针名;
访问元素:(*p).name p->name
两者是完全等价的。
共用体和typedef
共用体
不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体(union)。
定义
union 共用体名 { 成员表列; };
例如
union gy { int i; char c; float f; };
因为每个成员长度是不一样的,从内存的角度占用的空间是所有元素中最大的。
赋值
a.i = 1; a.c = 'a'; a.f = 1.5;
因为内存区域是公用的关系,在保存结束
a.f
之后,其他的都已经没有意义,因为相关的内存的区域会被覆盖掉,数据就会发生变化。
在程序中经常使用结构体与共用体相互嵌套的形式。
struct datas { char *ps; int type; union { float fdata; int idata; char cdata; }udata; };
typedef 改名
在C语言中,允许使用关键字typedef定义新的数据类型
其语法如下:typedef <已有数据类型> <新数据类型>;
例如:
typedef int INTEGER;
此时:
INTEGER i;
等价于int i;
在C语言中经常在定义结构体类型的时使用typedef
typedef struct _node_ { int data; struct _node_ *next; } listnode, *linklist;
这里定义了两个新的数据类型listnode和linklist。其中listnode
等价于数据类型struct node 而 linklist等价于struct node *
内存管理
C/C++定义了4个内存区间:
代码区、全局变量与静态变量区、局部变量区即栈区、动态存储区,即堆区
静态存储分配
通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。
在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
动态存储分配
- 有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为。
- 所有动态存储分配都在堆区中进行。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
malloc/free
void * malloc(size_t num) void free(void *p)
- malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
- malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NULL。
- malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。
- 如果free的参数是NULL的话,没有任何效果。
- 释放一块内存中的一部分是不被允许的。
注意事项:
- 删除一个指针p(free§;),实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针,建议将指针置为NULL
- 动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。
- malloc与free是配对使用的, free只能释放堆空间。如果malloc返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏, 同一空间重复释放也是危险的,因为该空间可能已另分配, 所以必须妥善保存malloc返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。
野指针
是NULL指针,是指向“垃圾”内存的指针。“野指针”是很危险的。
“野指针”的成因主要有三种:
- 指针变量没有被初始化。
- 指针p被free之后,没有置为NULL,让人误以为p是个合法的指针。
- 指针操作超越了变量的作用范围。(这种通常被人们忽略)
写在最后
今天主要讲解了C语言的补充知识,有些东西非常常用,在接下来的数据结构会更加常用到,大家学废了么?最后三连即可提高学习效率!!!
另外我在更新的就是算法笔记的一些例题笔记,这个系列是用于提高我的算法能力,如果有兴趣对算法领域感兴趣找不到合适的入门文章也可以追更,如果我更新的太慢了请大家点赞收藏,一键三连才能更有更新的动力呀0.0