gcc编译、动态库静态库及Makefile知识汇总
gcc编译过程
我们在Linux下使用vim命令写的.c文件,想要这个文件编译、运行的话就要使用"gcc [选项] 文件名" 这个格式的命令来操作,具体有四个步骤。通过下面的图片来简单介绍:
生成可执行程序过程为成四个步骤:
1、由.c文件到.i文件,这个过程叫预处理。
2、由.i文件到.s文件,这个过程叫编译。
3、由.s文件到.o文件,这个过程叫汇编。
4、由.o文件到可执行文件,这个过程叫链接。
1、预处理:
预处理是读取c源程序,对其中的伪指令和特殊符号进行“替代”处理;经过此处理,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,仍然是C文件,但内容有所不同。伪指令主要包括以下三个方面:
(1)宏定义指令,如#define NAME TokenString, #undef以及编译器内建的一些宏,如_DATE_,FILE, LINE, TIME,_FUNCTION_等。
(2)条件编译指令,如#ifdef, #ifndef, #else, #elif, #endif等。
(3)头文件包含指令,如#include “FileName”或者#include 等。
预处理的过程主要包括以下过程:
将所有的#define删除,并且展开所有的宏定义
处理所有的条件预编译指令,比如#if 、#ifdef、#elif、#else、#endif等
处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
删除所有注释“//”和“ / /”
添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
保留所有的#pragma编译器指令,因为编译器需要使用它们
2、编译
编译程序所要做的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码
3、汇编
汇编过程实际上把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个c语言源程序,都将最终经过一处理而得到相应的目标文件。
4、链接
汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,在某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件的函数,等等,所有的这些问题,都需要经过链接才能得以解决,链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体,也就是可执行程序,根据开发人员指定的库函数的链接方式的不同,链接处理可分为两种:①静态链接 ②动态链接。
动态链接:动态链接使用动态链接库进行链接,生成的程序在执行的时候需要加载所需的动态库才能运行。 动态链接生成的程序体积较小,但是必须依赖所需的动态库,否则无法执行。
静态链接:静态链接使用静态库进行链接,生成的程序包含程序运行所需要的全部库,可以直接运行,不过静态链接生成的程序体积较大。
对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越,在某些情况下动态链接可能带来一些性能上的损害。
在Linux下可以使用gcc --help 来查看详细内容
gcc的常用选项
选项 | 说明 |
---|
-E | 只进行预处理,不编译(简单的宏替代,伪指令的处理) |
-S | 只编译,不汇编 |
-c | 只编译、汇编,不链接 |
-g | 编译生成可执行文件包含gdb调试信息,可被gsb调试 |
-o | 指定编译生成可执行文件名 |
-I(i大写) | 指定include包含文件的搜索目录 |
-L | 指定链接所需库(动态库或静态库)所在路径 |
-l(L小写) | 指定所需链接库名 |
-ansi | ANSI标准 |
-std=c99 | c99标准 |
-werror | 不区分警告和错误,遇到任何警告都停止编译 |
-wall | 开启大部分警告提示 |
–static | 静态编译 |
-static | 静态链接 |
-O0 | 关闭所有优化选项 |
-O1 | 第一级别优化,使用此选项可使可执行文件更小、运行更快,并不会增加太多编译时间,简写为-O |
-O2 | 第二级别优化,采用了几乎所有的优化技术,使用此选项会延长编译时间 |
-O3 | 第三级别优化,在-O2的基础上增加了产生inline函数、使用寄存器等优化技术 |
-Os | 此选项类似-O2,作用是优化所占用的空间,但不会进行性能优化,常用生成最终版本 |
静态库,动态库并制作;
库是什么呢?本质上来说,库时一种可执行代码的二进制形式,可以被操作系统载
入内存执行。我们通常将一些通用函数写成函数库,所以库是别人写好的,现有的,成熟的,可以复用的代码,你可以使用但要必须得遵守许可协议。在我们现实开发过程中,不可能每一份代码都从头编写,当我们拥有库时,我们就可以直接将我们所需要的文件链接到我们的程序中。可以为我们节省大量的时间,提高开发效率。Linux下库分为两种,静态库和动态库。这两种库相
同点是两种库都是由.o文件生成的
1, 静态库
静态库文件名的命名方式是“libxxx.a”,库名前加”lib”,windows和linux下都是后缀用”.a”,“xxx”为静态库名,windows下的静态库名也叫libxxx.a;
链接时间: 静态库的代码是在编译过程中被载入程序中。
链接方式:静态库的链接是将整个函数库的所有数据都整合进了目标代码。这样做优点是在编译后的执行程序不在需要外部的函数库支持,因为所使用的函数都已经被编进去了。缺点是,如果所使用的静态库发生更新改变,你的程序必须重新编译。
1.1制作静态库
首先,我们需要有三个文件,分别为 function.c、fuction.h、test.c .
#include <stdio.h>
#include<string.h>
char *my_strchr(const char *s, int c)
{
if(s == NULL)
{
return NULL;
}
while(*s != '\0')
{
if(*s == (char)c )
{
return (char *)s;
}
s++;
}
return NULL;
}
上面这个函数就好比第三方的库中函数你需要使用它就要链接它;
#include <stdio.h>
#include<string.h>
#include"function.h"
int main(int argc, char *argv[])
{
const char str[]= "hello,this is a library";
char ch = 'a';
char *ret;
ret = my_strchr(str,ch);
printf ("%c后面的字符串是:%s\n",ch,ret);
return 0;
}
#ifndef __FUNCTION__H
#define __FUNCTION__H
char *my_strchr(const char *s, int c);
#endif
现在我们有三个文件
maqianjun@ubuntu-18:~/library$ ls
function.c function.h test.c
然后,来制作静态库;
1.gcc -c *.c ( gcc -c funtion.c test.c )
//将所有的c文件编译生成 .o 文件;
2.ar -rcs libfunction.a *.o
//将所有的 .o 文件打包生成一个静态库 .a 文件
maqianjun@ubuntu-18:~/library$ gcc -c *.c
maqianjun@ubuntu-18:~/library$ ls
function.c function.h function.o test.c test.o
maqianjun@ubuntu-18:~/library$ ar -rcs libfunction.a *.o
maqianjun@ubuntu-18:~/library$ ls
function.c function.h function.o libfunction.a test.c test.o
做到这里我们已经制作好一个静态库了,这样你就可以把这个静态库发给别人了,别人可以使用了。然后我们要进行编译运行了;
maqianjun@ubuntu-18:~/library$ gcc test. -lfunction
gcc: error: test.: No such file or directory
这是为什么?
报错说没有找到文件或文件夹;
其实,系统在就收到 -lfunction 命令后将会到存放库的文件夹中寻找这个静态库,我的Linux系统是在 /usr/lib 或 /usr/local/lib 的这个文件夹下,很显然,这个文件夹下并没有我们制作的 function.h 所以,一种方法是将我们自己制作好的静态库放到这个文件夹下,编译时就可以找到这个库了,但是,想要将我们制作的库放到 /usr/lib 下,是需要 root 权限的,所以,我们通常会加上 **-L.**这个选项,-L 用来指定除了 /lib/usr 文件以外还要到其他路径寻找, . 表示当前路径,命令如下:
gcc test.c -lfunction -L.
maqianjun@ubuntu-18:~/library$ gcc test.c -lfunction -L.
maqianjun@ubuntu-18:~/library$ ./a.out
a后面的字符串是:a library
2, 动态库
动态库的命名方式与静态库类似,前缀相同为“lib”,linux下后缀名为“.so(shared object)”即libxxx.so;而windows下后缀名为“.dll(dynamic link library)”即libxxx.dll;
链接时间:动态库在编译的时候并没有被编译进目标代码,而是当你的程序执行到相关函数时才调用该函数库里的相应函数。这样做缺点是因为函数库并没有整合进程序,所以程序的运行环境必须提供相应的库。优点是动态库的改变并不影响你的程序,所以动态函数库升级比较方便。
它们两个还有很明显的不同点:
当同一个程序分别使用静态库,动态库两种方式生成两个可执行文件时,静态链接所生成的文件所占用的内存要远远大于动态链接所生成的文件。
这是因为静态链接是在编译时将所有的函数都编译进了程序,而动态链接是在程序运行时由操作系统帮忙把动态库调入到内存空间中使用。另外如果动态库和静态库同时存在时,链接器优先使用动态库。
2.1制作动态库
首先,要通过命令来生成;
gcc -fPIC -shared change.c -o libchange.so
这样就生成好了动态库。
maqianjun@ubuntu-18:~/library/dynamtic$ ls
function.c function.h libfunction.so test.c
然后我们开始编译运行;
maqianjun@ubuntu-18:~/library/dynamtic$ gcc test.c -lfunction -L.
maqianjun@ubuntu-18:~/library/dynamtic$ ls
a.out function.c function.h libfunction.so test.c
maqianjun@ubuntu-18:~/library/dynamtic$ ./a.out
./a.out: error while loading shared libraries: libfunction.so: cannot open shared object file: No such file or directory
为什么会报错?
其实,因为在程序选择使用动态链接到动态库时,并不会将动态库中的代码拷贝到可执行文件中,而仅仅只是告诉可执行文件需要用到那个动态库,当可执行文件运行时,回到指定目录 /usr/lib 下去找到这个动态库并加载进来然而,我们并没有把 libchange.so 文件放到指定的文件夹中,所以就出现了“没有这样的文件或目录”错误;
一种方法,和前面一样,就是将这个动态库考到 /usr/lib 下,但是并不是所有人都有 root 权限;
第二种方法:在linux系统中,存在有个环境变量(在不同目录中都是不一样的)LD_LIBRARY_PATH,系统在寻找动态库时,会到两个地方去找,一个是我们提到的 /usr/lib 中,而另一个,便是该环境变量指定的路径;通过修改这个环境变量为当前的路径,就可以让系统除了到 /usr/lib 下寻找动态库,还可以到当前路径下寻找;
还需要注意一点,在更改时需要使用绝对路径,不能使用相对路径,pwd命令可以查看当前的绝对路径;
可以先使用pwd查看绝对路径后在赋值,也可以使用命令置换符 · ;
export LD_LIBRARY_PATH=`pwd`
maqianjun@ubuntu-18:~/library/dynamtic$ export LD_LIBRARY_PATH=`pwd`
maqianjun@ubuntu-18:~/library/dynamtic$ ./a.out
a后面的字符串是:a dynamtic library
Makefile 基本使用
先来了解一下Makefile的基本概念:
makefile其实就是一个文档,里面定义了一系列的规则指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,它记录了原始码如何编译的详细信息! makefile一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
make工具简介
make工具是一个根据makefile文件内容,针对目标(可执行文件)进行依赖性检测(要生成该可执行文件之前要有哪些中间文件)并执行相关动作(编译等)的工具 。而这个makefile文件类似一个脚本,其中内容包含make所要进行的处理动作以及依赖关系。
另外make的一个好处就是当你对某个源文件进行了修改,你再次执行 make 命令,它将只编译与该源文件相关的目标文件而不是整个代码工程,因此,为编译完最终的目标(可执行文件)节省了大量的时间提高工作连贯性。比如一个工程下面有A.c 、B.c、 B.c三个文件,在make生成可执行目标文件之后,改动了A.c,则再次make时,只会执行有关A.c处理动作,其它的不处理。
学习make工具,需要明白三个概念:目标、依赖、处理动作。
下面通过一个简单的文件来学习使用一下Makefile;
#include<stdio.h>
#include"hello.h"
int main(int argc, char *argv[])
{
int a = 5;
int b = 6;
int c;
c = add(&a, &b);
printf("%d\n",c);
printf ("hello world\n");
return 0;
}
#include<stdio.h>
int add(int *a, int *b)
{
int c;
c = ((*a)+(*b));
return c;
}
int add(int *a, int *b);
然后我们开始写个makefile;
首先makefile 的基本格式是:目标;依赖;命令;
target ... : prerequisites ...
command
...
...
target也就是一个目标文件,prerequisites就是要生成那个target所需要的文件或是目标。command也就是make需要执行的命令。(任意的Shell命令)这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
APP_NAME = hello
cc=gcc
all:
${cc} add.c hello.c -o ${APP_NAME}
clean:
rm -rf ${APP_NAME}
敲make
可以看到
maqianjun@ubuntu-18:~/test$ ls
add.c hello hello.c hello.h hello.h.gch makefile memory.c
maqianjun@ubuntu-18:~/test$ ./hello
11
hello world
这就是makefile简单的使用。
以上内容就是博主自己对这些知识的汇总。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)