Gcc编译器
Gcc编译时分为四个阶段:
预处理 编译 汇编 链接
.c .cc .a .i .ii .o (编译后文件) .s (汇编后文件)
编译选项
-E 只做预处理,不做其他的
-c 只编译,不链接成可执行文件 生成.o的目标文件
-S 汇编,生成 .s文件。
-g 产生调试工具所必要的符号信息,想要程序能进行调试必须加入这个选项
-O 对程序进行优化编译、链接,产生的可执行文件的执行效率可以提高但是变异速度就要相应的要慢一些。
-O2 比-O更好的优化。
-Idirname 将dirname所指出的目录加入到程序头文件目录列表中。告诉编译器如果在默认目录中没有找到头文件就去指定目录dirname中去找。
-Ldirname 将dirname所指出的目录加入到库文件的目录列表中。
-lname 在链接时装载名字为libname.a的函数库 例如-lm 表示为libm.a的数学函数库。通常把lib和.a省略。
-static 静态链接库文件 gcc默认动态链接。
-Wall 生成所有警告信息
-w 不生成警告信息
-DMACRO 定义MACRO宏,等效于在程序中使用#define MACRO
GDB调试器
List(l) 查看程序
break(b) 函数名/行号/文件名:行号 在指定函数入口/指定行/指定文件的指定行设置断点
break 行号 if条件当条件为真时,指定行处断点生效。例如b 5 if I = 10
run运行 next单步执行 step单步运行(step在遇到子函数时会进入子函数运行,而next不会) continue
info break 查看所有设置的断点。
delete 断点编号 删除断点
print 变量名 查看指定变量值
finish 运行程序知道当前函数结束
watch 变量名 对指定变量进行监控
quit 退出gdb
makefile工程管理
规则:用于说明如何生成一个或多个目标文件,规则格式如下:
targets : prerequisites
command
目标 依赖命令
main.o : main.c
gcc –cmain.c
l 命令需要以TAB键开始
make 命令默认在当前目录下寻找makefile或Makefile 当名字不为两者之一是可如下指定:
make–f 文件名
makefile中把那些没有任何依赖只有执行动作的目标称为伪目标
.PHONY:clean
clean :
rm –fhello main.o func .o func2.o
.PHONY 将clean目标声明为伪目标
Makefile中使用变量
存在系统默认的自动变量
l $^:代表所有依赖文件
l $@:代表目标
l $<:代表第一个依赖文件
例:
hello :main.o func1.o func2.o
gcc main.ofunc1.o func2.o –o hello
例:
hello :main.o func1.o func2.o
gcc $^ -o$@
makefile 中“#”字符后的内容被视为注释
C语言预处理的指令
C程序中允许使用指令,指令就是 以#开头的。指令在预处理阶段转换成纯C语法。
指令的分类:
1. #include (用于包含一个其他的文件,主要是头文件)
#include 后面支持两种格式: <> 和"",区别是<>只查找系统的文件路径,""会先查找当前目录,然后再查找系统路径。
2. #define和 #undef 定义/取消 宏变量或宏函数
3. 条件编译- 按条件选择编译的分支
#if 、#elif、、#else、#endif
#ifdef、#ifndef
补充几个指令:
#error 和 #warning 产生一个错误或警告
#pragma 提供一些额外的功能
#pragma GCCdependency 文件名
让当前文件依赖dependency后面的文件,如果后面的文件比当前文件新,会产生一个警告。
#pragma GCCpoison 单词
定义某个东西为毒药,禁止使用,否则产生错误。
注:在#pragma之前的代码 可以使用,之后不能。
#pragmapack(int) -结构的对齐和补齐
对齐 就是每个成员 必须放在自己大小的整数倍位置(大于4字节的按4字节算);补齐就是每个结构的大小应该是 最大的成员大小的整数倍(大于4字节的按4字节算)
有些时候 不需要对齐和补齐或者 改变对齐和补齐的单位,可以用 pragma pack()可以改变对齐和补齐的方式。比如:
pack(1) 按1字节的整数倍对齐和补齐,不做对齐和补齐了
pack(2) 按2字节的整数倍对齐和补齐
头文件 (.h) 中定义
各种声明都应该定义在头文件中,是对外的清单。各种声明包括: 变量声明、宏的声明、结构和联合、函数的声明。
各种具体的实现 都定义在源文件中,是具体的代码。具体实现包括:变量的赋值、函数的代码。
头文件的基本格式:
#ifndef XXXX_H_
#define XXXX_H_
//各种声明
#endif
系统查找自定义的头文件方法:
1. 用""可以解决,""中支持路径 "./a/test.h"
2. gcc -I头文件所在路径 (<>都可以找的到)
3. 配置环境变量CPATH,加入头文件所在的路径。
静态库和共享库(动态库)
静态库是代码的归档,每次使用时都会直接复制代码段到目标文件中。共享库是代码的集合,每次使用时都不复制代码段,而是提供代码的地址(函数指针)。
静态库的优点 速度稍快,链接成功后与库文件完全脱离。缺点 占用空间非常大,而且不利于修改和维护。共享库的优点 占用空间小,修改和维护比较方便。缺点 速度稍慢,链接成功以后 目标文件和共享库文件必须存在才可以运行代码。
开发项目时,绝大多数使用 共享库。因此,C程序员需要提供两样东西: 共享库文件和头文件。
静态库的创建和使用:
创建步骤:
1. 写源程序,保存退出。
2. 编译源程序为.o文件, gcc -c add.c
3. 把所有的.o文件打包成静态库文件(lib库名.a)
ar-r libmyku.a add.o
调用步骤:
1. 写调用的代码,保存退出。
2. 编译调用代码为.o文件, gcc -c test.c
3. 链接调用程序和静态库文件。有三种办法:
1. 直接连接法
gcc test.o libmyku.a
2. 双L连接法(库名不带lib和.a)
gcc test.o -l库名 -L库所在的目录
3. c 单l连接法
首先配置环境变量 LIBRARY_PATH,把库所在的目录配置进来。
gcc test.o -l库名
共享库的创建和使用:
创建步骤:
1. 写源程序,保存退出。
2. 编译生成共享库文件。(lib库名.so)
gcc -fpic -shared add.c -olibmyku.so(-fpic可以省略)
使用步骤:
与静态库的使用步骤完全一样。
注:共享库在运行期间需要配置环境变量LD_LIBRARY_PATH才能找到。(链接成功,运行失败)
ldd a.out 可以查看关联的共享库文件。
系统提供了直接打开共享库的函数(代码中直接访问),包括:
#include<dlfcn.h>
dlopen() - 打开一个共享库文件
dlsym() - 从一个打开的共享库文件中找到一个函数
dlclose() - 关闭打开的文件
dlerror() - 处理上面三个函数产生的错误
(了解即可,动态编程)
C程序的错误处理
错误处理对于所有的语言都是必须的,高级语言都使用 异常(Exception)机制 进行错误处理。C程序用返回值 标识错误,判断返回值进行错误的处理。
返回标识错误的4种情况:
1. 如果函数返回值是int,并且返回的数据不可能是负数;直接用返回-1 代表出错,非负数代表正常返回的数据。
2. 如果函数返回值是int,但返回的数据也可以是负数;返回0代表正常,返回-1代表出错。用指针类型的参数带出返回的数据。
3. 如果函数返回值是指针,返回NULL(0)代表出错,其他情况代表正常的返回指针。
4. 如果函数不需要考虑出错的情况,可以用void返回值。
注:以上4种情况不是绝对的,比如:有些时候返回-1代表错误的指针。
C标准中,对于错误处理提供了1个全局变量和3个函数:
errno - 外部的全局变量,用于存储错误的编号。<errno.h>中定义。
函数 : strerror() / perror() / printf() 都可以把错误的编号变成错误的信息。
strerror() - 传入一个错误编号,返回 错误信息
perror() - 打印当前的错误信息(自动查找errno得到错误编号)
printf("%m") - 打印当前的错误信息(自动查找errno)
errno的值在调用函数时,出错就会改变;但在调用函数时,不出错,errno的值 不变。因此,不能用errno判断某个函数是不是出错,只能通过函数的返回值判断是否出错。errno的作用是出了错以后,获得具体的错误信息。
注:errno 不是所有函数都使用,某些函数没有使用(线程)。