Linux C语言高级编程
GCC编译器
GCC简介
全称为GNU CC ,GNU项目中符合ANSI C标准的编译系统
gcc所支持后缀名解释:
.c C原始程序
.C/.cc/.cxx C++原始程序
.m Objective-C原始程序
.i 已经过预处理的C原始程序
.ii 已经过预处理的C++原始程序
.s/.S 汇编语言原始程序
.h 预处理文件(头文件)
.o 目标文件
.a/.so 编译后的库文件
GCC的基本用法和选项
Gcc最基本的用法是∶gcc [options] [filenames]
-c 只编译,不链接成为可执行文件。
-o output_filename,确定输出文件的名称为output_filename,名称不能和源文件同名。如果不给出这个选项,gcc默认 生成可执行文件a.out。
-g gdb调试时使用。
-O 对程序进行优化编译、连接,采用这个选项,源代码会在编译、连接过程中进行优化处理,产生的可执行文件执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。
-O2 比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。
-I dirname 将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数。
-L dirname 将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是在链接过程中使用的参数。
GCC编译过程
GCC的编译流程分为四个步骤:
预处理(Pre-Processing)
编译(Compiling)
汇编(Assembling)
链接(Linking)
生成预处理代码
$ gcc –E test.c -o test.i
生成汇编代码
$ gcc –S test.c –o test.s
生成目标代码
$ gcc –c test.s –o test.o //用gcc直接从C源代码中生成目标代码
$ as test.s –o test.o //用汇编器从汇编代码生成目标代码
生成可执行程序
$ gcc test.s –o test
GDB调试工具
调试器–Gdb调试流程
gcc -g test.c -o test //首先使用gcc对test.c进行编译,注意一定要加上选项‘-g’
gdb test
Gdb调试流程
-
查看文件
(gdb) l
-
设置断点
(gdb) b 6
-
查看断点情况
(gdb) info b
-
运行代码
(gdb) r
-
查看变量值
(gdb) p n
-
单步运行
(gdb) n (gdb) s
-
恢复程序运行
(gdb) c
-
帮助
(gdb) help [command]
条件编译
编译器根据条件的真假决定是否编译相关的代码
-
根据宏是否定义
#ifdef __宏__ …… #else …… #endif
-
根据宏的值
#if <macro> …… #else …… #endif
结构体
结构体类型定义的一般形式
struct 结构体名
{
数据类型 成员名1;
数据类型 成员名2;
:
数据类型 成员名n;
};
struct 结构体名 变量名;
注意事项:“struct 结构体名” 代表类型名,不能分开写;
在定义类型的同时定义变量
这种形式的定义的一般形式为:
struct 结构体名
{
成员列表;
}变量名;
直接定义结构类型变量
struct //没有结构体名
{
成员列表;
}变量名;
结构体的大小
由于字节对齐,所以结构体成员要对齐成员当中数据类型占用字节中最大的一个。
共用体
定义共用体类型的一般形式
union 共用体名
{
成员表列;
};
共用体的大小
由于共用体中各成员的数据长度往往不同,所以共用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间。 在这一点上共用体与结构体不同,结构体类型变量在存储时总是按各成员的数据长度之和占用内存空间。
注意:在共用体类型变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。
typedef
-
typedef的语法
typedef <已有数据类型> <新数据类型>;
-
在定义结构体类型时使用typedef
typedef struct _node_ { int data; struct _node_ *next; } listnode, *linklist;
定义了两个新的数据类型listnode和linklist。其中listnode等价于数据类型struct node 而 linklist等价于struct node *
内存管理
内存
C/C++定义了4个内存区间:
代码区/全局变量与静态变量区/局部变量区即栈区/动态存储区,即堆区。
静态内存
通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从、而系统在适当的时候为他们分配确定的存储空间。在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限
动态存储分配
- 有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为。
- 所有动态存储分配都在堆区中进行。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
堆内存的分配与释放
堆区是不会自动在分配时做初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。
malloc/free
void * malloc(size_t num);
void free(void *p);
a. malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
b. malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NULL。
c. malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。
d. 如果free的参数是NULL的话,没有任何效果。
e. 释放一块内存中的一部分是不被允许的。
注意事项
- 删除一个指针p(free§;),实际意思是删除了p所指的目标(变量或对等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针。
- 动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。
- malloc与free是配对使用的, free只能释放堆空间。如果malloc返回的指针值丢失,所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存malloc返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。
- 必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错
野指针
不是NULL指针,是指向“垃圾”内存的指针。“野指针”是很危险的。 “野指针”的成因主要有两种:
- 指针变量没有被初始化。
- 指针p被free之后,没有置为NULL,让人误以为p是个合法的指针。指针操作超越了变量的作用范围。这种情况让人防不胜防。