本文旨在让大家真正明白C程序如何运行、库文件的种类区别、如何生成、如何使用等!

一、简介:

gcc 最初是 "GNU C Compiler" 的简称,只是当作一个 C 语言的编译器,现在已经变成了 "GNU Compiler Collection",可以编译多种语言。

常用的命令参数选项:

二、编译的四个阶段:

在使用 gcc 编译程序时,编译过程可以被细分为 4 个阶段:

◆ 预处理(Pre-Processing)

◆ 编译(Compiling)

◆ 汇编(Assembling)

◆ 链接(Linking)

三、举例

下面以一份简单的 C 代码来说明:

#include <stdio.h>
int main()
{printf("Hello,World!\n");
}

1)首先,预处理生成test.i文件

gcc -E -o test.i test.c

从上面我们可以知道,预处理其实就是 展开头文件、宏替换、去掉注释、条件编译

2)编译,解析词义、检查语法、解析语义。生成汇编语言。

gcc -S -o test.s test.i

从上面我们可以知道,编译其实就是检查语法、解析语义、生成汇编代码。

3)汇编

gcc -c -o test.o test.s

从上面我们可以知道,汇编其实就是生成二进制0-1代码,计算机所能识别的代码。

4)链接(这一步可以链接静态库和动态库,后面会讲解到),生成计算机可执行文件。

gcc  -o test test.o


四、那么什么是库文件?

在说明Linux的.a、.so和.o文件关系之前,我们先来看看windows下obj,lib,dll,exe的关系!

windows下obj,lib,dll,exe的关系

lib是和dll对应的。lib是静态链接库的库文件,dll是动态链接库的库文件。 
    所谓静态就是link的时候把里面需要的东西抽取出来安排到你的exe文件中以后运行你的exe的时候不再需要lib
    所谓动态就是exe运行的时候依赖于dll里面提供的功能,没有这个dll,你的exe无法运行。 
    
    lib,dll,exe都算是最终的目标文件,是最终产物。而c/c++属于源代码。源代码和最终目标文件中过渡的就是中间代码obj,实际上之所以需要中间代码,是你不可能一次得到目标文件。比如说一个exe需要很多的cpp文件生成。而编译器一次只能编译一个cpp文件。这样编译器编译好一个cpp以后会将其编译成obj,当所有必须要的cpp都编译成obj以后,再统一link成所需要的exe,应该说缺少任意一个obj都会导致exe的链接失败。(win下面的obj相当于linux里的.o文件)
    
    1.obj里存的是编译后的代码跟数据,并且有名称,所以在连接时有时会出现未解决的外部符号的问题。当连成exe后便不存在名称的概念了,只有地址。lib就是一堆obj的组合
    2.理论上可以连接obj文件来引用其他工程(可以认为一个obj文件等价于编译生成它的cpp文件,可以引用obj来替换cpp,也可以添加cpp来替换obj ),但实际中通常用lib来实现工程间相互引用。
    3.编译器会默认链接一些常用的库,其它的需要你自己指定
    
lib和DLL的区别

(1)lib是编译时需要的,dll是运行时需要的。如果要完成源代码的编译,有lib就够了。如果也使动态连接的程序运行起来,有dll就够了。在开发和调试阶段,当然最好都有。

(2) 一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。 静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。

(3)在动态库的情况下,有两个文件,一个是引入库(.LIB)文件(实际上也算是一个静态库,只是在链接时只能把函数在DLL的入口链接到exe中,而不像真正静态链接库那样将函数体真正链接到exe中 ,通过lib进行的动态链接实际上也使用了静态链接来实现 ),一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源(共享属性)。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

DLL内的函数分为两种: 
    (1)DLL导出函数,可供应用程序调用;
    (2)DLL内部函数,只能在DLL程序使用,应用程序无法调用它们

创建静态链接库和创建动态链接库

VC6中创建[Win32 Dynamic-Link Library]工程便可以创建出一个空的DLL工程.

VC6中创建[Win32 Static Library]工程便可以创建出一个空的LIB工程(静态链接库工程,仅生成一个lib文件).

