一、GCC与GDB
1、GCC编译器
(1)GNU工具
编译工具 :把一个源程序编译为一个可执行程序调试工具 :能对执行程序进行源码或汇编级调试软件工程工具 :用于协助多人开发或大型软件项目的管理,如make、CVS、Subvision其他工具 :用于把多个目标文件链接成可执行文件的链接器,或者用作格式转换的工具。
(2)部分相关资源链接
http://www.gnu.org/ http://gcc.gnu.org/ http://www.kernel.org/ http://www.linux.org/ http://www.linuxdevices.com/ http://sourceforge.net/index.php
(3)GCC简介 全称为GNU CC ,GNU项目中符合ANSI C 标准的编译系统 编译如C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多种语言。 一个交叉平台编译器 ,适合在嵌入式领域的开发编译
gcc所支持后缀名解释
.c C原始程序
.C/.cc/.cxx C++原始程序
.m Objective-C原始程序
.i 已经过预处理的C原始程序
.ii 已经过预处理的C++原始程序
.s/.S 汇编语言原始程序
.h 预处理文件(头文件)
.o 目标文件
.a/.so 编译后的库文件
(4)编译器的主要组件
分析器 :分析器将源语言程序代码 转换为汇编语言 。汇编器 :汇编器将汇编语言 代码转换为CPU可以执行字节码 。链接器 :链接器将汇编器生成的单独的目标文件组合成可执行的应用程序 。标准C库 :核心的C函数都有一个主要的C库来提供。
(5)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所指出的目录加入到程序函数档案库文件的目录列表中,是在链接过程中使用的参数。
(6)GCC使用实例
#include<stdio.h>
int main(void)
{
int i,j;
j=0;
i=j+1;
printf(“hello,world\n”);
printf(“the result is %d\n”,i);
}
编译: $ gcc –o test test.c
执行: $ ./test
查看更详细的信息:
$ gcc –v –o test test.c
(7)GCC编译过程 GCC的编译流程分为四个步骤:
预处理 (Pre-Processing): -E ,生成预处理代码.i文件 编译 (Compiling):–S ,生成汇编代码,.s文件 汇编 (Assembling): –c ,生成可执行二进制代码,.o文件 链接 (Linking) : –o ,生成可执行程序
2、GDB调试工具
(1) 调试器 首先使用gcc对test.c进行编译,注意一定要加上选项‘-g’
例如:
gcc -g test.c -o test
gdb test
(2) gdb调试流程
- ①查看文件
(gdb) l - ②设置断点
(gdb) b 6 - ③查看断点情况
(gdb) info b - ④运行代码
(gdb) r - ⑤查看变量值
(gdb) p n - ⑥单步运行
(gdb) n (gdb) s :进入函数 - ⑦恢复程序运行
(gdb) c - ⑧帮助
(gdb) help [command]
二、条件编译与结构体
1、条件编译
编译器根据条件的真假决定是否编译相关的代码
常见的条件编译有两种方法:
①根据宏是否定义,其语法如下:
#ifdef <macro>
……
#else
……
#endif
实例:
#define _DEBUG_
#ifdef _DEBUG_
printf(“The macro _DEBUG_ is defined\n”);
#else
printf(“The macro _DEBUG_ is not defined\n”);
#endif
②根据宏的值,其语法如下:
#if <macro>
……
#else
……
#endif
实例:
#define _DEBUG_ 1
#if _DEBUG_
printf(“The macro _DEBUG_ is defined\n”);
#else
printf(“The macro _DEBUG_ is not defined\n”);
#endif
2、结构体
(1)简述 在实际的处理对象中,有许多信息是由多个不同类型的数据组合 在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体 。 此时,就要用到一种新的构造类型数据——结构体 (structure),简称结构。
(2)定义
定义一个结构体类型的一般形式为:
struct 结构体名
{
数据类型 成员名1;
数据类型 成员名2;
:
数据类型 成员名n;
};
(3)说明 结构体类型中的成员名可以与程序中的变量名相同,二者并不代表同一对象,编译程序可以自动对它们进行区分。 最后,总结一下结构体类型的特点:
- 结构体类型是
用户自行构造 的。 - 它由若干
不同的基本数据类型的数据 构成。 - 它属于C语言的一种数据类型,与整型、实型相当。因此,定义它时不分配空间,
只有用它定义变量时才分配空间 。
(4)结构体类型变量的定义方法
①先定义结构体类型再定义变量名 struct 结构体名 { 成员列表; }; struct 结构体名 变量名; 例如:
struct worker
{
long number;
char name[20];
char sex;
int age;
float salary;
char address[80];
char phone[20];
};
struct worker worker1,worker2;
②在定义类型的同时定义变量 这种形式的定义的一般形式为: struct 结构体名 { 成员列表; }变量名; 例如:
struct worker
{
long number;
char name[20];
char sex;
int age;
float salary;
char address[80];
char phone[20];
} worker1,worker2;
③直接定义结构类型变量 其一般形式为: struct //没有结构体名 { 成员列表; }变量名; 例如
struct
{
long number;
char name[20];
char sex;
int age;
float salary;
char address[80];
char phone[20];
} worker1,worker2;
(5)大小 一个结构体变量占用内存的实际大小,也可以利用sizeof求出。它的运算表达式为: sizeof(运算量)
(6)结构体变量的使用形式 结构体变量的成员用以下一般形式表示: 结构体变量名.成员名 说明: 如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员,例如:变量.成员.成员,worker1.birthday.year
(7)结构体变量的初始化 ①struct 结构体名 变量名={初始数据表} ; ②另一种是在定义结构体类型时进行结构体变量的初始化。
struct 结构体名
{
成员列表;
}变量名={初始数据表};
3、结构体数组与结构体指针
(1)结构体数组初始化的一般形式 结构体数组初始化的一般形式是:
struct 结构体名
{
成员列表;
};
struct 结构体名 数组名[元素个数]={初始数据表};
或者:
struct 结构体名
{
成员表列;
}数组名[元素个数]={初始数据表};
特别注意:
包围在大括号中的初始数据的顺序,以及它们与各个成员项间的对应关系。
(2)结构体数组的使用 一个结构体数组的元素相当于一个结构体变量,因此前面介绍的有关结构体变量的规则也适应于结构体数组元素。 引用某一元素中的成员: 结构体变量[n].成员变量 示例:
struct
{
char name[20];
char sex;
int age;
char addr[20];
}stu[3] = {{"xiaoming",'n',"123134"},{0},{0}};
printf("%s\n", stu[0].name);
(2)结构体指针
可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。 | 结构体指针在程序中的一般定义形式为:
struct 结构体名 *结构指针名;
结构体指针的说明规定了它的数据特性,并为结构体指针本身分配了一定的内存空间。 但是指针的内容尚未确定,即它指向随机的对象。 使用方法;
结构体指针名->成员名
另外(*p).name 等价于 p->name
三、共用体及typedef
1、共用体
在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体。 | 定义一个共用体类型的一般形式为:
union 共用体名
{
成员表列;
};
例如:
union gy
{
int i;
char c;
float f;
};
共用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间。
2、typedef
在C语言中,允许使用关键字typedef 定义新的数据类型
其语法如下:
typedef <已有数据类型> <新数据类型>;
如:
typedef int INTEGER;
这里新定义了数据类型INTEGER, 其等价于int
在C语言中经常在定义结构体类型时 使用typedef . 例如:
typedef struct _node_
{
int data;
struct _node_ *next;
} listnode, *linklist;
这里定义了两个新的数据类型listnode和linklist。其中listnode 等价于数据类型struct _node_ 而 linklist 等价于struct _node_ *
四、内存管理
1、储存模型
(1)内存区间 C/C++定义了4个内存区间: 代码区 ,全局变量与静态变量区 ,局部变量区即栈区 ,动态存储区,即堆区 。
(2)静态存储分配
通常定义变量,编译器在编译时都可以根据该变量的类型 知道所需内存空间的大小 ,从而系统在适当的时候为他们分配确定的存储空间。
一般在栈上创建。在执行函数 时,函数内局部变量 的存储单元都可以在栈 上创建,函数执行结束 时这些存储单元自动被释放 。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)动态存储分配
有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间 ,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配 。
所有动态存储分配 都在堆区 中进行。
程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
2、内存管理
(1)当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,
用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,
这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。
(2)堆区是不会自动在分配时做初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。
malloc/free
void * malloc(size_t num)
void free(void *p)
- malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
- malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回
NULL 。 - malloc返回值的类型是
void * ,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型 。 - 如果
free 的参数是NULL 的话,没有任何效果。 - 释放一块内存中的一部分是不被允许的。
3、malloc/free注意事项
- ①删除一个指针p(free§;),实际意思是删除了p所指的目标(变量或对象等),
释放了它所占的堆空间 ,而不是删除p本身 ,释放堆空间后,p 成了空悬指针 - ②
动态分配失败 。返回一个空指针 (NULL ),表示发生了异常,堆资源不足,分配失败。 - ③
malloc 与free 是配对使用的, free 只能释放堆空间。如果malloc 返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏 ,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存malloc返回的指针。 - ④动态分配的变量或对象的生命期。无名对象的生命期并不依赖于建立它的作用域, 比如在函数中建立的动态对象在函数返回后仍可使用。我们也称堆空间为
自由空间 (free store)就是这个原因。 但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错。 - ⑤
野指针 : 不是NULL指针,是指向“垃圾”内存的指针。“野指针”是很危险的。 “野指针”的成因主要有两种: 1)指针变量没有被初始化。 2)指针p被free之后,没有置为NULL,让人误以为p是个合法的指针。
到这里就结束啦!
|