问题集合 ---- linux 静态库和动态库

2023-05-16

本文转自多网址,对作者表示感谢


===================================================================

linux静态库和动态库分析

本文转自 http://www.linuxeden.com/html/develop/20100326/94297.html

 

1.什么是库

  在windows平台和linux平台下都大量存在着库。

  本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。

  由于windows和linux的本质不同,因此二者库的二进制是不兼容的。

  本文仅限于介绍linux下的库。


2.库的种类

  linux下的库有两种:静态库和共享库(动态库)。

  二者的不同点在于代码被载入的时刻不同。

  静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。

  共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。


3.库存在的意义

  库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议

  现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

  共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。


4.库文件是如何产生的在linux下

  静态库的后缀是.a,它的产生分两步

  Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表

  Step 2.ar命令将很多.o转换成.a,成文静态库

  动态库的后缀是.so,它由gcc加特定参数编译产生。

  例如:

$ gcc -fPIC -c *.c 
$ gcc -shared -Wl,-soname, libfoo.so.1 -o libfoo.so.1.0 *.

5.库文件是如何命名的,有没有什么规范

  在linux下,库文件一般放在/usr/lib /lib下,

  静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称

  动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号


6.如何知道一个可执行程序依赖哪些库

  ldd命令可以查看一个可执行程序依赖的共享库,

  例如# ldd /bin/lnlibc.so.6

  => /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
  => /lib/ld- linux.so.2 (0×40000000)

  可以看到ln命令依赖于libc库和ld-linux库


7.可执行程序在执行的时候如何定位共享库文件

  当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径

  此时就需要系统动态载入器(dynamic linker/loader)

  对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的

                —DT_RPATH

                —环境变量LD_LIBRARY_PATH

                —/etc/ld.so.cache文件列表

                —/lib/,/usr/lib目录找到库文件后将其载入内存


8.在新安装一个库之后如何让系统能够找到他

  如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。

  如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下

  1.编辑/etc/ld.so.conf文件,加入库文件所在目录的路径

  2.运行ldconfig,该命令会重建/etc/ld.so.cache文件



==============================================================================================

gcc生成静态库和动态库


本文转自 http://hi.baidu.com/gaomanyi/blog/item/1be9a11bdca98c1f8718bf9e.html


  我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。

       静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。

      本文主要通过举例来说明 在Linux中如何创建静态库和动态库,以及使用它们。在创建函数库前,我们先来准备举例用的源程序,并将函数库的源程序编译成.o文件。


  第1步:编辑得到举例的程序--hello.h、hello.c和main.c;

  hello.h(见程序1)为该函数库的头文件。

  hello.c(见程序2)是函数库的源程序,其中包含公用函数hello,该函数将在屏幕上输出"Hello XXX!"。

  main.c(见程序3)为测试库文件的主程序,在主程序中调用了公用函数hello。


  程序1: hello.h

  #ifndef HELLO_H
  #define HELLO_H
  void hello(const char *name);
  #endif //HELLO_H

  程序2: hello.c

  #include
  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文件;

  无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件。

  在系统提示符下键入以下命令得到hello.o文件。

  # gcc -c hello.c
  #

  (注1:本文不介绍各命令使用和其参数功能,若希望详细了解它们,请参考其他文档。)

  (注2:首字符"#"是系统提示符,不需要键入,下文相同。)

  我们运行ls命令看看是否生存了hello.o文件。

  # ls
  hello.c hello.h hello.o main.c
  #

  (注3:首字符不是"#"为系统运行结果,下文相同。)

  在ls命令结果中,我们看到了hello.o文件,本步操作完成。

  下面我们先来看看如何创建静态库,以及使用它。


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

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

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

  # ar cr libmyhello.a hello.o
  #

  我们同样运行ls命令查看结果:

  # ls
  hello.c hello.h hello.o libmyhello.a main.c
  #

  ls命令结果中有libmyhello.a。


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

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

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

  # gcc -o hello main.c -L. -lmyhello
  # ./hello
  Hello everyone!
  #

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

  # rm libmyhello.a
  rm: remove regular file `libmyhello.a'? y
  # ./hello
  Hello everyone!
  #

  程序照常运行,静态库中的公用函数已经连接到目标文件中了。

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


  第5步:由.o文件创建动态库文件;

  动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。

  在系统提示符下键入以下命令得到动态库文件libmyhello.so。

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

  我们照样使用ls命令看看动态库文件是否生成。

  # ls
  hello.c hello.h hello.o libmyhello.so main.c
  #

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

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

  # gcc -o hello main.c -L. -lmyhello
  # ./hello
  ./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
  #

  哦!出错了。快看看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件libmyhello.so复制到目录/usr/lib中, 再试试。

  # mv libmyhello.so /usr/lib
  # ./hello
  ./hello: error while loading shared libraries: /usr/lib/libhello.so: cannot restore segment prot after reloc: Permission denied

  由于SELinux引起,

  # chcon -t texrel_shlib_t /usr/lib/libhello.so
  # ./hello
  Hello everyone!
  #

  成功了。这也进一步说明了动态库在程序运行时是需要的。

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

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

  # rm -f hello hello.o /usr/lib/libmyhello.so
  # ls
  hello.c hello.h main.c
  #

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

  # gcc -c hello.c
  # ar cr libmyhello.a hello.o
  # gcc -shared -fPCI -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生成目标文件hello,并运行程序 hello。

  # gcc -o hello main.c -L. -lmyhello
  # ./hello
  ./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
  #

  从程序hello运行的结果中很容易知道,当静态库和动态库同名时, gcc命令将优先使用动态库

  

      Note:
      编译参数解析
      最主要的是GCC命令行的一个选项:
      -shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
      -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的,所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
      -L.:表示要连接的库在当前目录中
      -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称


      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文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。



=========================================================================================

关于Unix静态库和动态库的分析


转自http://xiaobin.net/200911/analytics-on-unix-static-and-dynamic-library/


基本概念

  库有动态与静态两种,动态通常用.so为后缀,静态用.a为后缀。

  例如:libhello.so libhello.a 为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,

       例如: libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。

  ln -s libhello.so.1.0 libhello.so.1
  ln -s libhello.so.1 libhello.so

1. 使用库

  当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然 而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。

       现在假设有一个叫hello的程序开发包,它提供一个静态库libhello.a 一个动态库libhello.so,一个头文件hello.h,头文件中提供sayhello()这个函数

 /* hello.h */
 void sayhello();

       另外还有一些说明文档。

  这一个典型的程序开发包结构 与动态库连接 linux默认的就是与动态库连接,下面这段程序testlib.c使用hello库中的sayhello()函数

  /*testlib.c*/
  #include
  #include
  int main()
  {
      sayhello();
      return 0;
  }



      使用如下命令进行编译

 $gcc -c testlib.c -o testlib.o

  与动态库连接用如下命令连接: 

 $gcc testlib.o -lhello -o testlib

  连接时要注意,假设libhello.o 和libhello.a都在缺省的库搜索路径下/usr/lib下,如果在其它位置要加上-L参数

       (上例中,如果/usr/lib中既有动态又有静态库,这样写是连接动态库)


       与静态库连接麻烦一些,主要是参数问题。还是上面的例子:

  $gcc testlib.o -o testlib -WI,-Bstatic -lhello

  注:这个特别的"-WI,-Bstatic"参数,实际上是传给了连接器ld。指示它与静态库连接,如果系统中只有静态库当然就不需要这个参数 了。

      (上例中,如果/usr/lib中既有动态又有静态库,这样写是连接静态库)

 

       如果要和多个库相连接,而每个库的连接方式不一样,比如上面的程序既要和libhello进行静态连接,又要和libbye进行动态连接,其命令应为:

  $gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic -lbye


2、动态库的路径问题

       为了让执行程序顺利找到动态库,有三种方法:

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

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

       例如动态库libhello.so在/home/ting/lib目录下,以bash为例,使用命令:

       $export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib

  (3) 修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾,并执行ldconfig刷新。这样,加入的目录下的所有库文件都可见。


3、查看库中的符号

  有时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有三种:

  一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;

  一种是库中定义的函数,用T表示,这是最常见的;

  另外一种是所谓的“弱 态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。


  例如,假设开发者希望知道上文提到的hello库中是否定义了 printf():

  $nm libhello.so |grep printf U

  其中printf U表示符号printf被引用,但是并没有在函数内定义,由此可以推断,要正常使用hello库,必须有其它库支持,再使用ldd命令查看hello依赖于哪些库:

   $ldd hello
     libc.so.6=>/lib/libc.so.6(0x400la000)
     /lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)

  从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以go on


4、生成库

  第一步要把源代码编绎成目标代码。

  以下面的代码为例,生成上面用到的hello库:

  /* hello.c */
  #include
  void sayhello()
  {
      printf("hello,world ");
  }

  用gcc编绎该文件,在编绎时可以使用任何全法的编绎参数,例如-g加入调试代码等:

 gcc -c hello.c -o hello.o

  (1) 连接成静态库 连接成静态库使用ar命令,其实ar是archive的意思

 $ar cqs libhello.a hello.o

  

       (2)连接成动态库 生成动态库用gcc来完成,由于可能存在多个版本,因此通常指定版本号:

 $gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o

  另外再建立两个符号连接:

  $ln -s libhello.so.1.0 libhello.so.1
  $ln -s libhello.so.1 libhello.so

  这样一个libhello的动态连接库就生成了。最重要的是传gcc -shared 参数使其生成是动态库而不是普通执行程序

       -Wl 表示后面的参数也就是-soname,libhello.so.1直接传给连接器ld进行处理。实际上,每一个库都有一个soname,当连接器发现它正在查找的程序库中有这样一个名称,连接器便会将soname嵌入连结中的二进制文件内,而不是它正在运行的实际文件名,在程序执行期间,程序会查找拥有 soname名字的文件,而不是库的文件名,换句话说,soname是库的区分标志。 这样做的目的主要是允许系统中多个版本的库文件共存,习惯上在命名库文件的时候通常与soname相同 libxxxx.so.major.minor 其中,xxxx是库的名字,major是主版本号,minor 是次版本号


总结
通过对LINUX库工作的分析,我们已经可以理解程序运行时如何去别的地方寻找“库”。


附上针对这个工程的Makefile:

# xiejingquan@gmail.com
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/C/lib

BIN_DIR=bin
LIB_DIR=lib
INC_DIR=inc
SRC_DIR=src

BIN=${BIN_DIR}/testlib
LIB=${LIB_DIR}/libhello.a ${LIB_DIR}/libhello.so

CC=gcc
AR=ar
DOC=doxygen

CFLAGS=-g -Wall -c -Iinc
LFLAGS=-lhello -L${LIB_DIR} -o
ARFLAGS=cqs
SOFLAGS=-shared -Wl,-soname,

all: ${BIN}
lib: ${LIB}
run: all
@./bin/testlib
clean:
rm -f ${BIN_DIR}/* ${LIB_DIR}/*

${BIN_DIR}/testlib: ${LIB_DIR}/testlib.o ${LIB}
${CC} $< ${LFLAGS} $@

${LIB_DIR}/testlib.o: ${SRC_DIR}/testlib.c ${INC_DIR}/hello.h
${CC} ${CFLAGS} $< -o $@

${LIB_DIR}/libhello.a: ${LIB_DIR}/hello.o
${AR} ${ARFLAGS} $@ $<

${LIB_DIR}/libhello.so: ${LIB_DIR}/hello.o
${CC} ${SOFLAGS}libhello.so.1 -o ${LIB_DIR}/libhello.so.1.0 ${LIB_DIR}/hello.o
ln -s ${LIB_DIR}/libhello.so.1.0 ${LIB_DIR}/libhello.so.1
ln -s ${LIB_DIR}/libhello.so.1 ${LIB_DIR}/libhello.so

${LIB_DIR}/hello.o: ${SRC_DIR}/hello.c ${INC_DIR}/hello.h
${CC} ${CFLAGS} $< -o $@


附上文件的目录结构:

|– bin
|   `– testlib
|– doc
|– inc
|   `– hello.h
|– lib
|   |– hello.o
|   |– libhello.a
|   |– libhello.so -> lib/libhello.so.1
|   |– libhello.so.1 -> lib/libhello.so.1.0
|   |– libhello.so.1.0
|   `– testlib.o
|– src
|   |– hello.c
|   `– testlib.c


说来也巧了,今天刚好在twitter上看到某大牛的推:

Linux下动态链接库的查找顺序:①DT_RPATH、②LD_LIBRARY_PATH环境变量、③/etc/ld.so.conf文件及/etc/ld.so.cond.d/目录内的*.conf文件、④默认路径/usr/lib,如果改动了/etc/ld.so.conf 需要使用 /sbin/ldconfig –v 来更新系统。


=============================================================================================

[Linux]链接,静态库和动态库

转自 http://blog.csdn.net/leonsc/article/details/4917816

 ------------------------------------------------------------------------------------------------------

Filename:[Linux]链接,静态库和动态库

Version:V1.0

Date:12/01/2009

Author:S.C.Leon <leonsc@126.com>

------------------------------------------------------------------------------------------------------

linux下使用静态库需要注意的几个问题

转自http://blog.csdn.net/baymoon/article/details/1437491

写了一个程序,程序分三层架构,将中层和底层都分别独立出来,包装成为静态库,最后在连接程序的时候连接这几个静态库即可;想法就是这样的简单,可是没想到在使用时,却碰到了一些小麻烦,这些小麻烦,看似不起眼,却阻止你进一步进行开发工作的进程;遇到了什么麻烦呢?

下面一一列出来进行说明:
1、先提一下一个很重要的一点,那就是你连接无论是连接静态库也好,还是共享动态库也好,都要在最后链接成为程序时将所有的这些链接库放在那条连接命令的后面,类似下面这样子:

gcc -o main main.c -L. -lhello -L/usr/local/lib -lavformat
或者:
gcc -o main main.c libhello.a -L/usr/local/lib -lavformat
因为静态链接库本身就是要将所需代码链接到程序最后的二进制文件中去,所以这种链接方法也是可取的;
但是,下面紧接这又是另外一个致命的问题;


2、在进行静态库的链接时,你的静态链接库的顺序和链接时搜索符号表的顺序有关系,规则就是你要把最低层的静态链接库放在最后面,依次类推;否则就会出现
undefined reference to `
的错误,让你丈二和尚摸不找头脑,不知所以,明明已经定义了嘛,呵呵,这就是其中的原故;由此也大致可以估计出gcc在进行参数处理或者准确的说是符号表链接处理时,对应的参数顺序就是从右到左的查找顺序,所以,在进行静态链接库时一定要注意这一点;