添加lib文件的常用办法有二个: 
    1、把*.lib放在VC的Lib目录中 
    2、修改project setting的Link->Input中的Addtional library path,加入你的目录dll:是可实际运行的二进制代码,有定位代码的!

3、也可以在object/library中直接写上lib文件路径.(这里实际上是可以写上任意obj文件或者lib文件的).

以上说完了windows下的库文件,现在来说说linux下的库文件:

linux     .o   .a    .so

.o,是目标文件,相当于windows中的.obj文件

  .so 为共享库,是shared object,用于动态连接的,相当于windows下的dll

  .a为静态库,是好多个.o合在一起,用于静态连接

静态函数库

特点:实际上是简单的普通目标文件的集合,在程序执行前就加入到目标程序中。
优点:可以用以前某些程序兼容;描述简单;允许程序员把程序link起来而不用重新编译代码,节省了重新编译代码的时间(该优势目前已不明显);开发者可以对源代码保密;理论上使用ELF格式的静态库函数生成的代码可以比使用共享或动态函数库的程序运行速度快(大概1%-5%)
生成:使用ar程序(archiver的缩写)。

例子:ar rcs my_lib.a f1.o f2.o      把目标代码f1.o和f2.o加入到my_lib.a这个函数库文件中(如果my_lib.a不存在则创建)
使用:用gcc生成可执行代码时,使用-l参数指定要加入的库函数。也可以用ld命令的-l和-L参数。

共享函数库

共享函数库在可执行程序启动的时候加载,所有程序重新运行时都可自动加载共享函数库中的函数。.so文件感觉很复杂,光是命名规则就已经看得我很晕了~整理一下,共享库需要:soname、real name,另外编译的时候名字也有说法。依次解释下:
soname:必须的格式:lib+函数库名+.so+版本号信息(但是记住,非常底层的C库函数都不是以lib开头命名的)。

例子:/usr/lib/libreadline.so.3
real name:顾名思义是真正的名字啦,有主版本号和发行版本号。但是没找到实例……
编译器编译的时候需要的函数库的名字就是不包含版本号信息的soname,例如上面的例子把最后的.3去掉就可以了。
位置:共享函数库文件必须放在特定目录,对于开放源码来说,GNU标准建议所有的函数库文件都放在/usr/local/lib目录下,而且建议命令、可执行程序都放在/usr/local/bin目录下。

不过这个只是习惯啦,可以改变,具体的位置信息可以看/etc/ld.so.conf里面的配置信息。当然,也可以修改这个文件,加入自己的一些特殊的路径要求。

创建:在网上找到了gcc方式和easyeclipse环境下两种创建方式。
gcc方式:
    首先创建object文件,这个文件将加入通过gcc –fPIC 参数命令加入到共享函数库里面,标准格式:gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list(说实话这个标准格式看起来好复杂,我找了个实例,但是好像和那个标准格式稍有不同:gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so)

在easyeclipse环境下生成.so文件:
        1.选择新建工程,建立一个c++工程
        2.在工程类型选项里选择 Shared Library,然后填入工程名字PXXX点击完成即可。
        3.编写程序,然后编译就会在debug或者release里生成一个libPXXX.so文件,如果不要lib的起头标记点击project菜单的Properties选项,然后在弹出的界面的右边点击Build artifact页面,将Output prefix选项的内容清空即可。
        4.如果是C++程序,注意在接口函数的前面加上extern "C"标记,在头文件加上如下标记:
#ifdef   __cplusplus  
#extern   "C"{  
#endif  
   
头文件主体  
   
#ifdef   __cplusplus  
}  
#endif  
     如果不加以上标记,经过编译后,so里的函数名并非你编写程序时设定的函数名,在开发环境左侧的工程文件列表中点开debug项里的PXXX.o可以看到so文件里的函数名都是在你设定的函数名后面加了一个__Fi标记,比如你用的设定的函数名称是Func(), 而so里的函数名则为Func__Fi()或者其他的名称。
安装:拷贝共享库文件到指定的标准的目录,然后运行ldconfig。如果没有权限这样做,那么就只好通过修改环境变量来实现这些函数库的使用了。方法不再说了,很复杂。
查看:可以通过运行ldd来看某个程序使用的共享函数库。例如ldd /bin/ls。查看.so文件使用nm命令,如nm libXXX.so。(注意,nm对于静态的函数库和共享的函数库都起作用)
关于覆盖:如果想用自己的函数覆盖某个库中的一些函数,同时保留该库中其他的函数的话,可以在/etc/ld.so.preload中加入要替换的库(.o结尾的文件),这些preloading的库函数将有优先加载的权利。
关于更新:每次新增加动态加载的函数库、删除某个函数库或者修改某个函数库的路径时,都要重新运行ldconfig来更新缓存文件/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表

(在Linux下,共享库的加载是由/lib/ld.so完成的,ld.so加载共享库时,会从ld.so.cache查找)

五、库文件例子

1)静态库。

我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。本文主要通过举例来说明在Linux中如何创建静态库和动态库,以及使用它们。

第一步:在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o文件。

程序1: hello.h

#ifndef HELLO_H
#define HELLO_H

void hello(const char *name);

#endif //HELLO_H

程序2: hello.c

#include <stdio.h>

void hello(const char *name)
{
printf("Hello %s!\n", name);
}

程序3: main.c

#include "hello.h"

int main()
{
hello("everyone");
return 0;
}

第2步:将hello.c编译成.o文件;

第3步:由.o文件创建静态库;

静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是libmyhello.a。在创建和使用静态库时,需要注意这点。

创建静态库用ar命令。在系统提示符下键入以下命令将创建静态库文件libmyhello.a。

ar -cr libmyhello.a hello.o

第4步:在程序中使用静态库;

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。

在程序3:main.c中,我们包含了静态库的头文件hello.h,然后在主程序main中直接调用公用函数hello。下面先生成目标程序main,然后运行main程序看看结果如何。

gcc -o main main.c libmyhello.a

还可以用以下命令,等价的。

法一 # gcc -o main main.c -L. –lmyhello,或gcc  main.c -L. –lmyhello -o main自定义的库时,main.c还可放在-L.和 –lmyhello之间,但是不能放在它俩之后,否则会提示myhello没定义,但是是系统的库时,如g++ -o main(-L/usr/lib) -lpthread main.cpp就不出错。

法二 #gcc main.c libmyhello.a -o main或gcc  -o main main.c libmyhello.a

法三:先生成main.o:gcc -c main.c ,再生成可执行文件:gcc -o main main.o libmyhello.a或gccmain.o libmyhello.a -o main ,动态库连接时也可以这样做。

我们删除静态库文件试试公用函数hello是否真的连接到目标文件 main中了。

会发现还是可以执行,可以见得hello已经连接到目标文件main中了。

2) 动态库。

我们继续看看如何在Linux中创建动态库。我们还是从.o文件开始。

第1步:动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。在系统提示符下键入以下命令得到动态库文件libmyhello.so。

发现报错了:解决方案   (要在编译.o文件时就要带上 -fPIC)

第2步:在程序中使用动态库;

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。我们先运行gcc命令生成目标文件,再运行它看看结果。

发现报错了,原因是在mian运行的时候,在调用hello函数的时候,linux会默认去/usr/lib下去找.so文件。因此需要将libmyhello.so复制到/usr/lib目录下即可,于是:

至此,动态库生成成功,链接运行完成。


我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样,那当静态库和动态库同名时,gcc命令会使用哪个库文件呢?抱着对问题必究到底的心情,来试试看。

先删除除.c和.h外的所有文件,恢复成我们刚刚编辑完举例程序状态。

在来创建静态库文件libmyhello.a和动态库文件libmyhello.so。

在生成动态库时,需要使用-fPIC,这样才能生成位置无关的代码,达到代码段和数据段共享的目的

# gcc -c -fpic hello.c  //编译hello.c时也需要加上-fpic选项,否则rodata' can not be used when making a shared object; recompile with -fPIC

# ar -cr libmyhello.a hello.o (或-cvr )

