链接(extern、static关键词\头文件\静态库\共享库)

原文链接:http://www.orlion.ga/781/

一致、 多目标文件的链接

   假设有两只文本:stack.c:

/* stack.c */
char stack[512];
int top = -1;
void push(char c)
{
        stack[++top] = c;
}
char pop(void)
{
        return stack[top--];
}
int is_empty(void)
{
        return top == -1;
}

    上边这个文件落实了仓库。main.c:

/* main.c */
#include <stdio.h>

int main(void)
{
        push('a');
        push('b');
        push('c');
        
        while(!is_empty())
                putchar(pop());
        putchar('\n');
        return 0;
}

    这个文件是采取了仓库,编译:gcc main.c stack.c -o main,也可以分步编译:

gcc -c main.c
gcc -c stack.c
gcc main.o stack.o -o main

    用nm命令查看目标文件之符表,会发现main.o中生不定义之号push、pop、is_empty、putchar:

  图片 1

前方三只标志在stack.o中落实了,链接生成可执行文件main时得举行标记解析,而putchar是libc的库函数,在可执行文件main中

依旧是无定义的,要当程序运行时举行动态链接。

  {

nm [option(s)] [file(s)]

有用的options:

  • -A 在每个符号信息的眼前打印所在对象文件名称;
  • -C 输出demangle过了之号子名称;
  • -D 打印动态符号;
  • -l 使用对象文件中之调试信息打印出所在源文件及行号;
  • -n 以地方/符号值来排序;
  • -u 打印出那些无定义之符号;

常见的标记类型:

  • A
    该符号的价当之后底链接中将不再改变;
  • B
    该符号放在BSS段中,通常是那些休初始化的全局变量;
  • D
    该符号放在普通的数段遭遇,通常是那些早已初始化的全局变量;
  • T
    该符号放在代码段遭遇,通常是那些全局非静态函数;
  • U 该符号未定义过,需要从其他对象文件中链接进来;
  • W
    未明显指定的弱链接符号;同链接的外对象文件中起它们的概念就是因此上,否则就因此一个系特别指定的默认值。

只顾几碰:

  • -C
    总是适用于c++编译出的目标文件。还记c++中生再次载么?为了区别重载函数,c++编译器会将函数返回值/参数等信息附加到函数名称被去形成一个mangle过之号,那用这个选项列出符号的时节,做一个逆操作,输出那些老的、我们而知道的标记名称。
  • 应用 -l
    时,必须保证你的目标文件中隐含符号调式信息,这一般要求而于编译的时段指定一个
    -g
    选项,见 Linux:Gcc。
  • 运nm前,最好先用Linux:File查看对象文件所属处理器架构,然后还用相应交叉版本的nm工具。

  }

    我们透过readelf -a
main命令可以见见,main的.bss段合并了main.o和stack.o的.bss段,其中含了变量a和stack,main的.data段为统一了main.o和stack.o的.data段,其中含有了变量b和top,main的.text段合并了main.o和stack.o的.text段,包含了各个函数的定义。如下图所示:

    图片 2图片 3

    如果当编译时拿stack.o放到main.o前面,即:gcc stack.o main.o -o
main,可执行文件main的每个段落受到源main.o的变量或函数都排到背后去矣。

   实际上链接的进程是由于一个链接脚本(Linker
Script)控制的,链接脚本决定了受每个段分配什么地方,如何对联合,哪个段于前,哪个段于晚,哪些段合并及同一个Segment,另外链接脚本还要插入一些记到终极生成的公文被,例如
__bss_start 、 _edata 、 _end 等。如果就此 ld 做链接时未尝因此 -T
选项指定链接脚本,则采取 ld 的默认链接脚本,默认链接脚本可以为此 ld
–verbose
命令查看。【结果很丰富直接接书上的截图,实际出口内容及生图并无平等】:

  图片 4

  图片 5

  图片 6

  ENTRY(_start)说明_start是整套程序的入口点,因此_start是入口点并无是确定,是好改用其他函数做入口点的。

  PROVIDE(__executable_start = 0x8048000); . = 0x9048000 +
