Unix 环境高级编程(一):开发环境
- 一、Unix操作系统
- 二、Linux操作系统
- 三、GNU编译工具(GCC)
- 1、简介
- 2、基本用法
- 3、文件后缀
- 4、构建过程
- 5、预处理指令
- 6、预定义宏
- 7、环境变量
- 四、静态库
- 1、简介
- 2、创建静态库
- 3、ar 指令
- 4、调用静态库
- 五、共享库
-
- 六、动态加载共享库
- 1、头文件
- 2、加载共享库
- 3、获取函数地址
- 4、卸载共享库
- 5、 获取错误信息
- 七、辅助工具
一、Unix操作系统
二、Linux操作系统
三、GNU编译工具(GCC)
1、简介
GCC是以GPL许可证所发行的自由软件,也是GNU计划的关键部分。GCC的初衷是为GNU操作系统专门编写一款编译器,现已被大多数类Unix操作系统(如Linux、BSD、MacOS X等)采纳为标准的编译器,甚至在微软的Windows上也可以使用GCC。GCC支持多种计算机体系结构芯片,如x86、ARM、MIPS等,并已被移植到其他多种硬件平台。
GCC原名为GNU C语言编译器(GNU C Compiler),只能处理C语言。但其很快扩展,变得可处理C++,后来又扩展为能够支持更多编程语言,如Fortran、Pascal、Objective -C、Java、Ada、Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)。
2、基本用法
gcc [options] [filenames] /* GCC最基本的用法是∶gcc [options] [filenames] * 其中 options 就是编译器所需要的参数* filenames 给出相关的文件名称* */-c /* 只编译,不链接成为可执行文件,* 编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,* 通常用于编译不包含主程序的子程序文件 * */-o /* output_filename,确定输出文件的名称为output_filename,* 同时这个名称不能和源文件同名。* 如果不给出这个选项,gcc就给出预设的可执行文件a.out * */-x /* 设定文件所使用的语言, 使后缀名无效, 对以后的多个有效* 根据约定 C 语言的后缀名称是 .c 的,* 而 C++ 的后缀名是 .C 或者 .cpp, * 如果你很个性,决定你的 C 代码文件的后缀名是 .pig ,* 那你就要用这个参数, 这个参数对他后面的文件名都起作用,* 除非到了下一个参数的使用* */-I /* Idirname,* 将dirname所指出的目录加入到程序头文件目录列表中,* 是在预编译过程中使用的参数 * */-E /* 只激活预处理,这个不生成文件, * 你需要把它重定向到一个输出文件里面 * */-S /* 只激活预处理和编译,就是指把文件编译成为汇编代码 */-g /* 产生符号调试工具(GNU的gdb)所必要的符号资讯,* 要想对源代码进行调试,我们就必须加入这个选项* */-O /* 对程序进行优化编译、链接,* 采用这个选项,整个源代码会在编译、链接过程中进行优化处理,* 这样产生的可执行文件的执行效率可以提高,* 但是,编译、链接的速度就相应地要慢一些 * */-v /* gcc执行时执行的详细过程,gcc及其相关程序的版本号 */-pedantic /* 对不符合ANSI/ISO C语言标准的扩展语法产生警告 */-Wall /* 产生尽可能多的警告 */-Werror /* 将警告作为错误处理 */
3、文件后缀
.c/* C语言源代码文件 */.h/* 程序所包含的头文件 */.i/* 预处理后的C语言源代码文件 */.s/* 汇编语言文件 */.S/* 经过预编译的汇编语言源代码文件 */.o/* 编译后的目标文件 */.a/* 静态库文件 */.so/* 共享库(动态库)文件 */
4、构建过程
编辑 -> 预编译(预处理)-> 编译 -> 汇编 -> 链接
编辑(hello.c) /* 使用 vim 编辑器编写代码 */vim hello.c
预编译(hello.i) /* 使用 -E 选项,生成 .i 预编译文件* 这个过程处理宏定义和include,去除注释,不会对语法进行检查*/gcc -E hello.c -o hello.i
编译(hello.s) /* 使用 -S 选项,生成 .s 汇编文件* 这个阶段,检查语法*/gcc -S hello.i
汇编(hello.o) /* 使用 -c 选项,生成 .o 目标文件 */gcc -c hello.s
链接(hello) /* 使用 -o 选项,生成可执行文件 */gcc hello.o -o hello
5、预处理指令
#include // 将指定文件的内容插至此指令处 #include_next // 与#include一样,但从当前目录之后的目录查找,极少用#define // 定义宏#undef // 删除宏#if // 判定#ifdef // 判定宏是否已定义#ifndef // 判定宏是否未定义#else // 与#if、#ifdef、#ifndef结合使用#elif // else if多选分支#endif // 结束判定## // 连接宏内两个连续的字符串# // 将宏参数扩展成字符串字面值#error // 产生错误,结束预处理#warning // 产生警告#pragma // 提供额外信息的标准方法,可用于指定平台#pragma GCC dependency <文件> // 若<文件>比此文件新则产生警告#pragma GCC poison <标识> // 若出现<标识>则产生错 误#pragma pack(1/2/4/8) // 按1/2/4/8字节对齐补齐#line // 指定行号
For example:
error.c #include <stdio.h>#if (VERSION < 1)#error "Version is too low!"#elif (VERSION > 4)#warning "version is too high!"#endifint main(void){printf("Version is :%d\n", VERSION);return 0;}
line.c #include <stdio.h>int main(void){printf("line is %d\n", __LINE__);#line 100printf("line is %d\n", __LINE__);return 0;}
pragma.c #include <stdio.h>#pragma GCC dependency "tmp.c" // 若tmp.c比此文件新则产生警告int main(void){return 0;} #include <stdio.h>#pragma GCC poison goto float // 若出现 goto 或 float 则产生错误int main(void){float a;loop :goto loop;return 0;} /* 64位操作系统下,默认8字节对齐 */#include <stdio.h>#pragma pack(1)struct S1 {double d;char c;int i;short h;}; // DDDDDDDDCIIIIHH, 15#pragma pack(4)struct S2 {double d;char c;int i;short h;}; // DDDDDDDDCXXXIIIIHHXX, 20#pragma pack(8)struct S3 {double d;char c;int i;short h;}; // DDDDDDDDCXXXXXXXIIIIHHXX, 24int main(void){#pragma pack()printf ("S1: %lu字节\n", sizeof (struct S1));printf ("S2: %lu字节\n", sizeof (struct S2));printf ("S3: %lu字节\n", sizeof (struct S3));return 0;}
6、预定义宏
__BASE_FILE__ // 正在编译的源文件名__FILE__ // 所在文件名__LINE__ // 行号__FUNCTION__ // 函数名__func__ // 同__FUNCTION____DATE__ // 日期__TIME__ // 时间__INCLUDE_LEVEL__ // 包含层数,从0开始__cplusplus // C++编译器将其定义为1,// C编译器不定义该宏
For example:
print.h #ifndef _PRINT_H#define _PRINT_H#include <stdio.h>void print (void) {printf ("__BASE_FILE__ : %s\n", __BASE_FILE__);printf ("__FILE__ : %s\n", __FILE__);printf ("__LINE__ : %d\n", __LINE__);printf ("__FUNCTION__ : %s\n", __FUNCTION__);printf ("__func__ : %s\n", __func__);printf ("__DATE__ : %s\n", __DATE__);printf ("__TIME__ : %s\n", __TIME__);printf ("__INCLUDE_LEVEL__ : %d\n", __INCLUDE_LEVEL__);#ifdef __cplusplusprintf ("__cplusplus : %d\n", __cplusplus);#endif // __cplusplus}#endif//_PRINT_H
predef.h #ifndef _PREDEF_H#define _PREDEF_H#include "print.h"#endif//_PREDEF_H
predef.c #include "predef.h"int main (void) {print ();return 0;}
7、环境变量
C_INCLUDE_PATH // C头文件的附加搜索路径,相当于gcc的-I选项CPATH // 同C_INCLUDE_PATHCPLUS_INCLUDE_PATH // C++头文件的附加搜索路径LIBRARY_PATH // 链接时查找静态库/共享库的路径LD_LIBRARY_PATH // 运行时查找共享库的路径
通过gcc的-I选项指定C/C++头文件的附加搜索路径 gcc calc.c cpath.c -I.
将当前目录作为C头文件附加搜索路径,添加到CPATH环境变量中 export CPATH=$CPATH:. // export保证当前shell的,子进程继承此环境变量echo $CPATHenv | grep CPATH
也可以在/.bashrc或/.bash_profile,配置文件中写环境变量,永久有效 export CPATH=$CPATH:.执行# source ~/.bashrc 或 # source ~/.bash_profile//生效,以后每次登录自动生效。
头文件的三种定位方式
a)#include “目录/xxx.h”
头文件路径发生变化,需要修改源程序
b)C_INCLUDE_PATH/CPATH=目录
同时构建多个工程,可能引发冲突
c)gcc -I目录
既不用改程序,也不会有冲突头文件的作用
a)声明外部变量、函数和类
b)定义宏、类型别名和自定义类型
c)包含其它头文件
d)借助头文件卫士,防止因同一个头文件被多次包含,而引发重定义错包含头文件时需要注意的问题
a)gcc的-I选项
指定头文件附加搜索路径
b)#include <…>
先找-I指定的目录,再找系统目录
c)#include “…”
先找-I指定的目录,再找当前目录,最后找系统目录
d)头文件的系统目录 /usr/include /usr/local/include /usr/lib/gcc/x86_64-linux-gnu/5.4.0/include
四、静态库
1、简介
链接静态库是将库中的被调用代码复制到调用模块中静态库占用空间非常大,不易修改但执行效率高静态库的缺省扩展名是.a 2、创建静态库
编辑源代码 vim xxx.c/xxx.h
编译成目标文件 gcc -c xxx.c -o xxx.o
打包成静态库文件 ar -r libxxx.a xxx.o ...
3、ar 指令
ar指令:ar [选项] 静态库文件名 目标文件列表 -r // 将目标文件插入到静态库中,已存在则更新 -q // 将目标文件追加到静态库尾 -d // 从静态库中删除目标文件 -t // 列表显示静态库中的目标文件 -x // 将静态库展开为目标文件 /* 注意:提供静态库的同时也需要提供头文件 */
4、调用静态库
直接调用 gcc main.c libxxx.a
通过LIBRARY_PATH环境变量指定库路径 export LIBRARY_PATH=$LIBRARY_PATH:.gcc main.c -lmath (环境法)
通过gcc的-L选项指定库路径 unset LIBRARY_PATHgcc main.c -lmath -L. (参数法)
一般化的方法: gcc .c/.o -l<库名> -L<库路径>
运行
在可执行程序的链接阶段,已将所调用的函数的二进制代码,复制到可执行程序中,因此运行时不需要依赖静态库。 五、共享库
1、简介
链接共享库则只是在调用模块中,嵌入被调用代码在库中的(相对)地址共享库占用空间小,易于修改但执行效率略低共享库的缺省扩展名是.so 2、创建共享库
编辑源程序 vim xxx.x/xxx.h
编译成目标文件 gcc -c -fpic xxx.c
链接成共享库文件 gcc -shared xxx.o -o libxxx.so
PIC (Position Independent Code)
位置无关代码。可执行程序加载它们时,可将其映射到其地址空间的任何位置。 -fPIC // 大模式,生成代码比较大,运行速度比较慢,所有平台都支持。 -fpic // 小模式,生成代码比较小,运行速度比较快,仅部分平台支持。 /* 注意:提供共享库的同时也需要提供头文件 */
3、调用共享库
直接调用 gcc main.c libxxx.so
通过LIBRARY_PATH环境变量指定库路径 export LIBRARY_PATH=$LIBRARY_PATH:.gcc main.c -lmath (环境法)
通过gcc的-L选项指定库路径 unset LIBRARY_PATHgcc main.c -lmath -L. (参数法)
一般化的方法 gcc .c/.o -l<库名> -L<库路径>
3、运行
运行时需要保证LD_LIBRARY_PATH,环境变量中包含共享库所在的路径。
在可执行程序的链接阶段,并不将所调用函数的二进制代码复制到可执行程序中。
而只是将该函数在共享库中的地址嵌入到可执行程序中,因此运行时需要依赖共享库。
gcc缺省链接共享库,可通过-static选项强制链接静态库。
六、动态加载共享库
1、头文件
#include <dlfcn.h>
2、加载共享库
/** 返回值:* 成功返回共享库句柄,失败返回NULL。* * 参数:* filename:共享库路径* 若只给文件名,则根据LD_LIBRARY_PATH环境变量搜索。* flag取值:加载方式* RTLD_LAZY - 延迟加载,使用共享库中的符号 (如调用函数)时才加载。 * RTLD_NOW - 立即加载。*/void* dlopen (const char* filename, int flag);
3、获取函数地址
/** 返回值:* 成功返回函数地址,失败返回NULL。* * 参数:* hanedle:共享库句柄* symbol:函数名*/void* dlsym (void* handle,const char* symbol );
4、卸载共享库
/** 成功返回0,失败返回非零。*/int dlclose (void* handle);
5、 获取错误信息
/** 有错误发生则返回错误信息字符串指针,否则返回NULL。*/char* dlerror (void);
七、辅助工具