# gcc -shared -fPIC -o libmyhello.so hello.o

# ls

hello.c hello.h hello.o libmyhello.a libmyhello.so main.c

通过上述最后一条ls命令,可以发现静态库文件libmyhello.a和动态库文件libmyhello.so都已经生成,并都在当前目录中。然后,我们运行gcc命令来使用函数库myhello生成目标文件main,并运行程序 main。

# gcc -o main main.c -L. –lmyhello (动态库和静态库同时存在时,优先使用动态库, 当然,直接#gcc main.c libmyhello.a -o main的话,就是指定为静态库了)

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory

从程序hello运行的结果中很容易知道,当静态库和动态库同名时,gcc命令将优先使用动态库,默认去连/usr/lib和/lib等目录中的动态库,将文件libmyhello.so复制到目录/usr/lib中即可。

Note:
编译参数解析
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件

-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code)。那么在产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

如果不加fPIC,则编译出来的代码在加载时需要根据加载到的位置进行重定位(因为它里面的代码并不是位置无关代码),如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)。(使用的是相对路径)

不用此选项的话编译后的代码是位置相关的,所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

-L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在源文件后面就可以了。比如:gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop
其中-L/usr/lib指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如前面的“法二 #gccmain.c libmyhello.a-o hello”)

-lmyhello 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so或.a来确定库的名称libmyhello.so或libmyhello.a。
LD_LIBRARY_PATH这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。

调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

静态库链接时搜索路径顺序:

1. ld(GNU linker)会去找GCC命令中的参数-L

编译过程是分为四个阶段:预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(link)  【链接】
2. 再找gcc的环境变量LIBRARY_PATH
3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的

动态链接时、执行时搜索路径顺序:

1. 编译目标代码时指定的动态库搜索路径
2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径
4. 默认的动态库搜索路径/lib
5. 默认的动态库搜索路径/usr/lib

有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径

另:

从上述可知,如何找到生成的动态库有3种方式:

(1)把库拷贝到/usr/lib和/lib目录下

(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。

例如动态库libhello.so在/home/example/lib目录下:

export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/home/example/lib

(3) 修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾(直接写在文件末尾,不要在路径前加include),并执行ldconfig刷新(ldconfig 命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.)。这样,加入的目录下的所有库文件都可见。

附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到:

g++ -o main main.cpp -L/usr/lib libpthread.a

必须这样g++ -o main main.cpp -L/usr/lib -lpthread才正确 。

自定义的库考到/usr/lib 下时,

g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a会出错,但是这样g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass就正确了。

参考文章:

1)Linux---C++编译后各种文件格式

2)c语言编译过程详解,预处理,编译,汇编,链接(干货满满)