小小总结,至此为止;
reference:
http://blog.csdn.net/baojiangeng/archive/2005/09/03/470616.aspx
http://blog.csdn.net/thinkerABC/archive/2006/03/11/621817.aspx


==============================================================================


Linux 动态库与静态库制作及使用详解

转自 http://www.ibm.com/developerworks/cn/linux/l-cn-linklib/index.html


两个要知道的基本知识

Linux 应用程序因为 Linux 版本的众多与各自独立性,在工程制作与使用中必须熟练掌握如下两点才能有效地工作和理想地运行。

  1. Linux 下标准库链接的三种方式(全静态 , 半静态 (libgcc,libstdc++), 全动态)及其各自利弊。
  2. Linux 下如何巧妙构建 achrive(*.a),并且如何设置链接选项来解决 gcc 比较特别的链接库的顺序问题。

三种标准库链接方式选项及对比

为了演示三种不同的标准库链接方式对最终应用程序产生的区别, 这里用了一个经典的示例应用程序 HelloWorld 做演示,见 清单 1 HelloWorld。 整个工程可以在文章末尾下载。

清单 1. HelloWorld

				
 #include <stdio.h> 
 #include <iostream> 
 using std::cout; 
 using std::endl; 

 int main(int argc, char* argv[]) 
 { 
  printf("HelloWorld!(Printed by printf)\n"); 
  cout<<"HelloWorld!(Printed by cout)"<<endl; 
  return 0; 
 } 
     

三种标准库链接方式的选项及区别见 表 1

表 1. 三种标准库链接方式的选项及区别
标准库连接方式 示例连接选项 优点 缺点
全静态 -static -pthread -lrt -ldl 不会发生应用程序在 不同 Linux 版本下的标准库不兼容问题。 生成的文件比较大,
应用程序功能受限(不能调用动态库等)
全动态 -pthread -lrt -ldl 生成文件是三者中最小的 比较容易发生应用程序在 
不同 Linux 版本下标准库依赖不兼容问题。
半静态 (libgcc,libstdc++) -static-libgcc -L. -pthread -lrt -ldl 灵活度大,能够针对不同的标准库采取不同的链接策略,
从而避免不兼容问题发生。
结合了全静态与全动态两种链接方式的优点。
比较难识别哪些库容易发生不兼容问题,
目前只有依靠经验积累。
某些功能会因选择的标准库版本而丧失。

上述三种标准库链接方式中,比较特殊的是 半静态链接方式,主要在于其还需要在链接前增加额外的一个步骤:
ln -s `g++ -print-file-name=libstdc++.a`,作用是将 libstdc++.a(libstdc++ 的静态库)符号链接到本地工程链接目录。
-print-file-name 在 gcc 中的解释如下:
-print-file-name=<lib> Display the full path to library <lib>

为了区分三种不同的标准库链接方式对最终生成的可执行文件的影响,本文从两个不同的维度进行分析比较:


维度一:最终生成的可执行文件对标准库的依赖方式(使用 ldd 命令进行分析)

ldd 简介:该命令用于打印出某个应用程序或者动态库所依赖的动态库 
涉及语法:ldd [OPTION]... FILE...
其他详细说明请参阅 man 说明。

三种标准库链接方式最终产生的应用程序的可执行文件对于标准库的依赖方式具体差异见 图 1、图 2、图 3所示:


图 1. 全静态标准库链接方式


图 2. 全动态标准库链接方式


图 3. 半静态(libgcc,libstdc++) 标准库链接方式


通过上述三图,可以清楚的看到,当用 全静态标准库的链接方式时,所生成的可执行文件最终不依赖任何的动态标准库,
而 全动态标准库的链接方式会导致最终应用程序可执行文件依赖于所有用到的标准动态库。
区别于上述两种方式的 半静态链接方式则有针对性的将 libgcc 和 libstdc++ 两个标准库非动态链接。
(对比 图 2与 图 3,可见在 图 3中这两个标准库的动态依赖不见了)

从实际应用当中发现,最理想的标准库链接方式就是半静态链接,通常会选择将 libgcc 与 libstdc++ 这两个标准库静态链接,
从而避免应用程序在不同 Linux 版本间标准库依赖不兼容的问题发生。


维度二 : 最终生成的可执行文件大小(使用 size 命令进行分析)

size 简介:该命令用于显示出可执行文件的大小 
涉及语法:size objfile...
其他详细说明请参阅 man 说明。

三种标准库链接方式最终产生的应用程序的可执行文件的大小具体差异见 图 4、图 5、图 6所示:


图 4. 全静态标准库链接方式


图 5. 全动态标准库链接方式


图 6. 半静态(libgcc,libstdc++) 标准库链接方式


通过上述三图可以看出,最终可执行文件的大小随最终所依赖的标准动态库的数量增加而减小。
从实际应用当中发现,最理想的是 半静态链接方式,因为该方式能够在避免应用程序于 
不同 Linux 版本间标准库依赖不兼容的问题发生的同时,使最终生成的可执行文件大小最小化。

示例链接选项中所涉及命令(引用 GCC 原文):

-llibrary
-l library:指定所需要的额外库 
-Ldir:指定库搜索路径 
-static:静态链接所有库 
-static-libgcc:静态链接 gcc 库 
-static-libstdc++:静态链接 c++ 库 
关于上述命令的详细说明,请参阅 GCC 技术手册 


Linux 下静态库(archive)的制作方式:

涉及命令:ar

ar 简介:处理创建、修改、提取静态库的操作 

涉及选项:
t - 显示静态库的内容 
r[ab][f][u] - 更新或增加新文件到静态库中 
[s] - 创建文档索引 
ar -M [<mri-script] - 使用 ar 脚本处理 
其他详细说明请参阅 man 说明。

示例情景:

假设现有如 图 7所示两个库文件

图 7. 示例静态库文件

从 图 7中可以得知,CdtLog.a 只包含 CdtLog.o 一个对象文件 , 而 xml.a 包含 TXmlParser.o 和 xmlparser.o 两个对象文件 
现将 CdtLog.o 提取出来,然后通过 图 8方式创建一个新的静态库 demo.a,可以看出,demo.a 包含的是 CdtLog.o 以及 xml.a,
而不是我们所预期的 CdtLog.o,TXmlParser.o 和 xmlparser.o。这正是区别于 Windows 下静态库的制作。

图 8. 示例静态库制作方式 1

这样的 demo.a 当被链接入某个工程时,所有在 TXmlParser.o 和 xmlparser.o 定义的符号都不会被发现,从而会导致链接错误,
提示无法找到对应的符号。显然,通过图 8 方式创建 Linux 静态库是不正确的。

正确的方式有两种:

  1. 将所有静态库中包含的对象文件提取出来然后重新打包成新的静态库文件。
  2. 用一种更加灵活的方式创建新的静态库文件:ar 脚本

显然,方式 1 是比较麻烦的,因为涉及到太多的文件处理,可能还要通过不断创建临时目录用于保存中间文件。
推荐使用如 清单 2 createlib.sh所示的 ar 脚本方式进行创建:

清单 2 createlib.sh

				
 rm demo.a 
 rm ar.mac 
 echo CREATE demo.a > ar.mac 
 echo SAVE >> ar.mac 
 echo END >> ar.mac 
 ar -M < ar.mac 
 ar -q demo.a CdtLog.o 
 echo OPEN demo.a > ar.mac 
 echo ADDLIB xml.a >> ar.mac 
 echo SAVE >> ar.mac 
 echo END >> ar.mac 
 ar -M < ar.mac 
 rm ar.mac 
     

如果想在 Linux makefile 中使用 ar 脚本方式进行静态库的创建,可以编写如 清单 3 BUILD_LIBRARY所示的代码:

清单 3 BUILD_LIBRARY

				
 define BUILD_LIBRARY 
 $(if $(wildcard $@),@$(RM) $@) 
 $(if $(wildcard ar.mac),@$(RM) ar.mac) 
 $(if $(filter %.a, $^), 
 @echo CREATE $@ > ar.mac 
 @echo SAVE >> ar.mac 
 @echo END >> ar.mac 
 @$(AR) -M < ar.mac 
 ) 
 $(if $(filter %.o,$^),@$(AR) -q $@ $(filter %.o, $^)) 
 $(if $(filter %.a, $^), 
 @echo OPEN $@ > ar.mac 
 $(foreach LIB, $(filter %.a, $^), 
 @echo ADDLIB $(LIB) >> ar.mac 
 ) 
 @echo SAVE >> ar.mac 
 @echo END >> ar.mac 
 @$(AR) -M < ar.mac 
 @$(RM) ar.mac 
 ) 
 endef 

 $(TargetDir)/$(TargetFileName):$(OBJS) 
    $(BUILD_LIBRARY) 
     

通过 图 9,我们可以看到,用这种方式产生的 demo.a 才是我们想要的结果。


图 9. 巧妙创建的静态库文件结果

回页首

Linux 静态库链接顺序问题及解决方法:

正如 GCC 手册中提到的那样:
It makes a difference where in the command you write this option; the linker
searches and processes libraries and object files in the order they are specified.
Thus, ‘ foo.o -lz bar.o ’ searches library ‘ z ’ after file ‘ foo.o ’ but before
‘ bar.o ’ . If ‘ bar.o ’ refers to functions in ‘ z ’ , those functions may not be loaded.

为了解决这种库链接顺序问题,我们需要增加一些链接选项 :
$(CXX) $(LINKFLAGS) $(OBJS) -Xlinker "-(" $(LIBS) -Xlinker "-)" -o $@
通过将所有需要被链接的静态库放入 -Xlinker "-(" 与 -Xlinker "-)" 之间,可以是 g++ 链接过程中, 自动循环链接所有静态库,从而解决了原本的链接顺序问题。

涉及链接选项:-Xlinker

-Xlinker option
Pass option as an option to the linker. You can use this to supply system-specific
linker options which GCC does not know how to recognize.

回页首

小结

本文介绍了 Linux 下三种标准库链接的方式及各自利弊,同时还介绍了 Linux 下静态库的制作及使用方法,相信能够给 大多数需要部署 Linux 应用程序和编写 Linux Makefile 的工程师提供有用的帮助。


回页首

下载

描述 名字 大小 下载方法
本文用到的 HelloWorld 代码示例 HelloWorld.zip 2.49KB HTTP
参考的 GCC PDF 文档 gcc.pdf 2.88MB HTTP



参考资料

学习

  • 有关 ar, 请参考: linux ar 打包库到另一个库中。
  • 有关静态链接,请参考:Linking libstdc++ statically。
  • 在 developerWorks Linux 专区 寻找为 Linux 开发人员(包括 Linux 新手入门)准备的更多参考资料,查阅我们 最受欢迎的文章和教程。 
  • 在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程。 
  • 随时关注 developerWorks 技术活动和网络广播。 

获得产品和技术

  • 下载 IBM 软件试用版,体验强大的 DB2®,Lotus®,Rational®,Tivoli®和 WebSphere®软件。

讨论

  • 参与论坛讨论。
  • 查看 developerWorks 博客的最新信息。
  • 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。



=========================================================================================================

如何查看静态库内容 Unix/Linux

转自 http://blog.csdn.net/kitbo/article/details/3532632


以下从最好情况->最坏情况:

1. 最好知道静态库的原文件(.c),要是知道声明文件(.h)也比较好。

2. ar -t YourFile 看其结构,找其中的原文件。

3. 可以将库文件下到本地用UE打开,然后你就找吧。

另外,看动态库用 nm -D lib*.so

 

附:

1.  ar基本用法
2.  nm基本用法命令

  当我们的程序中有经常使用的模块,而且这种模块在其他程序中也会用到,这时按照软件重用的思想,我们应该将它们生成库,使得以后编程可以减少开发代码量。这里介绍两个命令ar和nm,用来对库操作。

1.     ar基本用法

  ar命令可以用来创建、修改库,也可以从库中提出单个模块。库是一单独的文件,里面包含了按照特定的结构组织起来的其它的一些文件(称做此库文件的member)。原始文件的内容、模式、时间戳、属主、组等属性都保留在库文件中。

  下面是ar命令的格式:

  ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files...


  例如我们可以用ar rv libtest.a hello.o hello1.o来生成一个库,库名字是test,链接时可以用-ltest链接。该库中存放了两个模块hello.o和hello1.o。选项前可以有‘-‘字符,也可以没有。下面我们来看看命令的操作选项任选项。现在我们把{dmpqrtx}部分称为操作选项,而[abcfilNoPsSuvV]部分称为任选项。

  {dmpqrtx}中的操作选项在命令中只能并且必须使用其中一个,它们的含义如下:

       d:从库中删除模块。按模块原来的文件名指定要删除的模块。如果使用了任选项v则列出被删除的每个模块。
       m:该操作是在一个库中移动成员。当库中如果有若干模块有相同的符号定义(如函数定义),则成员的位置顺序很重要。如果没有指定任选项,任何指定的成员将移到库的最后。也可以使用'a','b',或'I'任选项移动到指定的位置。 
       p:显示库中指定的成员到标准输出。如果指定任选项v,则在输出成员的内容前,将显示成员的名字。如果没有指定成员的名字,所有库中的文件将显示出来。 
      q:快速追加。增加新模块到库的结尾处。并不检查是否需要替换。'a','b',或'I'任选项对此操作没有影响,模块总是追加的库的结尾处。如果使用了任选项v则列出每个模块。 这时,库的符号表没有更新,可以用'ar s'或ranlib来更新库的符号表索引。 
      r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。 
      t:显示库的模块表清单。一般只显示模块名。(可通过此显示静态库的依赖,主要是.c和.cpp关系,静态库不存在依赖关系
      x:从库中提取一个成员。如果不指定要提取的模块,则提取库中所有的模块。 
  下面在看看可与操作选项结合使用的任选项:

       a:在库的一个已经存在的成员后面增加一个新的文件。如果使用任选项a,则应该为命令行中membername参数指定一个已经存在的成员名。 
      b:在库的一个已经存在的成员前面增加一个新的文件。如果使用任选项b,则应该为命令行中membername参数指定一个已经存在的成员名。 
       c:创建一个库。不管库是否存在,都将创建。 
      f:在库中截短指定的名字。缺省情况下,文件名的长度是不受限制的,可以使用此参数将文件名截短,以保证与其它系统的兼容。 
      i:在库的一个已经存在的成员前面增加一个新的文件。如果使用任选项i,则应该为命令行中membername参数指定一个已经存在的成员名(类似任选项b)。 
       l:暂未使用 
       N:与count参数一起使用,在库中有多个相同的文件名时指定提取或输出的个数。 
       o:当提取成员时,保留成员的原始数据。如果不指定该任选项,则提取出的模块的时间将标为提取出的时间。 
       P:进行文件名匹配时使用全路径名。ar在创建库时不能使用全路径名(这样的库文件不符合POSIX标准),但是有些工具可以。 
       s:写入一个目标文件索引到库中,或者更新一个存在的目标文件索引。甚至对于没有任何变化的库也作该动作。对一个库做ar s等同于对该库做ranlib。 
      S:不创建目标文件索引,这在创建较大的库时能加快时间。 
       u:一般说来,命令ar r...插入所有列出的文件到库中,如果你只想插入列出文件中那些比库中同名文件新的文件,就可以使用该任选项。该任选项只用于r操作选项。 
      v:该选项用来显示执行操作选项的附加信息。 
      V:显示ar的版本。 


2.    nm基本用法命令

  nm用来列出目标文件的符号清单。下面是nm命令的格式:

  nm [-a|--debug-syms] [-g|--extern-only] [-B][-C|--demangle] [-D|--dynamic] [-s|--print-armap][-o|--print-file-name] [-n|--numeric-sort][-p|--no-sort] [-r|--reverse-sort] [--size-sort][-u|--undefined-only] [-l|--line-numbers] [--help][--version] [-t radix|--radix=radix][-P|--portability] [-f format|--format=format][--target=bfdname] [objfile...]


  如果没有为nm命令指出目标文件,则nm假定目标文件是a.out。下面列出该命令的任选项,大部分支持"-"开头的短格式和"—"开头的长格式。

-A、-o或--print-file-name:在找到的各个符号的名字前加上文件名,而不是在此文件的所有符号前只出现文件名一次。 
      例如nm libtest.a的输出如下:

CPThread.o:
00000068 T Main__8CPThreadPv
00000038 T Start__8CPThread
00000014 T _._8CPThread
00000000 T __8CPThread
00000000 ? __FRAME_BEGIN__
.......................................

        则nm -A 的输出如下:

libtest.a:CPThread.o:00000068 T Main__8CPThreadPv
libtest.a:CPThread.o:00000038 T Start__8CPThread
libtest.a:CPThread.o:00000014 T _._8CPThread
libtest.a:CPThread.o:00000000 T __8CPThread
libtest.a:CPThread.o:00000000 ? __FRAME_BEGIN__
..................................................................

  -a或--debug-syms:显示调试符号。 
  -B:等同于--format=bsd,用来兼容MIPS的nm。 
  -C或--demangle:将低级符号名解码(demangle)成用户级名字。这样可以使得C++函数名具有可读性。 
  -D或--dynamic:显示动态符号。该任选项仅对于动态目标(例如特定类型的共享库)有意义。 
  -f format:使用format格式输出。format可以选取bsd、sysv或posix,该选项在GNU的nm中有用。默认为bsd。 
  -g或--extern-only:仅显示外部符号。 
  -n、-v或--numeric-sort:按符号对应地址的顺序排序,而非按符号名的字符顺序。 
  -p或--no-sort:按目标文件中遇到的符号顺序显示,不排序。 
  -P或--portability:使用POSIX.2标准输出格式代替默认的输出格式。等同于使用任选项-f posix。 
  -s或--print-armap:当列出库中成员的符号时,包含索引。索引的内容包含:哪些模块包含哪些名字的映射。 
  -r或--reverse-sort:反转排序的顺序(例如,升序变为降序)。 
  --size-sort:按大小排列符号顺序。该大小是按照一个符号的值与它下一个符号的值进行计算的。 
  -t radix或--radix=radix:使用radix进制显示符号值。radix只能为"d"表示十进制、"o"表示八进制或"x"表示十六进制。 
  --target=bfdname:指定一个目标代码的格式,而非使用系统的默认格式。 
  -u或--undefined-only:仅显示没有定义的符号(那些外部符号)。 
  -l或--line-numbers:对每个符号,使用调试信息来试图找到文件名和行号。对于已定义的符号,查找符号地址的行号。对于未定义符号,查找指向符号重定位入口的行号。如果可以找到行号信息,显示在符号信息之后。
  -V或--version:显示nm的版本号。 
  --help:显示nm的任选项。

  注: 有其它方法请留言,谢!



===============================================================================================================

一些与编译,链接相关的问题

转自  http://hi.baidu.com/ismayday/item/68e14e3f974047312e0f81ef

       地址无关代码,在64位下编译动态库的时候,经常会遇到下面的错误

/usr/bin/ld: /tmp/ccQ1dkqh.o: relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC

       提示说需要-fPIC编译,然后在链接动态库的地方加上-fPIC的参数编译结果还是报错,需要把共享库所用到的所有静态库都采用-fPIC编译一边才可以成功的在64位环境下编译出动态库。

       这里的-fPIC指的是地址无关代码

       这里首先先说明一下装载时重定位的问题,一个程序如果没有用到任何动态库,那么由于已经知道了所有的代码,那么装载器在把程序载入内存的过程中就可以直接安装静态库在链接的时候定好的代码段位置直接加载进内存中的对应位置就可以了。但是在面对动态的库的时候 ,这种方式就不行了。假设需要载入共享库A,但是在编译链接的时候使用的共享库和最后运行的不一定是同一个库,在编译期就没办法知道具体的库长度,在链接的时候就没办法确定它或者其他动态库的具体位置。另一个方面动态库中也会用到一些全局的符号,这些符号可能是来自其他的动态库,这在编译器是没办法假设的(如果可以假设那就全是静态库了)。

       基于上面的原因,就要求在载入动态库的时候对于使用到的符号地址实现重定位。在实现上在编译链接的时候不做重定位操作,地址都采用相对地址,一但到了需要载入的时候,根据相对地址的偏移计算出最后的绝对地址载入内存中。

       但是这种采用装载时重定位的方式存在一个问题就是相同的库代码(不包括数据部分)不能在多个进程间共享(每个代码都放到了它自己的进程空间中),这个失去了动态库节省内存的优势。

       为了解决这个问题,ELF中的做法是在数据段中建立一个指向那些需要被使用(内部的位置无关简单采用相对地址访问就可以实现)的地址列表(也被称为全局偏移表,Global offset table, GOT). 可以通过GOT相对应的位置进行间接引用.

       对于我们的32位环境来说, 编译时是否加上-fPIC, 都不会对链接产生影响, 只是一份代码的在内存中有几个副本的问题(而且对于静态库而言结果都是一样的).但在64位的环境下装载时重定位的方式存在一个问题就是在我们的64位环境下用来进行位置偏移定位的cpu指令只支持32位的偏移, 但实际中位置的偏移是完全可能超过64位的,所以在这种情况下编译器要求用户必须采用fPIC的方式进行编译的程序才可以在共享库中使用。

       从理论上来说-fPIC由于多一次内存取址的调用,在性能上会有所损失.不过从目前的一些测试中还无法明显的看出加上-fPIC后对库的性能有多大的损失,这个可能和我们现在使用的机器缓存以及大量寄存器的存在相关.


小提示

       -fPIC-fpic上面的介绍可以看到,gcc要使用地址无关代码加上-fPIC即可,但是在gcc的手册中我们可以看到一个-fpic(区别在一个大写一个小写)的参数,从功能上来说它们都是一样的。-fpic在一些特定的环境中(包括硬件环境)可以有针对性的进行优化,产生更小更快的代码, 但是由于受到平台的限制,像我们的编译环境,开发环境,运行环境都不完全统一的情况下面使用fpic有一定未知的风险,所有决大多数情况下我们使用 -fPIC来产生地址无关代码。

       共享内存效率

       共享内存在只读的情况下性能和读普通内存是一样的(如果不算第一载入的消耗),而且由于是多个进程共享对cpu cache还显的相对友好。

       同时存在静态库和动态库

       前面提到编译动态库的时候有提到编译动态库可以像编译静态库那样采用-Lpath -lxx的方式进行, 但这里存在一个问题,如果在path目录下既有动态库又有静态库的时候的行为又是什么样地? 事实上在这种情下, 链接器优先选择采用动态库的方式进行编译.比如在同一目录下存在 libx.a 和 libx.so, 那么在链接的时候会优先选择libx.so进行链接. 这也是为什么在com组维护的第三方库(third, third-64)中绝大多数库的产出物中只有.a的存在, 主要就是为了避免在默认情况下使用到.so的库, 导致在上线的时候出现麻烦(特别是一些系统中存在,但又与我们需要使用的版本有出入的库).

       为了能够控制动态库和静态库的编译, 有下面的几种方式

       直接使用要编译的库在前面也提到了在编译静态库的时候有三种方式目标文件.o 直接使用静态库文件.a 直接编译采用 -L -l方式进行编译

       编译的时候如果不采用-Lpath -lxx的方式进行编译, 而且直接写上 path/libx.a 或者 path/libx.so 进行编译,那么在链接的时候就是使用我们指定的 .a 或者 .so进行编译不会出现 所谓的动态库优先还是静态库优先的问题. 但这个方案需要知道编译库的路径,一些情况下并不适合使用。

       --static参数

       在gcc的编译的时候加上--static参数, 这样在编译的时候就会优先选择静态库进行编译,而不是按照默认的情况选择动态库进行编译.

       不过使用--static参数会带来另外的问题,不推荐使用,主要会带来下面的问题

       如果只有动态库,而不存在同名的静态库,链接的时候也不会报错,但在运行的时候可能会出现错误 /lib/ld64.so.1: bad ELF interpreter:由于我们程序本身在运行的需要系统中一些库的支持,在采用--static编译方式之后,链接的就是这些库的静态编译版本,等于使用的是编译机上的库,但是我们的运行环境可能和编译机有所不同,glibc这些动态库的存在本身的目的就是为了能让在一台机器上编译好的库能够比较方便的移到另外的机器上,程序本身只需要关注接口,至于从接口到底层的部分由每台机器上的.so来处理.

       不过这个问题也不是那么绝对,在一些特殊情况下(比如 glibc, gcc存在大版本差异的时候,主要是gcc2到gcc3有些地方没有做好,abi不兼容的问题比较突出,真遇到这些情况其实需要换编译器了) --static编译反倒可以正常的运行.但是还是不推荐使用, 这些是可以采用其它方法规范在后面的第6点中有说明.

       另外就是glibc --static编译可能会产生下面的warning:warning: Using 'getservbyport_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking。

       这个主要原因是由于getservbyport_r。这样的接口还是需要动态库的支持才可以运行,许多glibc的函数都存在这样的问题, 特别是网络编程的接口中是很常见的.对一些第三方工具不友好,类似valgrind检查内存泄露为了不在一些特殊的情况下误报, 它需要用动态库的方式替换glibc中的函数,如果静态编译那么valgrind就无法替换这些函数,产生误报甚至无法报错.

        tcmalloc在这种情况下也不能支持.64位环境中使用的pthread库,如果是使用的是动态库那么采用的是ntpl库,如果是静态库采用的linuxthread库,使用--static 会导致性能下降--static之后会导致代码大小变大,对cpu代码cache不友好,浪费内存空间,不过对于小代码问题也不大.

       链接参数控制

       链接器中提供了-dn -dy 参数来控制使用的是动态库还是静态库,-dn表示后面使用的是静态库,-dy表示使用的是动态库

       例:

g++ -Lpath -Wl,-dn -lx -Wl,-dy -lpthread

      这样如果在path路径下有libx.so和libx.a这个时候只会用到 libx.a.

      注意在最后的地方如果没有-Wl,-dy 让后面的库都使用动态库,可能会报出 "cannot find -lgcc_s" 的错误,这是由于glibc的.a库和.so库名字不同,--static会自动处理,但是 -Wl,-dy却不会去识别这个问题.

      小提示

      如果使用--static, 由于-dy的使用导致后面的库都是共享库(dy强制屏蔽了静态库),这个时候编译出来的程序和只有动态库的情况下强制使用--static编译一样都会报错

      运行报错 "undefined reference to `xxx()' "

      对于动态链接库,实际的符号定位是在运行期进行的.在编译.so的时候,如果没有把它需要的库和他一起进行联编,比如libx.so 需要使用uldict, 但是忘记在编译libx.so的时候加上-luldict的话,在编译libx.so的时候不会报错,因为这个时候libx.so被认为是一个库,它里面存在一些不知道具体实现的符号是合法的,是可以在运行期指定或者编译另外的二进制程序的时候指定.

      如果是采用 g++ -Lpath -lx 的方式进行编译,链接器会发现所需要的uldict的符号表找不到从而报错,但是如果是程序采用dlopen的方式载入,由于是运行期,这个程序在这个地方就直接运行报错了.另外还有一种情况就是一个对外的接口在动态库中已经声明定义了,但是忘记实现了,这个时候也会产生类似的错误.

      如果在运行期报出这样的错误,就要注意是否是由于某些库没有链接进来或者某些接口没有实现的原因产生。


=================================================

      收集了一些与编译,链接相关的问题, 有问题随时欢迎提问.

      纯C程序如何使用ullib这些用g++编译出来的库

      上文已经介绍过了, 在g++的环境中直接编译的结果会导致符合表与gcc编译的结果不同导致不能混合编译.

      gcc使用g++编译的库原则:

      1. g++编译库的时候需要把被外界使用的接口按照纯C++可以接受的方式用extern "C" 包起来,并且加上__cplusplus宏的判断,可以参考public/mcpack, public/nshead中的写法. 对于一些特殊情况,比如已经是g++编译出来的库又不适合修改,比如ullib, 分词库等,可以自己写一个 xxx.cpp的程序,在xxx.cpp对需要使用的接口再做一次纯C接口的封装,同时用extern "C"把纯C接口导出使用.使用g++编译,并且在链接的时候加上ullib等库即可. 2. gcc编译g++库在我们的64位环境中需要在最后加上-lstdc++。

      gcc使用g++编译的库多见于需要将基础库与php扩展,apache mod进行联编;

      g++使用gcc编译出来的库: 这个比较简单,只需要gcc编译的提供的头文件采用了extern "C"封装即可.

      在同样的环境下用同样的方式编译出来的程序md5是否都一样。

      如果环境完全一样包括编译路径,环境变量等都是一样的,一般情况下确实是一样的,但是许多环境的情况我们很难做到一样,比如程序使用一些DATA这样与时间相关宏就会导致每次编译的结果都是不一样的,有时候甚至内存的多少也会影响编译的结果

      链接和运行的时候,静态库和动态库路径的查找顺序都是什么?

      链接的时候查找顺序:

      -L 指定的路径, 从左到右依次查找由 

            环境变量 LIBRARY_PATH 指定的路径,使用":"分割从左到右依次查找

            /etc/ld.so.conf 指定的路径顺序/lib 和 /usr/lib (64位下是/lib64和/usr/lib64)

      动态库调用的查找顺序:

      ld的-rpath参数指定的路径, 这是写死在代码中的ld脚本指定的路径

      LD_LIBRARY_PATH 指定的路径

      /etc/ld.so.conf 指定的路径/lib和/usr/lib(64位下是/lib64和/usr/lib64)

      一般情况链接的时候我们采用-L的方式指定查找路径, 调用动态链接库的时候采用LD_LIBRARY_PATH的方式指定链接路径.

      另外注意一个问题,就是只要查找到第一个就会返回,后面的不会再查找. 比如-L./A -L./B -lx 在A中有libx.a B中有libx.a和libx.so, 这个时候会使用在./A的libx.a 而不会遵循动态库优先的原则,因为./A是先找到的,并且没有同名动态库存在.

      哪些情况会出现 "undefined reference error" 的错误?

      这里再总结一下这个问题可能出现的场景:

      没有指定对应的库(.o/.a/.so) 使用了库中定义的实体,但没有指定库(-lXXX)或者没有指定库路径(-LYYY),会导致该错误,连接库参数的顺序不对 在默认情况下,对于-l 使用库的要求是越是基础的库越要写在后面,无论是静态还动态gcc/ld版本不匹配 gcc/ld的版本的兼容性问题,由于gcc2 到 gcc3大版本的兼容性存在问题(其实gcc3.2到3.4也一定程度上存在这样的问题) 当在高版本机器上使用低版本的机器就会导致这样的错误, 这个问题比较常见在32位的环境上, 另外就在32位环境不小心使用了64位的库或者反过来64位环境使用了32位的库.C/C++相互依赖和链接 gcc和g++编译结果的混用需要保证能够extern "C" 两边都可以使用的接口,在我们的64位环境中gcc链接g++的库还需要加上 -lstdc++,具体见前文对于混合编译的说明运行期报错 这个问题基本上是由于程序使用了dlopen方式载入.so, 但.so没有把所有需要的库都链接上,具体参加上文中对于静态库和动态库混合使用的说明      可以把两个.o直接合并成一个.o文件吗?

      可以,命令是 ld -r a.o b.o -o x.o, 不过不推荐这样做,这样做唯一的好处是静态库在链接的时候如果使用到了a.o中的符号也可以同时把b.o中的符号链接进来,可以避免--whole-archive的应用.

      但是不推荐这样做,无形中增加了对源文件维护的麻烦

      为什么使用inline,并没有把代码inline进程序?

      首先加了inline的函数是否可以被inline这个是由编译器决定,很多时候即时是指定了inline但还是无法被inline

      另外注意到gcc中,只有在使用-O以上的优化后inline才会起作用,没有-O, -O2, -O3这些优化手段,无论是否加上了-finline-functions gcc都是不会进行inline优化的,这个时候的inline相当于一个普通函数(其实还是有一点区别,在符号表中表示是不一样的).程序在编译的时候加上了-finline-functions 但如果没有-OX(X>=1)的配合, -finline-functions其实是无效的,不会起作用也不会报错。

      gcc里面为了能够支持在不加-OX(X>=1)的情况下能够将函数inline, 提供了一个扩展always_inline, 将函数写成下面这样

__attribute__((always_inline)) int foo()
{
...
}

      就可以在不加-OX(X>=1)的情况下把foo inline进程序,不过always_inline 这个扩展只在gcc3以后支持,32位环境中使用的2.96 gcc是不支持的.

      64位机器上可以编译出32位程序吗?

      理论上是可以的, 在64位机器上的64位gcc中提供了-m32的参数,可以指定进行32位的编译, 但是编译问题虽然解决链接问题却还是存在,在64位的机器上可以用进行链接的库主要有2个一个是供64位程序使用的,另外一个供gcc2.96编译程序在64位机器上运行的,这两个库都不能给gcc -m32出来的结果提供链接环境(32位库不能连接64位库,给gcc2.96的库太老的不兼容), 所以在编译机器环境上是不能直接编译出可执行的32位程序(编译成.o文件还是可以的)

      为什么编写的动态链接库不能直接运行?

      在共享库的总结中介绍了如何实现共享库可以自己运行,但是有些时候会出现undefined reference error的错误导致共享库不能被运行。

      这种情况产生的原因是:动态库中采用了类似 static int val = func(xxx);的写法, 其中val 是一个全局变量(或者静态全局变量)。 动态库被载入内存中使用的时候会直接先运行func这个函数,如果func是来自其他的库(比如一些情况下主程序使用-rdynamic编译,动态库使用主程序的空间), 在编译动态链接的库的时候又没有被链接上, 这个时候就会出现这样的问题。

      对于这样的问题主要考虑下面的解决方案:

      1. 不要采用static int val = func(xxx);这种写法

      将使用的静态库链接进共享库, 但这里要注意-rdynamic的影响,必要的时候需要保证和主程序使用的库版本是相同的。让共享库不可运行也是一种解决方案

      是否可以在main函数开始前就执行程序?

      如果在main函数开始前执行代码,一般有下面的两种方法

      采用 int val = func(xxx)的方式,在func(xxx)中执行声明一个class, 把需要运行的函数写在class. 并且定义一个全局(或者static)的类变量

      在实现上,编译器把它们放到一个特殊的符号 _init 中,在程序被载入内存的时候被执行

      但是这种方式我们不推荐使用,特别是在这些执行代码中存在库与库之间的依赖关系的时候, 比如下面的场景:

// libA.cpp
class Aclass
{
     public:
     Aclass()
     {
         int * u = Bfunc(); //这是另外一个库libB中的函数
         int c = u[0];
     }
}

static Aclass s_test;
// libB.cpp
staticint *s_test = test_init(); //初始化s_test
int *Bfunc()
{
     return s_test;
}

      上面的程序中有2个库,A库有一个static变量的构造函数依赖了 B库中的一个函数, B库中的这个函数又操作了一个由函数test_init初始化的static变量.

按照程序的要求我们必须要让test_init()这个函数在Aclass这个函数之前运行, 但是可惜的在某些情况我们很难做到这点, 这里涉及到链接器对库链接和初始化顺序的问题.

      在默认情况下, test_init()和s_test的构造函数的执行顺序是按照链接的时候-l的顺序从右到左, 比如-lB -lA 那么Aclass的构造函数会在test_init()前执行,这个时候就会出现问题,需要保证-lA -lB的顺序才可以正常.

      这里又涉及到另外一个问题, 就是 正常情况既然A依赖B, 那么在链接的时候肯定需要 保证 -lA在-lB. 但是这里我们只能说需要把越基础的库放在越后面,而不是必需放在最后面.还是上面的例子. 如果这个时候有一个test.cpp 使用了 A库, 并且在test中没有直接使用到B库中的东西, 这个时候如果-lB放在-lA前面,链接器会报错, 因为符号在从左往右展开的时候, 由于test没有使用到B的东西,所以没有做任何展开, 从这个角度而言在链接A的时候就找不到符号. 但是如果在test中有使用到B中和test_init相关联的函数,那么这个时候如果把-lB放在-lA的前面展开B函数的时候会把test_init导出, 这样导致A会认为已经存在了test_init, 从而不报编译错误. 但是这样的结果就是test_init的初始化顺序被放到Aclass之后, 那么在程序运行的时候就可能导致错误.

      对这种问题解决,主要有几种考虑

      采用 单例模式, 采用类似 

if (NULL == ptr) 
    ptr = new xxx; 
return ptr

的方式通过用户态的判断来控制,不过有些时候需要考虑些多线程问题,了解依赖关系, 把-lB放到-lA的后面不允许这种方式的存在.

      在使用全局变量的时候 需要特别注意这种初始化的顺序问题.小提示:构造初始化等,是在_init中处理, 另一个方面_fini是存在在程序退出前的执行析构等操作.



================================================================================================

gcc里的参数-fPIC的一些问题

转自 http://blog.csdn.net/hollyhock13/article/details/5716629

请问gcc里的参数-fPIC的一些问题

加上-fPIC参数后,编译后的文件和没有加这个参数的文件,有什么区别呢,在代码里面做了什么修改能增强它的可重定位性,或者说位置无关性呢?
而且,用没有加这个参数的编译后的共享库,也可以使用,它和加了参数后的使用起来又有什么区别呢
谢谢

印随
可能是两个原因:
1:gcc默认开启-fPIC选项
2:loader使你的代码位置无关

yjm0573
一般都用于编译共享库

baohuaihuai
我的理解.
不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
这样就失去了共享库的好处,实际上和静态库的区别并不大,在运行时占用的内存是类似的,仅仅是二进制代码占的硬盘空间小一些.而且在加载时才重定位的开销也很大(这一点使得这种做法更加没有意义).

[[i] 本帖最后由 baohuaihuai 于 2007-12-28 11:54 编辑 [/i]]

baohuaihuai
事实上有比不用fPIC编译so更加bh的...........那就是..用fPIC选项制作静态库.

accelerator
[quote]原帖由 [i]baohuaihuai[/i] 于 2007-12-28 11:52 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7779936&ptid=1035042][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]
我的理解.
不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程 ... [/quote]

阁下忽略了动态连接库的另外一个非常重要的作用, 动态连接, 这样程序可以支持二进制文件接口, 比如连接libc时一般都使用.so而不是.a, 你总不想在libc更新后重新链接你的程序吧? 实际上这种功能比所谓的share更重要, 应用也更广泛.

baohuaihuai
[quote]原帖由 [i]accelerator[/i] 于 2007-12-28 12:32 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7780263&ptid=1035042][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]


阁下忽略了动态连接库的另外一个非常重要的作用, 动态连接, 这样程序可以支持二进制文件接口, 比如连接libc时一般都使用.so而不是.a, 你总不想在libc更新后重新链接你的程序吧? 实际上这种功能比所谓的shar ... [/quote]

阁下好象误会了我的意思,我只是针对fPIC在做说明.fPIC与非fPIC的区别,与.so和.a的区别是两回事.虽然我们总是用fPIC来生成so,也从来不用fPIC来生成a.
fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目.

baohuaihuai
因此,不用fPIC编译so并不总是不好.
如果你满足以下4个需求/条件:
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大.
4.该库基本不需要被多个应用程序共享

我认为你的so就完全可以不用fPIC编译.

zx_wing
[quote]原帖由 [i]yongzhi[/i] 于 2007-12-27 16:36 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7775986&ptid=1035042][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]
加上-fPIC参数后,编译后的文件和没有加这个参数的文件,有什么区别呢,在代码里面做了什么修改能增强它的可重定位性,或者说位置无关性呢?
而且,用没有加这个参数的编译后的共享库,也可以使用,它和加了参 ... [/quote]
从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见如下
=========================================================================================
GNU/Linux下库机制笔记
转自  http://comcat.blog.openrays.org/blog-htm-do-showone-tid-69.html
1. 创建静态库:
    gcc -c hello.c -o hello.o
    ar rcs libhello.a hello.o

