一、环境介绍
我目前使用的是ubuntu的16.04版本,所以的话这篇文章所有的截图都来自于ubuntu下的终端,个人的话推荐不要装16.04,或者说不要装ubuntu,centos相对于它更加稳定(前提是你能够接受那种丑爆了的桌面)。我用ubuntu的时候wifi就经常炸,各种奇葩的连不上或者说一直连,然后还是连不上。
这个时候推荐使用下面的命令:
sudo service network-manager restart```
不过这个命令有时候还是不好使,所以只能是重启,总的来说这就很尴尬了,所以大家懂的···
##二、编译器
***
gcc是linux下自带的编译器,其功能可以说很强大,可以编译c,c++,java等多门语言,不过这里我讲的是关于c语言编译的时候的一些使用方法。在此之前,你至少需要知道下面的一些基本知识,然后后面的一些命令才好进行讲解。
#####(一)基础知识
- 从你写好的代码到生成最终的可运行文件,大致分为以下几步:
- 对源文件进行预处理,处理除#pragma以外以#号开头的命令,去除文件中的空格,注释,添加行号和文件标识(在生成调试文件时有用)。
- 对处理过的文件进行编译,对语法进行解析,在进行相应的优化后,生成汇编代码,但是生成的是AT&T语法格式的汇编代码,而不是intel的。
- 将汇编代码通过几乎是直接翻译的方式生成机器码,然后你打开文件就会看到一堆二进制码。
- 最后一步链接,就是将生成的二进制文件与静态库链接或者做动态库的链接标记。如果说没有自己创建库文件,这一步可以由编译器自动完成。
- 库文件
- 库文件分为静态库和动态库。
- 库文件在linux下为.a或.so文件,它们的名字以lib开头,是经过编译了的源文件,它在生成时需要有一个头文件(.h)标注其所含有的函数以及这些函数的类型定义,同时需要有一个源文件(.c),实现所有的函数。比如:
calc.h:
double aver(double, double);
double sum(double, double);```
calc.c:
double aver(double num1, double num2)
{
return (num1+num2)/2;
}
double sum(double num1, double num2)
{
return num1+num2;
}```
- 静态库在编译的时候会把代码放入程序中,而动态库则是在程序中留下调用的链接,等到程序运行的时候再将动态库加载进入内存进行使用,所以对于同样的程序,使用动态库会使程序文件本身占空间更少。
- 动态库可以多个程序共用,而只占一份的空间。而静态库本身是不共享的,也就是说一个程序要使用,就要自己留有一份代码。
- gcc编译器可以控制编译的过程,通过在终端(terminal)中输入不同的命令,可以生成从源文件到可运行程序间的各种文件。
- .s汇编语言文件
- .o二进制文件
- .i仅仅进行预处理的文件
- .a静态库文件
- .so动态库文件
#####(二)gcc基础命令
gcc的命令超过了100条,所以这里仅仅只是列举一些常常用到的命令,其余的命令会在后面需要时再列举。这里我用到了一个很简单的hello.c作为例子进行讲解。
include <stdio.h>
int main()
{
printf("hello world");
return 0;
}```
- 直接生成一个可以运行的文件
gcc hello.c```
这个时候你应该可以在当前的目录下找到a.out文件,因为在命令中没有指定最终生成的文件名,所以会输出一个带有默认文件名的文件。如果你想指定生成文件的名字,可以用-o命令。
gcc hello.c -o hello```
这样就可以生成一个hello的可运行程序,最后你需要做的就是再输入下面的命令让程序运行起来。
./hello```
- 生成一个仅仅进行过预处理的文件
gcc -E hello.c -o hello.i```
这里的-E命令告诉编译器在预处理后就停止,至于hello.i你可以改成别的名字,只是一般习惯于把预处理文件的后缀名命名为i。这个时候打开文件,你可以看到所有的stdio.h中的内容都导入到了hello.i中。
- 对预处理过的文件进行编译
gcc -S hello.i -o hello.s```
这里注意是大写的S,等你再次打开文件的时候,熟悉的c代码就变成了汇编代码,同样的,.s文件常常表示汇编语言文件。
- 将汇编代码翻译为二进制代码
gcc -c hello.s -o hello.o```
这个时候你写的源文件的转换就已经结束了,但是这个时候的文件还不能使用,因为这个文件还没有和库文件进行链接,许许多多的函数在头文件中只有定义,如果你要使用这些函数,就需要与动态库或者静态库进行链接。
- 进行链接
gcc hello.o -o hello```
将二进制文件链接,最终生成可以运行的hello文件。
实际上,如果你只想要自定义链接过程,你可以从源文件开始,直接生成没有链接的二进制文件,命令和上面的差不多
gcc -c hello.c -o hello.o```
上面的命令包括-c -E -S其实都是停止命令,意思是告诉编译器在某一步骤停下来,所以说gcc会自动判断目前文件所在的步骤,你需要做的就是告诉编译器怎么停下来。
(三)动态库和静态库
这里主要是讲在编译的时候使用库文件,制作库文件只是简单说一下,制作静态库和动态库的制作和gcc的关系并不是很大。如果你要生成静态库,你需要的东西有:你的.o文件和库的头文件。然后你需要运行的命令如下:
ar rc libmylib.a mylib.o```
如果你的库文件由多个.o文件组成,你只需要把.o文件都列出来就ok了
ar rc libmylib.a mylib1.o mylib2.o```
完成后你的目录下就会生成一个libmylib.a的文件,需要注意到的是,不论是动态库还是静态库都需要以lib作为命名的前缀。然后生成动态库的命令要稍微长一些:
gcc -shared -o libmylib.somylib1.o mylib2.o```
说完了怎么制作,就要说说怎么用了,如果你要使用静态库,那么原本的编译命令中仅仅需要加上几个字母:
gcc test.c -lmylib -L . -o test```
需要注意的是第一个-l是库名,但是省略了lib这个前缀和.a的扩展名,第二个-L是说明编译器寻找库的位置,‘.’代表了当前文件夹,如果在别的文件夹寻找,则可以用正常的地址说明(比如‘..’代表上一个文件夹,别的的话可以用绝对地址和相对地址)。最后如果你要使用动态库的话,就自己左转百度吧,因为那个用起来比较麻烦,需要设置系统的寻找路径,如果只是简单的程序,静态库就足够了。
下面是我做的一个简单的例子,演示了上面的过程,代码拙劣,希望不要笑话我(我只是个学生),当然有什么建议还是希望指出。
这个是最早的三个源文件
stack.h是用来后期给引用库文件的程序include的,里面存放了函数的原型和数据结构
stack.h
#pragma once
#ifndef STACK_INTERFACE
#define STACK_INTERFACE
typedef struct {
char *base;
char *top;
int stack_size;
int ele_size;
}Stack;
Stack* stackInit(int ele_size);
int stackDes(Stack *stack);
int stackPop(Stack *stack, void *value);
int stackPush(Stack *stack, void *value);
int stackGetTop(Stack *stack,void *value);
int stackEleNum(Stack *stack);
#endif
stackBase.h主要是存放了一些基本定义和宏函数
stackBase.h
//基本定义
#include <stdio.h>
#include <stdlib.h>
#pragma once
#ifndef JUDGEMENT
#define JUDGEMENT
#define YES 1;
#define NO 0;
#endif
#ifndef STACK_ERROR
#define STACK_ERROR
#define OVERFLOW -1
#define STACK_EMPTY -2
#endif
#ifndef STACK_INI
#define STACK_INI
#define STACK_INIT_SIZE 40
#define STACK_INCREMENT_SIZE 40
#endif
#ifndef STACK_STRUCT
#define STACK_STRUCT
typedef struct {
char *base;
char *top;
int stack_size;
int ele_size;
}Stack;
#endif
stackBase.h
//用于扩展栈的大小
#ifndef STACK_BASE_FUNC
#define STACK_BASE_FUNC
/*
*@param char* _INCRE_BASE_
*@param char* _INCRE_TOP_
*@param int _INCRE_STACK_SIZE_
*@param int _INCRE_STACK_ELE_SIZE_
*
*/
#define stackIncre(_INCRE_BASE_, _INCRE_TOP_, _INCRE_STACK_SIZE_, \
_INCRE_STACK_ELE_SIZE_) \
({ \
int _INCRE_RETURN_; \
_INCRE_BASE_ = realloc(_INCRE_BASE_, _INCRE_STACK_SIZE_ + \
STACK_INCREMENT_SIZE * _INCRE_STACK_ELE_SIZE_); \
if(_INCRE_BASE_) { \
_INCRE_TOP_ = _INCRE_BASE_ + _INCRE_STACK_SIZE_; \
_INCRE_STACK_SIZE_ += STACK_INCREMENT_SIZE * \
_INCRE_STACK_ELE_SIZE_; \
_INCRE_RETURN_ = 0; \
} else { \
_INCRE_RETURN_ = OVERFLOW; \
} \
_INCRE_RETURN_; \
})
stackBase.h
//检查栈是否为空
/*
*@param char* _EMPTY_BASE_
*@param char* _EMPTY_TOP_
*
*/
#define stackEmpty(_EMPTY_BASE_, _EMPTY_TOP_) \
({ \
(_EMPTY_BASE_ == _EMPTY_TOP_) ? 1 : 0; \
})
stackBase.h
//检查栈是否已满
/*
*@param char* _FULL_BASE_
*@param char* _FULL_TOP_
*@param int _FULL_STACK_SIZE_
*
*/
#define stackFull(_FULL_BASE_, _FULL_TOP_, _FULL_STACK_SIZE_) \
({ \
(_FULL_TOP_ - _FULL_BASE_ == _FULL_STACK_SIZE_) ? 1 : 0; \
})
#endif
stackBase.h
//向栈中插入数据
#ifndef DATA_HANDLE
#define DATA_HANDLE
/*
* Insert data into stack.
*
* @param char* _IN_TOP_
* @param char* _IN_DATA_
* @param int _IN_ELE_SIZE_
*
*/
#define stackDataIn(_IN_TOP_, _IN_DATA_, _IN_ELE_SIZE_) \
do { \
int _IN_TEMP_COUNT_ = 0; \
while(_IN_TEMP_COUNT_++ < _IN_ELE_SIZE_) { \
*(_IN_TOP_)++ = *(_IN_DATA_)++; \
} \
} while(0)
stackBase.h
//删除栈中的数据
/*
* Delete data from stack
*
* @param char* _OUT_TOP_
* @param char* _OUT_DATA_
* @param int _OUT_ELE_SIZE_
*
*/
#define stackDataOut(_OUT_TOP_, _OUT_DATA_, _OUT_ELE_SIZE_) \
do { \
int _OUT_TEMP_COUNT_ = 0; \
(_OUT_DATA_) += _OUT_ELE_SIZE_; \
while(_OUT_TEMP_COUNT_++ < _OUT_ELE_SIZE_) { \
*--(_OUT_DATA_) = *--(_OUT_TOP_); \
} \
} while(0)
#endif
最后的.c文件放了真正的函数
stackInterface.c
#include "stackBase.h"
//初始化一个栈
Stack* stackInit(int ele_size)
{
Stack *init = (Stack *)malloc(sizeof(Stack));
if(init->base = (char *)malloc(ele_size * STACK_INIT_SIZE)) {
init->stack_size = ele_size * STACK_INIT_SIZE;
init->top = init->base;
init->ele_size = ele_size;
} else {
init->top = init->base = 0;
init->stack_size = 0;
init->ele_size = 0;
}
return init;
}
stackInterface.c
//将元素出栈
int stackPop(Stack *stack, void *value)
{
char *cvalue = (char *)value;
int empty = stackEmpty(stack->base, stack->top);
if(empty) {
return STACK_EMPTY;//stack_empty
}
stackDataOut(stack->top, cvalue, stack->ele_size);
return 0;
}
stackInterface.c
//将元素入栈
int stackPush(Stack *stack, void *value)
{
char *cvalue = (char *)value;
if(stackFull(stack->base, stack->top, stack->stack_size)) {
if(stackIncre(stack->base, stack->top, stack->stack_size, stack->ele_size)) {
return OVERFLOW;
}
}
stackDataIn(stack->top, cvalue, stack->ele_size);
return 0;
}
stackInterface.c
//获取栈顶元素而不出栈
int stackGetTop(Stack *stack, void *value)
{
char *cvalue = (char *)value;
if(stackEmpty(stack->base, stack->top)) {
return STACK_EMPTY;//stack_empty
}
stackDataOut(stack->top, cvalue, stack->ele_size);
stack->top += stack->ele_size;
return 0;
}
//释放栈空间
void stackDes(Stack *stack)
{
free(stack->base);
stack->base = stack->top = 0;
stack->stack_size = 0;
}
//计数栈中元素的个数
int stackEleNum(Stack *stack)
{
return (stack->top - stack->base) / stack->ele_size;
}
这些代码写完了之后,依次使用如下的命令
gcc -c stackInterface.c -O2
ar rc libstack.a stackInterface.o
就会生成一个静态库(不要在意Makefile文件,下一节讲)
然后如果哪一天要用到这个库了,记得在程序里面包含stack.h这个头文件然后按照上面的命令就ok了。