SIZEOF_HEADERS;是Text
Segment的发端地址,这个Segment包含后面列有底那些段,
.plt、.text、.rodata等等。每个段落的叙述格式都是”段名:{组成}”,例如.plt :
{ *(.plt))
},左边表示最终生成的文件之.plt段,右边表示有目标文件的.plt段,意思是最终生成的公文之.plt段由各目标文件的.plt段组成。

  . = ALIGN (CONSTANT (MAXPAGESIZE)) – ((CONSTANT (MAXPAGESIZE) – .) &
(CONSTANT (MAXPAGESIZE) – 1)); . = DATA_SEGMENT_ALIGN (CONSTANT
(MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));是Data
Segment的发端地址,要举行相同层层之对齐操作,这个Segment包含后面列有的那些段,.got、.data、.bss等等。

次、定义及声明

    

    1、extern和static关键字

    在点编译stack.c与main.c文件时其实产生同样触及多少题目,(-Wall选项好看出)由于编译器在处理函数调用代码时不曾检索打函数原型,只好根据函数调用代码做隐式声明,把三只函数声明也

int push(char);
int pop(void);
int is_empty(void);

    现在到家一中的代码:

/* main.c */
#include <stdio.h>
extern void push(char);
extern char pop(void);
extern int is_empty(void);
int main(void)
{
        push('a');
        push('b');
        push('c');
        
        while(!is_empty())
                putchar(pop());
        putchar('\n');
        return 0;
 }

    这样编译器就不见面报警报了,extern关键字表示此标识符具有External
Linkage【关于External Linkage参看《[汇编与C语言关系]3.
变量的储存布局》】(“extern关键字表示是标识符具有External
Linkage”其实是无确切的,准确地说应该是Previous Linkage。Previous
Linkage的定义是:这次声明的标识符具有什么样的Linkage取决于前一模一样不善声明,这眼前无异糟糕声明具有同样的标识符名,而且得是文本作用域的宣示,如果在程序文件被搜索不顶眼前一模一样不成声明(这次声明是首先次于声明),那么这个标识符具有External
Linkage),push这个标识符具有External
Linkage指的凡:如果把main.c和stack.c链接以同步,如果push在main.c和stack.c中还产生扬言(在stack.c中既是声称同时是概念)那么这些声明指的是同一个函数,链接后是与一个GLOBAL符号,代表及一个地点。

    函数声明中的extern也足以概括不写,不写extern仍然表示这函数名具有External
Linkage。C语言不同意嵌套定义函数,但只要一味是宣称如无定义,这种声明时许写于函数体里面的,这样声明的标识符具有块作用域,例如地方的main.c可以描绘成:

/* main.c */
#include <stdio.h>
int main(void)
{
        void push(char);
        char pop(void);
        int is_empty(void);
        push('a');
        push('b');
        push('c');
        
        while(!is_empty())
                putchar(pop());
        putchar('\n');
        return 0;
}

    如果就此static关键字修饰一个函数声明,则意味着该标识符具有Internal
Linkage,例如有瞬间少单程序文件:

/* foo.c */
static void foo(void) {}

    

/* main.c */
void foo(void);
int main(void) { foo(); return 0; }

    编译链接以并会错:

 

    虽然于foo.c中定义了函数foo,但此函数只拥有Internal
Linkage,只有当foo.c中数声明才表示和一个函数,而于main.c中扬言就非代表她了。如果管foo.c编译成靶子文件,函数名foo在里头凡一个LOCAL的标志,不参与链接过程,所以于链接时,main.c中之所以到一个External
Linkage的foo函数,链接器却招来不至它们的定义在啊,无法确定她的地方,也不怕无法开标记解析,只能报错。凡是受数声明的变量或函数,必须发还只有发一个宣称是概念,如果来多独概念,或者一个定义都未曾,链接器就无法完成链接。

    对于变量如果当main.c中之main()函数中用extern int
top;来声称top变量可以拜到stack.c中之top变量。(变量的extern声明不可知简单,而且不可知定义如:extern
int top =
-1;这样写是非正常的)。如果无思量给main.c访问到top变量可以把top用static声明,在stack.c中static
int top = -1;

 

    2、头文件

    (一)中的stack.c模块封装了top和stack两个变量导出了push\pop\is_empty三个函数接口,但是利用这个模块的每个程序文件都要描写三只函数声明,假设以起一个文书也采取了这模块,那么快要写三单函数声明。这时候可以写一个头文件stack.h来简化代码:

/* stack.h */
#ifndef STACK_H
#define STACK_H
extern void push(char);
extern char pop(void);
extern int is_empty(void);
#endif

    在main.c中单独需要包含就得了:

