1 GCC
1.1 功能:预处理、编译、连接、汇编。
1.2 gcc –E 只预处理;
gcc –S 汇编;
gcc –c 只编译,不连接;
gcc –o 连接,生成目标文件;
gcc –std 设置标准规范 C89、C99 等;
gcc –Wall 显示尽可能多的警告信息;
1.3 .a 静态库文件;
.o 目标文件;
.i 不需要预处理的源程序(预处理后的源文件,文本文件);
.s 汇编文件(文本文件);
.so 共享库文件;
1.4 头文件写什么?
各种声明。其它写源文件中。使用者只需头文件和库文件即可。
1.5 编译过程(从源代码到可执行文件)
a 预处理:gcc –E file.c –o file.i //o 应该是 output 的意思,而非 object
b 汇编:gcc –S file.i //生成同名的.s 文件,也可输入源文件
c 目标代码生成:gcc –c file.s //生成同名的.o 文件,可输入源文件和.i 文件
d 连接:gcc file.o –o exe_file //生成可执行文件 exe_file,输入源文件可一步到位
实例:
一个源文件就是一个编译单元,编译生成对应的目标文件,多个目标文件可连接成一个可
执行文件。
1.6 gcc 专用预处理指令
产生一个错误,遇到错误停止(预)编译,输出错误信息;
#error
#warning 产生一个警告,但不停止编译;
#pragma 额外指定功能,增强指令;
#pragma GCC dependency “file_name”
若 file_name 比当前文件更新(比较最后修改时间),将产生警告;
#pragma GCC poison symbol
符号 symbol 不能使用,否则产生错误;
#pragm pack(integer)
改变结构体的对齐和补齐方式:integer=1,不对齐和补齐;integer=2,按 2 字节做对
齐和补齐,还可以取其它值;
1.7 a.c 文件中定义了全局变量 g,如何在 b.c 中使用,在 b.c 中作如下声明:
extern type g;
如果不想 g 被其它文件访问,在 a.c 中这样定义 g(即使在其它文件中 extern 也不行):
static type g;
2 编程环境
2.1 常用环境变量(注意大小写)
PATH 系统路径
CPATH, C_INCLUDE_PATH C 语言头文件路径
LD_LIBRARY_PATH 共享库路径
LIBRARY_PATH 库路径
路径分隔符:”:”(Unix), “;”(Windows);
2.2 设置环境变量 PATH(bash):
a export PATH=new_dir:$PATH
=前后不能有空格
b 将 上 述 命 令 加 入 当 前 用 户 启 动 文 件 : /home/{user_name}/.bashrc;
/home/{user_name}/.bash_profile;
重启生效或$source .bashrc
2.3 头文件的查找
a 系统默认路径:/usr/include 等;
b 环境变量 CPATH,C_INCLUDE_PATH 指向的路径;
c gcc –I header_dir,编译时指定头文件路径;
d #include “path”, 目录 path。
2.4 在源文件中实现头文件中声明的函数时,可以不 include 相应的头文件。
2.5 源文件编译打包成库文件,发布头文件和库文件而不发布源文件的方式:
a 静态库:归档目标代码,连接时直接复制代码段到目标;
连接时使用 gcc –static 选项生成可执行文件
b 动态库(共享库):归档目标代码,连接时在目标中仅写入调用地址;
1 静态库的创建和使用
创建:
a 生成目标文件:$gcc –c {src_file_list}
b 生成库:$ar rs|-r {lib_file_name} {obj_file_list}
库文件命名规则:lib{name}.a
c 发布库文件和头文件。
使用:
a 连接目标文件和库文件:
$gcc {obj_file_list} {lib_file_name}
添加库文件路径至 LIBRARY_PATH,执行命令(lib_name 不含前缀 lib 和扩展名.a):
$gcc {obj_file_list} –l {name}
不添加 LIBRARY_PATH 执行命令:
$gcc {obj_file_list} –l {name} –L {library_path}
即使库文件在当前目录下,也要指定库的路径: -L .
为什么-l 使用库的名字而非文件名?因为 gcc 优先搜索动态库。
b 发布可执行文件即可。
2 动态库的创建和使用
创建:
a 生成目标文件:$gcc –c –fpic {src_file_list}
b 生成.so 库:$gcc –shared {obj_file_list} –o lib{name}.so
c 发布头文件和库文件。
使用:
a 连接目标和库文件:
$gcc {obj_file_list} –l{name} –L {library_path}
b 发布可执行文件和动态库。
c 运行时添加库路径至 LD_LIBRARY_PATH,再执行可执行文件。
3 与库有关的命令
ldd 查询库文件或可执行文件对其它库的依赖;
4 运行时调用共享库
a 打开一个共享库文件(
);
void *dlopen(const char *filename, int flag)
flag 须是 RTLD_LAZY 和 RTLD_NOW 之一;
b 在打开库中获取一个函数;
void *dlsym(void *handle, const char *symbol)
c 调用函数;
d 关闭共享库。
int dlclose(void *handle)
连接时使用-ldl(置于命令末:$gcc obj_file_list –o exe_file -ldl)。
出错信息 char *dlerror():在调用上述三个函数后调用此函数,如有错发生返回错误信息,
否则返回 NULL;
5 C 程序错误处理
5.1 通过函数的返回值代表错误:
a 如果返回指针类型,一般用 NULL 代表错误;
b 如果返回 int 类型,若返回值不可能为负,-1 表示错误;若返回值可为任何值,可使用指
针带出返回数据,return 0 代表成功,return -1 代表失败;
c 不需要考虑出错,返回值设为 void;
5.2 C 错误处理方式
全局变量:errno 代表错误编号,#include;
perror(const char *) 输出错误信息到终端,参数指定额外信息,#include;
char *strerror(int errno) 转换错误代码到字符串,#include
不是所有的函数在出错时都将错误代码写入 errno。
不能用 errno 来判断函数调用是否正确,仅当函数返回值指明其出错时,才应调用 errno 检
查具体错误。errno 的值直到出错才会被修改!
6 环境变量获取
6.1 环境表: 环境变量名值对的字符指针数组
extern char** environ;
environ 的最后一个元素存放 NULL!
6.2 单个环境变量操作
char *getenv(const char *name) name 指定环境变量名,返回变量值;
1 环境变量操纵函数
#include
char *getenv(const char *name);
获取环境变量;
int setenv(const char *name, const char *value, int overwrite);
若 name 不存在设置新的名值对,若 name 已存在,且 overwrite 非零,给 name 设置新的
value;
int unsetenv(const char *name);
删除环境变量 name;
int putenv(char * name_value_pair);
设置环境变量:name_value_pair 格式:name=value;
2 main()函数的参数
完整格式:
int main(int argc, char **argv, char **env);
argv:参数列表,包括可执行文件名;
argc:argv 的个数;
env:环境变量列表,同 extern char **environ 全局变量。
3 内存管理相关函数图
STL -> 自动分配和自动回收
|
C++ -> new 和 delete 关键字
|
C -> malloc()和 free()
|
系统函数 -> sbrk()和 brk()
|
底层系统函数 -> mmap()和 munmap()
____________________________________________________用户层
|
内核函数 -> kmalloc(), vmalloc(), get_free_page()等
____________________________________________________核心层
4 进程和程序
4.1 程序:保存在辅助存储器上的可执行文件;进程:内存中正在执行的程序;
4.2 进程的内存模型
a 代码段:存放指令(包括函数),只读;
b 全局段:存放全局变量,static 局部变量,读写;
c bss 段:未初始化的全局变量,在 main()执行之前全部清零;
d 栈:存放局部变量,包括函数形参,内存自动分配和回收;
e 堆(数据段):程序员管理;
f 只读常量段:和代码段接近,有些资料将其和代码段合并一起讲。
排列次序:代码段、只读常量段、全局段、bss 段、堆、栈;
堆从低地址向高地址分配,栈从高地址(接近 3G)向低地址分配。
4.3 虚拟内存地址和物理内存
a Linux 使用虚拟内存地址进行内存管理,每个进程都有 0-4G 的虚拟内存地址(32bit 无符
号整数),0-3G 用户空间,3-4G 内核空间;用户空间不能直接访问内核空间,可通过系统
调用访问;
b 虚拟内存必须先映射到一段物理内存或硬盘上的文件才能使用,分配内存就是将虚拟内存
映射到物理内存,未经映射而使用将引发段错误;
c 程序中操纵的都是虚拟内存地址;
d 内存管理的基本单位:页(4K),内存的分配和回收以页为基本单位;
5 malloc 和 free 函数特征
5.1 malloc 分配内存时,除了分配申请的数量之外,还需要额外分配一定量的空间,以存储
额外的信息(实际分配数量,下一个区域的指针,如何回收等),这些信息存储在一个链表
中;
5.2 malloc 分配物理内存时,按照一次 33 个内存页分配,如果一次性申请超过 33 页,将分
配比指定数稍多的页数;
5.3 free 并不保证立即释放物理内存,释放到了最后一个变量时,系统依然后保留 33 个内存
页不释放;
5.4 什么情况下引发段错误?
a 引用没有映射物理内存的虚拟地址时;
b 写只读内存页。
1 brk 和 sbrk
都可以调整进程数据段(堆)的 break,brk 是系统调用,sbrk 是库函数,功能一致。
#include
int brk(void *addr);
void *sbrk(int increase);
1.1 对于 sbrk,参数为 0 返回当前进程的 break,参数非零返回前一次的 break!失败返回
(void*)-1.
实例:
输出:
1.2 brk 将当前的 break 设置为指定值,成功返回 0,失败-1.
实例: