zoukankan      html  css  js  c++  java
  • 静态库和共享库开发

    再讲静态库和共享库之前先讲一下一个可执行文件的生成过程

    1、预处理

      ①将所有的#define删除,并且展开所有的宏定义

      ②处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等

      ③处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。

      ④删除所有注释 “//”和”/* */”. 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。

      ⑤保留所有的#pragma编译器指令以备编译器使用

      ⑥通常使用以下命令来进行预处理:

        gcc -E test.c -o test.i

      参数-E表示只进行预处理,打开test.i就可以看到预处理完成的内容。

    2、编译

      ①编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。预处理之后,可直接对生成的test.i文件编译,生成汇编代码:

        gcc -S test.i -o test.s

      ②gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件

    3、汇编

      ①汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。

      ②汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。

      ③对于上面生成的汇编代码文件test.s,gas汇编器负责将其编译为目标文件,如下:

        gcc -c test.s -o test.o

    4、链接

      ①gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。

      ②对于上一小节中生成的test.o,将其与C标准输入输出库进行连接,最终生成程序test

        gcc test.o -o test

    下面讲一下gcc的用法

    gcc [选项] 文件...

    选项:

    -v     显示编译器调用的程序

    -E       仅作预处理,不进行编译、汇编和链接  生成.i文件

    -S       编译到汇编语言,不进行汇编和链接 生成.s文件

    -c     编译、汇编到目标代码,进行链接,=生成.o文件

    -o file    将经过gcc处理过的结果存为file,这个结果可能是预处理,汇编,目标或最终的可执行文件

    -g[gdb]    在可执行文件中加入调试信息,若使用中括号中的选项,表示加入gdb扩展的调试信息,方便使用gdb进行调试

    -I dirname 将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数

    -L dirname 将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是在连接过程中使用的参数,默认状态下在/usr/lib中找

    -l name    在连接时,装载名字为libname.a的函数库

    1、编写hello world,并使用脚本运行、

    包含文件:main.c、run.sh

    main.c:

    #include<stdio.h>
    
    int main(void)
    {
    
        printf("Hello World!
    ");
        return 0;
    }

    run.sh:

    #!/bin/bash
    
    gcc -o main main.c
    ./main

    2、预编译以下2个.c文件,并比较它们是否相同

    包含文件:main1.c、main2.c、run.sh

    main1.c:

    #include<stdio.h>
    
    int main(void)
    {
        printf("Hello World!
    ");
        return 0;
    }

    main2.c:

    #include<stdio.h>
    #define N world
    
    int main(void)
    {
        printf("hello N");
        return 0; 
    }

    run.sh:

    #!/bin/bash
    
    gcc -E  main1.c -o main1.i
    gcc -E  main2.c -o main2.i
    diff main1.i main2.i
    if test $? -eq 0;then
        echo    "file same"
    else
        echo    "file different"
    fi

    3、生成汇编代码文件,并查看

    包含文件:main.c、run.sh

    run.sh:

    #!/bin/bash
    
    gcc -S main.c -o main.s
    cat main.s

    4、生成目标文件,并查看

    包含文件:main.c、run.sh

    run.sh:

    #!/bin/bash
    
    gcc -c main.c -o main.o
    cat main.o

    5、执行链接命令,并确定文件类型,查看目标文件信息

    包含文件:main.c、run.sh

    run.sh:

    #!/bin/bash
    
    gcc -c main.c -o main.o
    gcc -o main main.o
    file main
    objdump -x main

    6、加法的 helloworld

    包含文件:main.c、run.sh

    main.c:

    #include<stdio.h>
    
    int main(void)
    {
        int a=1;
        int b=2;
        printf("%d+%d=%d
    ",a,b,a+b);
        return 0;    
    }

    run.sh:

    #!/bin/bash
    
    gcc -o main  main.c
    ./main

    7、以命令参数执行加法计算

    包含文件:main.c、run.sh

    相关知识:

    main()函数的形式
    在最新的 C99 标准中,只有以下两种定义方式是正确的:

    int main( void )--无参数形式
    {
        ...
        return 0;
    }
    int main( int argc, char *argv[] )--带参数形式
    {
        ...
        return 0;
    }

    int 指明了 main()函数的返回类型,函数名后面的圆括号一般包含传递给函数的信息。 void 表示没有给函数传递参数。关于带参数的形式,我们等会讨论。浏览老版本的 C 代码,将会发现程序常常以main()这种形式开始。C90标准允许这种形式,但是 C99标准不允许。因此即使你当前的编译器允许,也不要这么写。你还可能看到过另一种形式:void main()有些编译器允许这种形式,但是还没有任何标准考虑接受它。C++ 之父Bjarne Stroustrup 在他的主页上的 FAQ 中明确地表示:void main( ) 的定义从来就不存在于 C++ 或者 C 。所以,编译器不必接受这种形式,并且很多编译器也不允许这么写。坚持使用标准的意义在于:当你把程序从一个编译器移到另一个编译器时,照样能正常运行。

    main()函数的返回值
    从前面我们知道 main()函数的返回值类型是 int 型的,而程序最后的return 0; 正与之遥相呼应,0就是 main()函数的返回值。那么这个0返回到那里呢?返回给操作系统,表示程序正常退出。因为return语句通常写在程序的最后,不管返回什么值,只要到达这一步,说明程序已经运行完毕。而 return的作用不仅在于返回一个值,还在于结束函数

    main()函数的参数

    C 编译器允许 main()函数没有参数,或者有两个参数(有些实现允许更多的参数,但这只是对标准的扩展)。这两个参数,一个是 int 类型,一个是字符串类型。第一个参数是命令行中的字符串数。按照惯例(但不是必须的),这个 int 参数被称为 argc(argument count)。大家或许现在才明白这个形参为什么要取这么个奇怪的名字吧,呵呵!至于英文的意思,自己查字典吧。第二个参数是一个指向字符串的指针数组。命令行中的每个字符串被存储到内存中,并且分配一个指针指向它。按照惯例,这个指针数组被称为 argv(argument value)。系统使用空格把各个字符串格开。一般情况下,把程序本身的名字赋值给 argv[0],接着,把最后的第一个字符串赋给 argv[1],等等。

    main.c:

    #include<stdio.h>
    #include<stdlib.h>
    
    int main(int argc,char *argv[])
    {
        if(argc!=3){return 0;printf("error");}
        int a=atoi(argv[1]);
        int b=atoi(argv[2]);
        printf("%d+%d=%d
    ",a,b,a+b);
        return 0;
    }

    run.sh:

    #!/bin/bash
    
    gcc -o main main.c
    ./main 1 2

    8、编写 add 函数实现加法计算

    包含文件:main.c、run.sh

    main.c

    #include<stdio.h>
    
    int add(int p1,int p2)
    {   
         int c;
         c=p1+p2;
        return c;
    
    }
    
    int main(int argc,char *argv[])
    {
          if(argc!=3){ 
              printf("error
    ");            
            return 0;
         }
          else{
            int a=atoi(argv[1]);
            int b=atoi(argv[2]);
            printf("%d+%d=%d
    ",a,b,add(a,b));
          }
        return 0;
    }

    run.sh

    #!/bin/bash
    
    gcc -o main main.c
    ./main 3 5

    9、静态链接编译与动态链接编译的区别

    包含文件:main.c、run.sh

    相关知识:

    全静态:不会发生应用程序在不同linux版本下的标准库不兼容问题,但是生成的文件比较大,应用程序功能受限(不能调用动态库等)

    全动态:生成文件小,但是容易发生不兼容问题

    main.c:

    #include<stdio.h>
    
    int add(int p1,int p2)
    {   
        int c;
        c=p1+p2;
        return c;
    }
    
    int main(int argc,char *argv[])
    {
        if(argc!=3){ 
            printf("error
    ");            
            return 0;
        }
        else{
            int a=atoi(argv[1]);
            int b=atoi(argv[2]);
            printf("%d+%d=%d
    ",a,b,add(a,b));
        }
        return 0;
    }

    run.sh:

    #!/bin/bash
    
    gcc -static main.c -o main_static
    gcc main.c -o main_dynamic
    file main_static
    file main_dynamic
    objdump -x main_static|grep NEEDED
    objdump -x main_dynamic|grep NEEDED

    10、制作静态库

    静态库是目标文件的打包,可通过 ar 命令进行制作。生成的静态库名字应该是 lib***.a,否则 gcc 命令无法识别。

    包含文件:lib/add.c、include/add.h、main.c、run.sh  

    main.c:

    #include<stdio.h>
    #include"add.h"
    
    int main(int argc,char *argv[])
    {
        if(argc!=3){
            printf("error
    ");            
            return 0;
        }
        else{
        int a=atoi(argv[1]);
        int b=atoi(argv[2]);
        printf("%d+%d=%d
    ",a,b,add(a,b));
        }
    }

    ./include/add.h:

    int add(int p1,int p2);

    ./lib/add.c:

    int add(int p1,int p2)
    {
        int c;
        c=p1+p2;
        return c;
    }

    run.sh:

    #!/bin/bash
    
    gcc -c -I./include lib/add.c -o lib/add.o
    ar -rsv lib/libadd.a lib/add.o
    gcc -o main main.c -I./include -L./lib -ladd
    ./main 3 5

    11、制作共享库

    包含文件:lib/add.c、include/add.h、main.c、run.sh

    main.c:

    #include<stdio.h>
    #include"add.h"
    
    int main(int argc,char *argv[])
    {
        if(argc!=3){ 
            printf("error
    ");            
            return 0;
        }
        else{
            int a=atoi(argv[1]);
            int b=atoi(argv[2]);
            printf("%d+%d=%d
    ",a,b,add(a,b));
        }
        return 0;
    }

    ./include/add.h:

    int add(int p1,int p2);

    ./lib/add.c:

    int add(int p1,int p2)
    {
        int c;
        c=p1+p2;
        return c;
    }

    run.sh:

    #!/bin/bash
    
    gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so
    gcc -o main main.c -I./include -L./lib/ -ladd
    echo $LD_LIBRARY_PATH
    export LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH
    ./main 1 2

    12、以多种方式实现共享库的运行

    包含文件:lib/add.c、include/add.h、main.c、run1.sh、run2.sh、run3.sh

    run1.sh:

    #!/bin/bash
    
    gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so
    gcc -o main1 main.c -I./include -L./lib/ -ladd
    echo $LD_LIBRARY_PATH
    export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH
    ./main1 1 2

    run2.sh:

    #!/bin/bash
    
    gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so
    unset LD_LIBRARY_PATH
    gcc -I./include -L./lib -Wl,-rpath=./lib  -o main2 main.c -ladd
    ./main2 1 2

    run3.sh:

    #!/bin/bash
    
    gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so
    sudo cp ./lib/libadd.so /usr/lib 
    sudo chmod 0755 /usr/lib/libadd.so
    sudo ldconfig
    gcc -o main3 main.c -I./include -L./lib/ -ladd
    ./main3 1 2

    13、升级替换共享库

    共享库的优势是只需要针对相应的库重新编译升级,而不需要重新编译生成可执行文件,保证相互独立性。

    本题不需要main.c,直接在脚本中借用11题的main.c

    包含文件:lib/add.c、include/add.h、run.sh

    ./lib/add.c:

    int add(int p1,int p2)
    {
        int c;
        c=p1&p2;
        return c;
    }

    ./include/add.h:

    int add(int p1,int p2);

    run.sh:

    #!/bin/bash
    
    gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so
    #设置环境变量
    export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH 
    ../11/main 1 4 

      

    14、动态加载动态库

    本题不需要lib和include,直接借用12题和13题的动态库

    包含文件:main.c、run.sh

    main.c:

    #include <stdio.h>
    #include <dlfcn.h>

    int main(int argc, char **argv)
    {
      void *lib_handle;
      int (*add)();
      char *error;
      if(argc!=4){
        printf("error"); return 0;
      }
      /*打开一个动态链接库*/
      lib_handle = dlopen(argv[3], RTLD_LAZY);
      if (!lib_handle){
        fprintf(stderr, "%s ", dlerror());
        return 1;
      }
      /*获取函数地址和变量地址*/
      add=dlsym(lib_handle,"add");
      /*当动态链接库操作函数执行失败时,dlerror 可以返回出错信息,返回值为NULL 时表示操作函数执行成功。*/
      if ((error = dlerror()) != NULL){
        fprintf(stderr, "%s ", error);
        return 1;
      }
      int a=atoi(argv[1]);
      int b=atoi(argv[2]);
      printf("%d+%d=%d ",a,b,add(a,b));
      /*关闭指定句柄的动态链接库*/
      dlclose(lib_handle);
      return 0;
    }

    run.sh:

    #!/bin/bash
    
    gcc -o main main.c -ldl
    echo use the "+" 
    ./main 2 3 ../12/lib/libadd.so
    echo use the "&"
    ./main 2 3 ../13/lib/libadd.so

    15、检查系统环境是否满足依赖库要

    ldconfig -p 可以输出当前系统环境中缓冲加载的动态链接库。

    包含文件:lib/add.c、include/add.h、main.c、run.sh  

    main.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include "add.h"
    
    int main(int argc, char *argv[])
    {
        puts("this is a static libarary test ....");
        int a=atoi(argv[1]);    
        int b=atoi(argv[2]);
        int sum=add(a,b);
        printf("%d + %d = %d 
    ",a,b,sum);    
        return 0;
    }

    run.sh:

    #!/bin/bash
    
    #objdump -x main |grep NEEDED |sed -n '1,$p'|awk '{print $2}'>main1.txt
    
    ldconfig -p >main2.txt #打印出当前缓存文件所保存的所有共享库的名字.
    
    for line in `objdump -x main |grep NEEDED |sed -n '1,$p'|awk '{print $2}'`
    do
        cat main2.txt |grep -q "$line"
        if [ $? -eq 0 ];then
                echo -e "$line is met 
    "
        else
            echo -e "$line is not met 
    "
        fi
    done
  • 相关阅读:
    C++网络编程服务器select模型(参考)
    javascript中多维数组的使用
    C++ 网络编程 阻塞I/O模型并发回显服务器
    指针在javascript的使用方式
    C++网络编程之服务器编程
    C++网络编程之客户端程序
    Excel数据提取C++代码(仅供参考)
    SkipOut游戏实现代码
    C++读取Excel 精华
    MFC excel修改类
  • 原文地址:https://www.cnblogs.com/weijing24/p/4753648.html
Copyright © 2011-2022 走看看