/* main.c */
#include <stdio.h>
#include "stack.h"
int main(void)
{
        push('a');
        push('b');
        push('c');
        
        while(!is_empty())
                putchar(pop());
        putchar('\n');
        return 0;
}

    对于因此尖括号包含的条文件(#include
<stdio.h>)gcc会首先查找-I选项指定的目,然后查系统的头文件目录(通常是/usr/include),而对引号包含的峰文件,gcc首先查找包含头文件之.c文件所在的目录,然后搜索-I选项指定的目,然后找系统的峰文件目录。在#include预处理着得以用相对路劲如:#include
“header/stack.h”

    #idndef
STACK_H和#endif是如果STACK_H这个庞然大物没有概念了,那么由#ifndef到#endif之间的代码就含在先行处理的输出结果吃,否则就无异截代码就非出新于先行处理的输出结果中。stack.h这个腔文件之始末全被#ifndef和#endif包含起来了,如果当蕴藏这个腔文件时STACK_H这个宏已经定义了了,则相当给之文件中什么都未曾。

    还有一个题材,既然要#include头文件,那自己莫设直接以main.c中#include
“stack.c”得矣。这样将stack.c和main.c合并为与一个主次文件。虽然如此也能够编译通过,但是在一个面比较生之档次被无可知如此做,假如又发出一个foo.c也使运用stack.c这个模块怎么处置呢?如果当foo.c里面也#include
“stack.c”,就相当给push、pop、is_empty这三独函数在main.c和foo.c中都发定义,那么main.c和foo.c就不能够链接以同步了。如果应用包含头文件的方式,那么就三个函数只以stack.c中定义了一样潮,最后只是

盖将main.c、stack.c、foo.c链接以一起。

    
三、静态库

    

    有时候要将同组代码编译成一个仓房,这个库房在博种遭到都使使用,例如libc就是这样一个储藏室,我们于不同之程序中还见面为此到libc中的库函数(如printf)也会就此到libc中的变量(比如environ)

    把(一)中的stack.c拆成四个文本:

/* stack.c */
char stack[512];
int top = -1;

    

/* push.c */
extern char stack[512];
extern int top;
void push(char c)
{
        stack[++top] = c;
}

    

/* pop.c */
extern char stack[512];
extern int top;
char pop(void)
{
        return stack[top--];
}

    

/* is_empty.c */
extern int top;
int is_empty(void)
{
        return top == -1;
}

/* stack.h */
#ifndef STACK_H
#define STACK_H
extern void push(char);
extern char pop(void);
extern int is_empty(void);
#endif

/* main.c */
#include <stdio.h>
#include "stack.h"
int main(void)
{
        push('a');
        return 0;
}

    目录结构也:

    |–main.c

    |–stack

       |–is_empty.c

       |–pop.c

       |–push.c

       |–stack.c

       |–stack.h

    把stack.c、push.c、pop.c、is_empty.c编译成靶子文件:

gcc -c stack/stack.c stack/push.c stack/pop.c stack/is_empty.c

    然后由包改成一个静态库libstack.a:

ar rs libstack.a stack.o push.o pop.o is_empty.o

    库文件还是因lib开头的,静态库以.a作为后缀,表示Archive。。ar命令类似于tar命令,起一个卷入的打算,但是将目标文件包成静态库只能用ar命令而休可知因此tar命令。选项r表示以后面的文书列表添加到文件包,如果文件管不有就创造它,如果文件包吃已经发生同名文件就替换成新的。s是专用于生成静态库的,表示也静态库创建索引,这个目录被链接器使用。ranlib命令也堪吧静态库创建索引,以上命令等价于:

ar r libstack.a stack.o push.o pop.o is_empty.o
ranlib libstack.a

    然后把libstack.a和main.c编译连接于共:

gcc main.c -L. -l stack -I stack -o main

    -L选项报告编译器去哪找得的库文件,-L.表示在当前目录找。-lstack告诉编译器要链接libstack库,-I选项告诉编译器去哪里找头文件。注意,即使库文件就以当前目录,编译器默认为不见面失去找寻的,所以-L.选项无能够少。编译器默认会找的目可以就此-print-search-dirs选项查看。

    编译器会首先找来无发共享库libstack.so,如果发生就是链接它,如果没就摸索来没有产生静态库libstack.a,如果生就是链接它。所以编译器是先行考虑共享库的,如果期待编译器只链接静态库,可以指定-static选项。

    在链接libc共享库时只是指定了动态链接器和欠次所待的库文件,并无真正做链接,可执行文件main中调用的libc库函数仍然是未定义符号,要在运行时开动态链接。而当链接静态库时,链接器会管静态库中之对象文件取下和可执行文件真正链接以一块。

    

