linux-动静态库

什么是动静态库?

  • 我们可能常听别人这样说过,拿别人的代码过来用一下。 那么我们怎么样拿到别人的代码呢?直接源码拷贝吗?或者说,如果我们使用别人的代码,需要别人提供什么呢?
  • 我们知道C/C++语言生成可执行程序有几个阶段,预处理,编译,汇编和链接。在汇编结束后会生成一个.o(linux环境下)文件,我们只需要给别人提供代码对应的.o文件和头文件,别人就可以使用我们的代码。但是如果有多个.o文件,一个一个给别人又很麻烦,所以就干脆把所有的.o文件打包起来,而这个包就叫做库。
  • 所以,可以简单的理解为:动静态库就是若干个.o文件的打包。

动静态库的区别

  • 我们拿代码来举例子:
// main.c
#include <stdio.h>
int main(){
	printf("hello world");
	return 0;
}
  • 上面是一段经典的代码。printf的声明在stdio头文件中。但是我们没有printf的实现。它的代码实现在一个库中,在命令行中使用ldd命令查看文件的依赖库文件。
    ldd

命名区别

  • 这里就要说一下库的命名规则。库的命名一般是前缀+库名字+。后缀+。版本号。上面的libc.so.6就是一个库名字。而lib就是前缀,表明它是一个库文件。c是它真正的名字。so表明它是动态库,而6代表它的版本号。

  • 而我们的静态库命名规则跟动态库一样,只不过静态库的后缀是。a,不是so。

  • 那么怎么证明这是一个动态库呢?使用file命令,查看库的属性即可。
    file

  • 从这里我们也可以看到linux下默认就是使用的动态链接,链接动态库。但是我们可以在编译的时候使用-static选项生成静态链接的文件。

  • 那么使用动态链接和静态链接有什么区别呢?如果你使用静态链接,那么编译器会将你想要的方法(比如printf)的实现全部拷贝过来,也就是说使得代码的整体量变得很大,在链接的时候就已经链接完成静态库,然后再将代码加载到内存。这带来的结果是可能导致代码大量冗余,因为有可能多个进程在调用printf,这样内存就可能出现多份一模一样的printf实现,这太蠢了。静态库所带来的好处就是程序的实现不依赖库文件,因为它依赖的部分已经被拷贝过来了,他是一个独立的文件。
    静态库

  • 而动态链接就不太一样。它只是把printf的位置给你,如果你想要执行printf,那么就把动态库对应部分加载到内存中,然后跳转到那部分执行就可以,而动态库的加载是在执行阶段加载的。知道需要用到动态库,才进行加载。

  • 而动态库是允许多个文件共享的,因为它不需要被拷贝,只需要跳转过去执行就可以。这样做的结果就是使用动态库的程序大小要小的多,但是依赖库文件。如果动态库损坏,那么所有依赖它的程序都完蛋。
    动态库

生成静态库

  • 我们知道库就是一堆的.o文件打包,那么我们需要先来一个.o文件,这很简单-c选项就能实现。
  • 我们先搞2个头文件add.h和sub.h,分别实现两个整数的加和减。然后在add.c和sub.c中实现。最后使用gcc的-c选项完成.o文件的生成。
    .o文件
  • 我们使用ar -rc(ar是一种归档工具,rc代表replace和creat,如果该库已经存在那么就替换,如果不存在就创建。)进行静态库的打包工作。我们可以使用ar -tv查看静态库包含的.o文件。

创建静态库
我们将库文件和头文件全部封装在一个目录文件中用来使用。我们尝试在main.c中使用静态库。
封装

使用add.h

  • 但是编译器给我们报了一个错误:
    error

  • 编译器说找不到头文件!这是为什么呢?执行一个文件需要它的路径和名字,我们有了add头文件的名字,因为我们将头文件封装在了目录中,编译器不知道它在哪。我们需要使用gcc的-I选项指定库的位置。I代表include,表示我们自定义库的路径,如果在默认路径找不到就去这里找。
    -I
    error

  • but,编译器又给我们报了一个错误。这次的错误是没有add的定义。add的定义在哪里呢?在库里,但是编译器找不到库。所以我们要告诉编译器库的名字和位置。