2. 使用静态库:
    gcc -o test test.c -static -L. -lhello


3. 共享库版本: version.minor.release


4. 构建动态共享库:

  gcc/g++下加 -fPIC -shared 参数即可

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

  其中 -shared 作用于链接阶段,实际传递给链接器ld,让其添加作为共享库所需要的额外描述
  信息,去除共享库所不需的信息。

  可以分解为如下步骤:

    I. gcc -c err.c -fPIC -o err.o
    II. gcc -shared -o liberr.so.0.0 err.o

        II <==> ld -Bshareable -o liberr.so.0.0 err.o

    III. ln -s liberr.so.0.0 liberr.so



5. 动态共享库的使用:
    
  a. 由共享库加载器自动加载

    gcc -o test test.c -lerr -L. -Wl,-rpath=./

    -Wl,option
      Pass option as an option to the linker. If option contains commas, 
      it is split into multiple options at the commas.

    -rpath: 指定运行时搜索动态库的路径,可以用环境变量LD_LIBRARY_PATH指定。


  b. 程序自己控制加载、符号解析(使用libc6之dl库)

    gcc cos.c -o cos -ldl

    /* cos.c */
    #include <stdio.h>
    #include <dlfcn.h>

    int main()
    {
      void *handle;
      double (*cosine)(double);
      char *error;
      double rev;

      handle = dlopen("libm.so", RTLD_LAZY); // 加载动态库
      if(!handle)
      {
          fprintf(stderr, "%s\n", dlerror());
          exit(1);
      }

      dlerror();

      cosine = dlsym(handle, "cos"); // 解析符号cos
      if((error = dlerror()) != NULL)
      {
          fprintf(stderr, "%s\n", error);
          exit(1);
      }

      rev = cosine(3.1415926); // 使用cos函数
      printf("The cos result is: %f\n", rev);

      dlclose(handle);

      return 0;
    }