四、共享库

    

    1、编译、链接、运行

    组成共享库的对象文件及一般的对象文件有所不同,在编译时一旦加-fPIC选项:

// 原文是:gcc -c -fPIC stack/stack.c stack/push.c stack/pop.c stack/is_empty.c, 但是这样写不能生成.so文件
gcc -o libstack.so -O2 -fPIC -shared stack/stack.c stack/push.c stack/pop.c stack/is_empty.c stack/stack.h

    -f后止跟编译选项,PIC是里面同样种植,表示生成位置无关代码。一般的目标文件称Relocatable,在链接时得管对象文件被各段的地方做更一贯,重一贯时需要修改命令。

    现在拿main.c和共享库编译链接以同步,然后运行:

$ gcc main.c -g -L. -lstack -Istack -o main
$ ./main 
./main: error while loading shared libraries: libstack.so: cannot 
open shared object file: No such file or directory

    

    (TODO:这里本机实验时有问题)

    编译的时没有问题,由于指定了-L.选项,编译器可以以当前目录下找到libstack.so,而运行时却说找不顶libstack.so。

    可以为此ldd命令查看可执行文件依赖哪些共享库:

$ ldd main
        linux-gate.so.1 =>  (0xb7f5c000)
        libstack.so => not found
        libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7dcf000)
        /lib/ld-linux.so.2 (0xb7f42000)

    ldd模拟运行一整整main,在运作过程遭到做动态链接,从而得知这个可执行文件依赖让如何共享库,每个共享库都于啊路径下,加载到过程地址空间的呀地方。/lib/ld-linux.so.2是动态链接器,它的门路是在编译链接时指定的,gcc在召开链接时用-dynamic-linker指定动态链接器的路线,它吧像任何共享库一样加载到过程的地方空间受到。libc.so.6的路径/lib/tls/i686/cmov/libc.so.6是由动态链接器ld-linux.so.2在开动态链接时找到之,而libstack.so的路没有找到。linux-gate.so.1这个共享库其实并无存叫文件系统中,它是出于基础虚拟出来的共享库,所以它们并未对应的路子,它肩负处理体系调用。总之,共享库的搜索路径由动态链接器决定,从ld.so(8)Man
Page可以查到共享库路径的追寻顺序:

    1. 首先以环境变量LD_LIBRARY_PATH所记录的门路中找找。

    2.
然后于缓存文件/etc/ld.so.cache中寻找。这个缓存文件由ldconfig命令读取配置文

    件/etc/ld.so.conf之后转,稍后详细解释。

    3.
如上述手续都摸不交,则到默认的体系路径中找,先是/usr/lib然后凡/lib。

    

    解决:可以将libstack.so所在的目录的绝对路径添加到/etc/ld.so.conf中,然后运行ldconfig

 

    2、共享库的命名惯例

    按照同享库的命名惯例,每个共享库有三独文件称:real
name、soname和linker name。真正的库文件(而休是记链接)的名字是real
name,包含圆的共享库版本号。例如地方的libcap.so.1.10、libc-2.8.90.so等。soname是一个标志链接的名字,只包含一块享库的主版本号,主版本号一致即可保证库函数的接口一致,因此应用程序的.dynamic段只记录并享库的soname,只要soname一致,这个共享库就得用。例如地方的libcap.so.1和libcap.so.2是有限独主版本号不同的libcap,有些应用程序

倚让libcap.so.1,有些应用程序依赖让libcap.so.2,但对于依赖libcap.so.1的应用程序来说,真正的库文件不管是libcap.so.1.10还是libcap.so.1.11都足以为此,所以采用共享库可以非常方便地升级库文件要休需要再编译应用程序,这是静态库所没有底助益。注意libc的本编号有一些特,libc-2.8.90.so之主版本号是6要是无是2要么2.8。

    linker name仅在编译链接时使用,gcc的-L选项应该指定linker
name所当的目。有的linker

name是仓库文件之一个记链接,有的linker name是平段落链接脚本