ret
ret

  • 但是你也许已经发现了,为什么stdio的头文件和c标准库不需要进行查找呢?也是需要的。因为gcc有自己的默认查找路径,你只需要将你的库和头文件也加入那些默认路径即可。但是不推荐这样做,会污染库池。

生成动态库

  • 同样的,我们需要先将.o文件打包。我们直接将头文件和库封装在一个目录中,方便使用。
    动态库

  • .o文件的生成需要带上-fPIC选项,这个选项大概表示动态库加载到内存中的地址是相对的,不是绝对地址。(我也不太懂,有大佬请评论留言,谢谢了。)

  • 静态库的生成需要ar工具,但是动态库直接使用gcc即可,带上-shared选项。

  • 我们还写了一个动态库的发布,使用make output即可一键生成一个封装库和对应头文件的目录文件。

  • 接下来我们来生成main文件。
    main

  • 这是生成main的Makefile。注意要去掉-static选项。跟静态库一样,需要编译器能够找到库的位置和名字,头文件的位置。

  • 我们志得意满,尝试去执行。
    main

  • 但是发现找不到该库文件。因为动态库实在执行的时候由操作系统加载到内存中的,我们的Makefile只是告诉了编译器该库的位置,但是没有告诉系统该库的位置。

  • 可以在环境变量LD_LIBRARY_PATH中加入该库的路径即可。
    成功
    发现指向成功。

  • 当然,我们还可以将我们的库拷贝到共享库的系统路径中,同样的,不太推荐罢了。

minishell中增加重定向的功能

  #include <stdio.h>                                                                                                             
  #include <string.h>    
  #include <unistd.h>    
  #include <stdlib.h>    
  #include <sys/types.h>    
  #include <sys/stat.h>    
  #include <fcntl.h>  
  #include <sys/wait.h>  
      
  #define SIZE 256    
  #define NUM  10    
      
  //输出重定向    
  void redirect(char arr[]){    
    char *p = arr;    
    int flag = 0; //用来记录 '>'的个数    
    char* file = NULL; // 用来记录'>'或者'>>'后面的文件名字    
    while(*p){    
      if(*p =='>'){ // 发现'>'    
          *p++ = '\0';    
          flag = 1;    
      if(*p == '>'){ // 如果发现'>',那么就将这个位置变成0,然后查看它的后面一个位置    
        *p++ = '\0';    
        flag = 2;    
      }    
      while(*p != '\0' && *p == ' '){ // 去掉文件名和>>之间的空格     
        ++p;    
      }    
      file = p;    
      while(*p != '\0' && *p != ' '){ // 去掉文件名后面的空格    
        ++p;    
    }                                                                                                                          
      *p = '\0'; //文件名最后一个字符是0
      break;
      }
      ++p; //如果没找到>,就往后遍历
    }
  
    //开始重定向
    if(flag == 0){  //没找到>
      return ;
    }
    else if(flag == 1){ //找到1个>,重定向
        int fd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0644);
        dup2(fd, 1);
    }
    else if(flag == 2){  //找到2个>,追加重定向
       int fd = open(file, O_WRONLY | O_APPEND | O_CREAT, 0644);
       dup2(fd, 1);
    }
  }
  
  int main(){
    const char cmd_line[] = "[temp@zzh-linux base_IO]#";
        char buf[SIZE] = {0};
        char *args[NUM] = {0};
  while(1){
        printf("%s",cmd_line);
        fgets(buf, SIZE, stdin); // 从标准输入获取数据
        buf[strlen(buf) - 1] = '\0'; // 去掉最后的空字符
  
        pid_t id = fork();
        if(id < 0){  //fork失败
          perror("fork error"); 
          exit(-1);
        }
        else if(id == 0){
          //child
      
        redirect(buf); //查看是否重定向
        char* p = strtok(buf, " "); //解析字符串
        int i = 0;
        while(p){
            args[i++] = p;
            p = strtok(NULL, " ");
        }
          execvp(args[0], args); //进程替换
          exit(1);  //如果退出码是1,证明替换失败
        }
        else{                                                                                                                    
          // parent
           int status = 0;
        pid_t ret = waitpid(id, &status, 0); //进程等等
          if(ret < 0){
            printf("wait error"); 
           exit(2); //如果退出码是2,证明等待失败
          }
          if(ret == id){
              printf("the return code : %d\n", (status >> 8) & 0xff); //打印退出码
          }
        }
    }
    return 0;
  }                           
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值