6. GNU/Linux下动态库之加载器为/lib/ld-linux.so, 可执行的。

  /lib/ld-linux.so ./cos <===> ./cos


7. 有用的环境变量
    
    LD_LIBRARY_PATH

      指定运行时动态库的搜索路径

    LD_DEBUG

      调试用,其值可为:

      libs     display library search paths
      reloc     display relocation processing
      files     display progress for input file
      symbols   display symbol table processing
      bindings   display information about symbol binding
      versions   display version dependencies
      all       all previous options combined
      statistics display relocation statistics
      unused     determined unused DSOs
      help     display this help message and exit


8. 搜索含有cos函数的共享库名 

    nm -AD /lib/* /usr/lib/* 2>/dev/null | grep "cos$"

    nm -- 从对象文件中列出符号。


9. 读取ELF文件格式信息

    readelf -Ds ./libfoo.so #读出共享库的符号
=========================================================================================
ldconfig 命令介绍 
转自  http://www.cppblog.com/true/archive/2012/05/29/20667.html
ldconfig几个需要注意的地方
1. 往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf的,但是完了之后要调一下ldconfig,不然这个library会找不到
2. 想往上面两个目录以外加东西的时候,一定要修改/etc/ld.so.conf,然后再调用ldconfig,不然也会找不到
比如安装了一个mysql到/usr/local/mysql,mysql有一大堆library在/usr/local/mysql/lib下面,这时就需要在/etc/ld.so.conf下面加一行/usr/local/mysql/lib,保存过后ldconfig一下,新的library才能在程序运行时被找到。
3. 如果想在这两个目录以外放lib,但是又不想在/etc/ld.so.conf中加东西(或者是没有权限加东西)。那也可以,就是export一个全局变量LD_LIBRARY_PATH,然后运行程序的时候就会去这个目录中找library。一般来讲这只是一种临时的解决方案,在没有权限或临时需要的时候使用。
4. ldconfig做的这些东西都与运行程序时有关,跟编译时一点关系都没有。编译的时候还是该加-L就得加,不要混淆了。
5. 总之,就是不管做了什么关于library的变动后,最好都ldconfig一下,不然会出现一些意想不到的结果。不会花太多的时间,但是会省很多的事。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

问题集合 ---- linux 静态库和动态库 的相关文章

  • Fedora dnf 更新不起作用?

    当我尝试使用 update 命令更新 Fedora 22 时 sudo dnf update 我收到以下错误 错误 无法同步存储库 更新 的缓存 无法准备内部镜像列表 Curl 错误 6 无法解析主机名 无法解析主机 mirrors fed
  • Web 应用程序的带宽和流量模拟器?

    您能否建议如何创建一个测试环境来模拟 Web 应用程序中的各种类型的带宽和流量 或者也许是一个针对本地主机执行此操作的开源程序 我认为在编写网络应用程序时这是一个非常重要的主题 但这不是一个常见的主题 我能想象创建这种环境的唯一方法是在本地
  • 使用 inotify 的正确方法是什么?

    我想使用inotifyLinux 上的机制 我希望我的应用程序知道文件何时aaa被改变了 您能给我提供一个如何做到这一点的示例吗 文档 来自监视文件系统活动 inotify https developer ibm com tutorials
  • linux下写入后崩溃

    如果我使用 write 将一些数据写入磁盘上的文件会发生什么 但我的应用程序在刷新之前崩溃了 如果没有系统故障 是否可以保证我的数据最终会刷新到磁盘 如果您正在使用write 并不是fwrite or std ostream write 那
  • 从 Python 访问 802.11 无线管理帧

    我想从 Linux 上的 Python 嗅探 802 11 管理 探测请求 帧 这可以从 Scapy 中实现 如下所示 coding utf 8 from scapy all import def proc p if p haslayer
  • 用于编辑 /etc/sudoers 文件的正则表达式模式

    我想删除 etc sudoers 文件中的 uncommnet 轮组 那么我应该使用什么正则表达式模式 cat etc sudoers Allows members of the sys group to run networking so
  • 使用 gcc 理解共享库

    我试图理解 C 中共享库的以下行为 机器一 cat one c include
  • 在 Linux 上创建线程与进程的开销

    我试图回答在 python 中创建线程与进程有多少开销的问题 我修改了类似问题的代码 该问题基本上运行一个带有两个线程的函数 然后运行带有两个进程的相同函数并报告时间 import time sys NUM RANGE 100000000
  • 如何在 Linux/OS X 上温和地终止 Firefox 进程

    我正在使用 Firefox 进行一些自动化操作 尽管我可以从 shell 打开 Firefox 窗口 但我无法正确终止它 如果我kill火狐进程与kill 3 or kill 2当我下次打开新的 Firefox 窗口时 命令会询问我是否要在
  • 计算 TCP 重传次数

    我想知道在LINUX中是否有一种方法可以计算一个流中发生的TCP重传的次数 无论是在客户端还是服务器端 好像netstat s解决了我的目的
  • 在 Linux 控制台中返回一行?

    我知道我可以返回该行并用以下内容覆盖其内容 r 现在我怎样才能进入上一行来改变它呢 或者有没有办法打印到控制台窗口中的特定光标位置 我的目标是使用 PHP 创建一些自刷新的多行控制台应用程序 Use ANSI 转义码 http en wik
  • 如何在perl中使用O_ASYNC和fcntl?

    我想使用 O ASYNC 选项 当管道可以读取时 SIGIO 的处理程序将运行 但以下代码不起作用 任何人都可以帮助我吗 bin env perl use Fcntl SIG IO sub print catch SIGIO n my fl
  • 如何在文件夹中的 xml 文件中 grep 一个单词

    我知道我可以使用 grep 在这样的文件夹中的所有文件中查找单词 grep rn core 但我当前的目录有很多子目录 我只想搜索当前目录及其所有子目录中存在的所有 xml 文件 我怎样才能做到这一点 我试过这个 grep rn core
  • Bash - 比较 2 个文件列表及其 md5 校验和

    我有 2 个列表 其中包含带有 md5sum 检查的文件 即使文件相同 列表也具有不同的路径 我想检查每个文件的 md5 和 我们正在讨论数千个文件 这就是为什么我需要脚本来仅显示差异 第一个列表是普通列表 第二个列表是文件的当前状态 我想
  • 比较linux中的两个未排序列表,列出第二个文件中的唯一项

    我有 2 个包含号码列表 电话号码 的文件 我正在寻找一种列出第二个文件中第一个文件中不存在的数字的方法 我尝试过各种方法 comm getting some weird sorting errors fgrep v x f second
  • Visual Studio - X11:缺少 DISPLAY 环境变量

    我正在使用 Visual Studio 2019 Enterprise 开发跨平台 Windows Linux x64 GUI 应用程序 在这个 2019 版本中 我们可以使用 Visual Studio调试平台 Windows 本机 和
  • Grep 递归和计数

    需要在具有大量子目录的目录中搜索文件内的字符串 我在用着 grep c r string here 我怎样才能找到总数量 如何仅输出至少具有一个实例的文件 使用 Bash 的进程替换 这给出了我认为是您想要的输出 如果不是 请澄清问题 gr
  • 为什么 call_usermodehelper 大多数时候都会失败?

    从内核模块中 我尝试使用 call usermodehelper 函数来执行可执行文件 sha1 该可执行文件将文件作为参数并将文件的 SHA1 哈希和写入另一个文件 名为输出 可执行文件完美运行 int result 1 name hom
  • 如何回忆上一个 bash 命令的参数?

    Bash 有没有办法回忆上一个命令的参数 我通常这样做vi file c其次是gcc file c Bash 有没有办法回忆上一个命令的参数 您可以使用 or 调用上一个命令的最后一个参数 Also Alt can be used to r
  • 查找并删除超过 x 天的文件或文件夹

    我想删除超过 7 天的文件和文件夹 所以我尝试了 17 07 14 email protected cdn cgi l email protection find tmp mindepth 1 maxdepth 1 ctime 7 exec

随机推荐

  • RAID(独立磁盘冗余阵列)

    RAID 0 数据从内存缓冲区写入磁盘时 xff0c 根据磁盘数量将数据分成N份 xff0c 这些数据同时并发写入N块磁盘 xff0c 使得数据整体写入速度是一块磁盘的N倍 xff1b 读取的时候也一样 xff0c 因此具有极快的数据读写速
  • printf()函数常用格式控制

    格式字符含义d以十进制形式输出有符号整数o以八进制形式输出无符号整数x X以十六进制形式输出无符号整数u以十进制形式输出无符号整数f以小数形式输出单 双精度浮点数c输出单个字符s输出字符串p打印指针地址
  • getchar()函数使用要点

    注意点 xff1a 该函数一次只能读取一个字符 xff0c 但只在键盘上输入一个字符 xff08 回车外 xff09 时 xff0c getchar 函数读不到任何字符 xff0c 只有按下回车之后getchar 函数才会真正读取字符 键盘
  • 例子程序1:读取文件内容并打印到屏幕上

    文件名 xff1a 作者 xff1a kehanxin 时间 xff1a 说明 xff1a 读取特定文件paper txt中内容并打印在屏幕上 span class hljs comment include lt stdio h gt sp
  • 复制字符串strcpy()函数

    strcpy 函数原型 xff1a span class hljs preprocessor include lt string h gt span span class hljs keyword char span span class
  • Linux系统组成

    Linux一般有4个主要部分 xff1a 内核 Shell 文件系统 应用程序 Shell是一个命令解释器 xff0c 它解释用户输入的命令并且把它们送到内核执行 目前常见的Shell有Bourne Shell sh Korn Shell
  • 在VC++6.0中嵌入汇编

    span class hljs preprocessor include lt stdio h gt span span class hljs keyword int span main span class hljs keyword in
  • VS Code For Web 深入浅出 -- 进程间通信篇

    在上一篇中 xff0c 我们一起分析了 VS Code 整体的代码架构 xff0c 了解了 VS Code 是由前后端分离的方式开发的 且无论前端是基于 electron 还是 web xff0c 后端是本地还是云端 xff0c 其调用方式
  • QtMath:通用数学函数

    头文件 xff1a include lt QtMath gt 一 描述 这些函数是 C 或标准模板库中不可用的基本数学运算的部分方便定义 二 宏成员 M E xff1a 自然对数的底 xff0c e 61 exp 1 M LOG2E xff
  • 深入剖析PE文件 (告诉你exe文件打开后是依据什么来创建进程并在系统中运行)

    转自 深入剖析PE文件 告诉你exe文件打开后是依据什么来创建进程并在系统中运行 转载自http lwglucky blog 51cto com 1228348 283812 PE文件是Win32的原生文件格式 每一个Win32可执行文件都
  • 串口和TCP互相转发工具

    由于项目调试需求 xff0c 代码在远程服务器的虚拟机上 xff0c 在本地计算机通过串口连接需要对接的设备 xff0c 在远程服务器的上位机程序需要和此设备进行对接 xff0c 系统结构如图1所示 图1 系统网络结构图 如何将本地的串口共
  • Linux系统基础操作管理

    一 系统基础操作规范 1 第一个规范 输出命令信息需要在命令提示符之后输入 命令提示符 xff1a span class token punctuation span root 64 oldboy span class token punc
  • 解决Flask跨域问题的几种方式

    本文收录于 Python开发 专栏 xff0c 此专栏聚焦于Python开发中的编程技巧和总结 xff0c 将持续更新 xff0c 欢迎大家订阅 xff01 个人主页 xff1a 有梦想的程序星空个人介绍 xff1a 小编是人工智能领域硕士
  • STL四种智能指针浅析

    我们知道 xff0c 在C 43 43 中没有像Java那样的自动回收垃圾机制 xff0c xff0c 系统只会清理栈上由系统管理的资源 xff0c 在类中若有对堆资源的申请 xff0c 不进行手动释放资源就会导致内存泄漏问题 xff0c
  • C++摸板类 声明对象编译不过 类声明和实现都要放在头文件里

    通常情况下 xff0c 你会在 h文件中声明函数和类 xff0c 而将它们的定义放置在一个单独的 cpp文件中 但是在使用模板时 xff0c 这种习惯性做法将变得不再有用 xff0c 因为当实例化一个模板时 xff0c 编译器必须看到模板确
  • 什么是大端序和小端序,为什么要有字节序

    什么是字节序 字节序 xff0c 又称端序或尾序 xff08 英语中用单词 xff1a Endianness 表示 xff09 xff0c 在计算机领域中 xff0c 指电脑内存中或在数字通信链路中 xff0c 占用多个字节的数据的字节排列
  • opencv4.3.0+Visual Studio 2019环境配置

    1 1 解压opencv并添加环境变量 下载opencv4 3 0 xff0c 进行安装 其实是解压 xff0c 之后配置环境变量 xff0c 我的电脑 gt 属性 gt 高级系统设置 gt 环境变量 xff0c 找到Path变量 xff0
  • 动画图解:十大经典排序算法动画与解析,看我就够了!(配代码完全版)

    排序算法是 数据结构与算法 中最基本的算法之一 排序算法可以分为内部排序和外部排序 内部排序是数据记录在内存中进行排序 而外部排序是因排序的数据很大 xff0c 一次不能容纳全部的排序记录 xff0c 在排序过程中需要访问外存 常见的内部排
  • 什么是 P = NP 问题?

    点击关注上方 五分钟学算法 xff0c 设为 置顶或星标 xff0c 第一时间送达干货 转自后端技术指南针 1 前言 今天和大家一起了解个高能知识点 xff1a P 61 NP问题 看到这里我们可能是一头雾水 xff0c 不由得发问 xff
  • 问题集合 ---- linux 静态库和动态库

    本文转自多网址 xff0c 对作者表示感谢 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61