3)编译动态库时遇到relocation R_X86_64_32 against `a local symbol'的错误

GCC : 什么是编译?什么是静态库?什么是动态库?怎么生成?优先级?相关推荐

  1. [转]Linux下g++编译与使用静态库(.a)和动态库(.os) (+修正与解释)

    在windows环境下,我们通常在IDE如VS的工程中开发C++项目,对于生成和使用静态库(*.lib)与动态库(*.dll)可能都已经比较熟悉,但是,在linux环境下,则是另一套模式,对应的静态库 ...

  2. gcc生成静态库.a和动态库.a以及Ubuntu18.04下opencv3.4.11安装及示例

    目录 一.用gcc生成静态库和动态库 (一)hello示例使用库 1.准备过程 2.静态库使用 3.动态库的使用 4.静态库与动态库比较 (二)示例1使用库 二.opencv安装以及使用 (一)认识o ...

  3. gcc生成静态库.a和动态库.so和OpenCV3.4.11的安装及使用示例

    gcc生成静态库.a和动态库.so和OpenCV3.4.11的安装及使用示例 文章目录 gcc生成静态库.a和动态库.so和OpenCV3.4.11的安装及使用示例 一.用gcc生成静态库和动态库 ( ...

  4. linux库引入之动态库静态库(生成和使用)

    库: 库是一种可执行代码的二进制形式,可以被操作系统载入内存执行.就是将源代码转化为二进制格式的源代码,相当于进行了加密,别人可以使用库,但是看不到库中的内容. 如何使用 用户需要同时具有头文件和库. ...

  5. 在Linux中创建静态库.a和动态库.so

    转自:http://www.cnblogs.com/laojie4321/archive/2012/03/28/2421056.html 在Linux中创建静态库.a和动态库.so 我们通常把一些公用 ...

  6. 二十六篇,Linux库文件(动态库和静态库)的制作。

    一.嵌入式linux库文件概念. 1.什么是库文件? 在linux下,有非常多的库文件. 例如: /lib                        --> 存放系统自带的库文件       ...

  7. graalvm把java编译为c/c++能够使用的动态库(dll/so)

    graalvm把java编译为c/c++能够使用的动态库(dll/so) 1.安装graalvm oracle官方企业版 github的openjdk版本 1.1 下载对应系统版本,配置环境变量 本人 ...

  8. Linux静态库.a与动态库.so的生成与区别、以及.so库文件的封装与使用

    #一.前言 如果有公司需要使用你们产品的一部分功能(通过代码调用这些功能),如果不想提供源代码,那么就可以通过封装成库文件的形式提供给对方使用.本文主要介绍了生成动态库与静态库文件的过程.以及封装和使 ...

  9. 库文件、静态库(lib)与动态库(dll)的生成与使用

    静态库 程序编译一般需经预处理.编译.汇编和链接几个步骤.在应用中,有一些公共代码是需要反复使用,就把这些代码编译为"库"文件:在链接步骤中,连接器将从库文件取得所需的代码,复制到 ...

  10. C++中.lib静态库、.dll动态库的生成及调用1

    一.前言 1.动态链接库(dll)与静态链接库(lib): 动态链接库(dll)是一个可以被其他应用程序共享的程序模块,其中封装了一些可以被共享的例程和资源.在链接步骤中,连接器将从库文件取得所需的代 ...

最新文章

  1. 连SEO优化师都难以收集的搜索引擎规则,在这里!
  2. 2019寒假纪中集训总结学期总结(流水账)
  3. HR 的那些黑话大全,太扎心了!(漫画)
  4. jsp tag 自定义标签实现按钮的显示
  5. 《PowerShell V3——SQL Server 2012数据库自动化运维权威指南》——2.13 创建视图...
  6. 计算机网络--接入互联网方式
  7. mapping数据列表
  8. java 5 线程 睡眠,Java并发编程实例--5.线程睡眠
  9. Web端调用Outlook 的发信窗口
  10. 科大讯飞/百度/阿里/腾讯(BAT)智能语音识别(ASR)性能对比选择预计价格分享 - pytorch中文网...
  11. 工具之AlwaysUp
  12. 智能优化算法:供需优化算法-附代码
  13. 多元函数四则运算的一阶微分公式的存在性与性质
  14. PHP怎么计算百分比?PHP计算百分比的写法
  15. 计算机流体仿真,ANSYS FLUENT 计算流体力学软件
  16. e470c拆机图解全拆 thinkpad_ThinkPad 拆机教程,更新特别部分,很特别哟!!!
  17. 微信小程序 组件传值(二) triggerEvent 子传父
  18. Inverting Visual Representations with Convolutional Networks
  19. Sap S/4 Hana 和Sap ERP有什么不同
  20. win10系统预加载服务器,等等, 你的win10关闭了这几个服务, 会流畅许多

热门文章

  1. 【转】[Hadoop源码解读](四)MapReduce篇之Counter相关类
  2. AVI音视频封装格式学习
  3. C++程序的执行过程
  4. 用ACE的Reactor模式实现网络通讯的例子
  5. ubuntu用不了root用户:~$ su - root Password: su: Authentication failure怎么办?
  6. 删除第一个_学习数据结构--第二章:线性表(顺序存储、插入、删除)
  7. 用c语言读取固定大小的raw格式图像并统计灰度值
  8. [蓝桥] 算法提高 队列操作
  9. Swift 5 将进一步减小 iOS 应用安装包大小
  10. 015_面向对象_异常,包和Object类