mmap gcc 静态库 动态库

回顾:          brk/sbrk
          int brk(void *p);
          void *sbrk(int);
          维护一个位置。brk/sbrk改变这个位置
                         brk改变绝对位置。
                         sbrk相对改变位置。
         
          补充:全新的类型。
                         永远记住:c的基本类型就:整数(1,2,4),小数(4,8)
                         unsigned signed
                         所有全新类型都是使用typedef重新定义。
                         新的类型的C的原型。
                         类型重定义的好处:(程序要先想好定义的类型,要不到最后改类型是相当痛苦的)
                                   1.维护方便
                                   2.移植
                                   3.容易理解
1. 映射虚拟内存 mmap
          没有任何额外维护数据的内存分配。
          mmap(分配)/munmap(释放)  映射文件或设备到内存(虚拟内存)(c程序员在用户空间玩,物理内存是驱动,嵌入式玩的)
          1.函数说明 返回首地址

          void *mmap(
               void *start,//指定映射的虚拟地址(值为 0由系统指定开始位置)
               size_t length,//映射空间大小 一个页(pagesize)的倍数,比如是4,为这4个字节分配一个页4k。
                                   //分配大小的公式,(length/4k +1)*4k
               int prot, //保护权限 读写执行  PROT_NONE(无任何权限) | PROT_READ PROT_WRITE PROT_EXEC
               int flags,//映射方式
               int fd,//文件描述符号
               offset_t off //文件中的映射开始位置(必须是pagesize的倍数)
               );
              
               映射方式:
                         内存映射:匿名映射。 MAP_ANONYMOUS   MAP_SHARED   MAP_PRIVATE(二选一)
                         文件映射:映射到某个文件或设备,网络设备
                                               只有文件映射最后两个参数有效。 文件映射以后讲。
                                        
         int munmap(void *start, size_t length); 释放也是以页为基本单位。  释放的大小(length/4k +1)*4k              
          2.案例      
                    struct c
                     { char a;
                       short b;
                     }
                     
                     结构对齐与补齐,float和double都不存在对齐与补齐,提高查找速度
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
main()
{
int *p=mmap(  //也可以写成返回char*指针,指针类型只是说明指针运算时移动的大小。实际这里返回的是void*。
NULL,
getpagesize(),  //得到一页大小
PROT_READ,  //有写权限就有读权限,有读权限不能有写权限,权限不够产生段错误
MAP_ANONYMOUS|MAP_SHARED,
0,0 //对于匿名映射这两个没有意义
);
 bzero(p, getpagesize());
*p=20;
*(p+1)=30;
*(p+2)=40;

printf("%d\n",p[2]); //输出40   等价于*(p+2)
munmap(p,4096);
}
举例:
main()
{
 char *p=mmap(  NULL, 40, PROT_READ, MAP_ANONYMOUS|MAP_SHARED, 0,0 );  //等价于 char *p=sbrk(40);
 bzero(p, 40);
 time_t tt=time(0);   //取当前时间 man 2 time,time是系统函数
 struct tm *t=localtime(&tt); //时间值格式化。
 sprintf( p, "%04d年%02d月%2d日"
" "
"%02d:%02d:%02d",
t->tm_year+1900, t->tm_mon+1, t->tm_today,
t->tm_hour, t->tm_min, t->tm_sec);
printf("%s\n", p);
munmap(p,40);  //等价于brk(p);                                                                                          
}
 
          3.总结
选择什么样的内存管理方法?
          智能指针(指针池)
          STL              
          new         
          malloc (小而多数据)
          brk/sbrk (同类型的大块数据,动态移动指针)
          mmap/munmap(控制内存访问/使用文件映射/控制内存共享)         
          4.应用    
2.编程工具与动态库
     2.1.gcc
               -o  输出文件名
               -O  -O1 -O2 -O3 -O0  编译优化  例子:gcc map.c -omain -O3
                     ➔➔➔向右,文件越大
               -g  -g0 -g1 -g2 -g3  产生调试信息详细度的等级
                     ➔➔➔向右,文件越大
               -W  all/error  
               -Wall  显示所有警告
               -Werror       把警告当错误
               -w  关闭警告
               -E  预编译
               -S  编译器编译为汇编文件

               -c  只变为.o目标文件,不连接
               -D     在命令行定义宏。后面有例子。 -D宏名字[=宏替换值]有很好的用途可以传参数
                                                                      -Dparam=123
                            在代码中定义宏
                            在命令行定义宏

                             举例:    预处理指令:#line 指定行号  #error 指定人为错误  #warning  人为的在代码段里加警告,运行显示
                              int main()
                              {
                                   #line 1000 //指定这行是1000行,当输出错误时,以这行为标准,输出相对行数。
                                   #ifdef DEBUG
                                       #warning "hello"

                                   #else
                                      #error "wrong"
                                   #endif
                                   printf("hello\n");
                                   return 1;
                                }


                              采用宏的这种方式让代码执行不同路径
                               gcc main.c -omain -DDEBUG
                               gcc main.c -omain

               -x  指定编译的语言类型
                          c++
                          c
                          assembler     编译.S 汇编文件
                          none 根据扩展名,调用相应编译
               -std=c89
                       c99  程序语言标准 restrict
              
          编译过程:-E  -S -c   最后是自动调用连接器
          连接器 ld gcc后面自动调用连接器,gcc并不做连接,gcc只是编译器。
          补充:
                    .c
                    .cpp  .CC   c++文件
                    .h   .hpp     头文件
                    .o 目标文件
                    .a 静态库文件,也叫归档文件
                    .so 动态库文件,也叫共享目标文件
                    .i    预编译文件
                    .s   汇编文件  gcc可以编译汇编文件   
              
-D 例子:
int printf(const char*,…);  //变参函数,__fastcall不支持变参
main()
{
printf("%d\n",NUM);
}
gcc gcc.c -omain 这样编译出错
gcc gcc.c -omain -DNUM=56 这样就没错了

2.2 restrict修饰符

c99中的restrict,c98中无。是将指针指向的变量放到寄存器中,函数再次访问时就不到指针指向的外面取值了。优化函数参数的寻找过程,优化速度。
int add(int * restrict a, int  *restrict b)
{
return (*a)+(*b);
}
main()
{
for(int i=0;i<100;i++)
{
printf("%d\n",i);
}
}
3.静态库的编译
          1.编译过程(*.a   achieve)
               1.1.编译成目标文件(.o)
                         -static  可选
                         gcc -c -static 代码文件.c
                        
               1.2.归档成静态库
                         ar工具
                                   ar   -p 打印目标库的成员
                                         -r  两个参数进行归档,ar -r 静态库文件  被归档的文件         
                                         -t  显示归档文件的内容
                                         
                         nm工具(察看函数符号表)
                              nm 静态库或者动态库或者目标文件或者执行文件
                         
               1.3.使用静态库
                         gcc 代码  静态库     gcc  *.c  *.a  -omain             

          使用静态库完成如下程序:
                         输入一个菱形半径,打印菱形。
                         输入整数封装成IOTool
                         菱形的打印封装成Graphics
                         计划:
                                   1.实现输入
                                   2.实现菱形
                                   3.编译静态库
                                   4.调用静态库
          总结:
                    1.什么是库?
                              函数等代码封装的二进制已经编译的归档文件
                    2.ar归档工具
                    3.采用库德方式管理代码优点:
                              容易组织代码
                              复用
                              保护代码版权
                    4.静态库的静态的含义:
                              编译好的程序运行的时候不依赖库。
                              库作为程序的一部分编译连接。
                    5.静态库本质:
                              就是目标文件集合(归档)
                    6.-static可选
                   
           2.库的规范与约定                                       
                         库命名规则:
                                   lib库名.a.主版本号.副版本号.批号
                                   lib库名.a
                         库使用规则
                                   -l 库名
                                   -L 库所在目录  
                         利用别人的库必须指定库的所在目录和库名,编译器不会自己给你找,
                         比如写个新的主程序main.c利用下面的ku.a。  gcc main.c  -L. -lku  -omain
                                                                                                             指定当前目录                    
              例子:
                         ku1.c
                               int add(int a,int b)  {   return a+b;    }
                        ku2.c
                                int sub(int a,int b) {   return a-b;  }
                        callku.c
                                 main() {
                                             int r=add(45,55);
                                             int s=sub(100,45);
                                        }
               gcc -static  -c ku1.c
               gcc -static  -c ku2.c                                         
               生成:ku1.o  ku2.o
               开始归档: ar  -r ku.a ku1.o ku2.o
                nm ku.a 输出归档文件和归档文件里的函数
               使用静态库  gcc callku.c ku.a -omain  就可以执行main了。
 gcc ku.a callku.c  -omain 顺序错了就编译不过去了,主函数要在最前
如果将sub函数定义为静态函数:static int sub(int a, int b){return a-b; }重新编译出现:
 错误是找不到sub函数。
/tmp/ccKV5N0k.o: In function `main':
curse.c:(.text+0x38): undefined reference to `sub'


http://blog.sina.com.cn/s/blog_621841df0100mhk4.html
4.动态库的编译
               1.什么是动态库?(共享库)
                         动态库是可以执行,静态库不能执行  (文件格式不同)
                         但动态库没有main,不能独立执行。
                         动态库不会连接成程序的一部分。静态库会连接成程序的一部分。
                         程序执行的时候,必须需要动态库文件。程序运行不依赖静态库,可以编译完删除。
               2.工具
                         ldd  察看程序需要调用的动态库
                                   libc.so.6 c语言的标准库
                                   ldd 只能察看可执行文件.
                                   可执行文件格式ELF,黑客需要解析文件格式。
                                   黑客研究linux下ELF格式,windows下的PE格式。



Microsoft 库管理器 (LIB.exe) 创建和管理通用对象文件格式 (COFF) 对象文件库。LIB 还可用于创建导出文件和引用导出定义的导入库。          
                              readelf -h 察看执行程序头.  黑客必备
                              nm   察看库中的函数符号
               3.动态库的编译
                         3.1.编译
                                   -c -fpic(可选)     编译必须每个文件单独编译,系统是这么做的
                                        编译为与代码位置无关的文件格式
                         3.2.连接
                                   -shared
                                   gcc ✖.o ✖.o  -shared -olibku.so
                         3.3 合并步骤
                                   gcc -shared -fpic ✖.c 。。。✖.c  -olibku.so
                                            这些.c文件直接可以有相互调用关系,不需要指明头文件或声明,他自己就可以通过函数 表定位到。
                                              
               4.使用动态库
                         gcc 代码 动态库文件名
                         gcc 代码   -l库名 -L动态库所在路径 -o可执行文件
                         虽然最后编译可执行文件成功,但是运行时,可执行文件仍然找不到动态库,不能运行,引出下面4.1的问题
                        
               标准命名规则:
                         lib库名.so
                         lib库名.a
                         c调用时直接调用,c++需要声明下,因为c++是强类型语言
                         -l 库名  -L 库所在路径
                                  
               问题:
                         4.1.执行程序怎么加载动态库?
                         4.2.动态库没有作为执行程序的一部分,为什么连接需要指定动态库以及目录?
                                   连接器需要确定函数在动态库的中的位置
                         函数名被编译后就不存在了,作为符号在符号表里存在,函数就转化为地址,连接器在连接的时候要确认函数的位置。
                         连接器需要确定函数在动态库中的位置
                                  
               动态库的加载:
                         1.找到动态库
                         2.加载动态库到系统内存空间
                         3.映射到用户的内存空间
               系统对动态库查找规则:(不象windows找当前路径下的,linux默认不会找当前路径下的)
                         /lib
                         /usr/lib
                         到环境变量LD_LIBRARY_PATH指定的路径中查找(使用冒号分割各个路径)
                         export LD_LIBRARY_PATH=.:~:..  //这里将当前路径,主路径,上级路径都加载到了环境变量
               缓冲机制:
                         把/lib:/usr/lib:LD_LIBRARY_PATH加载到缓冲。通过查找缓冲能加快定位so文件。
                         /sbin/ldconfig -v 刷新缓冲中so的搜索数据
                         ldconfig -nv /lib 将/lib下的文件都加载到缓冲中
                         (网上查找:为了让动态链接库为系统所共享,还需运行动态链接库的管理命令--ldconfig
                          ldconfig 命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,
                          搜索出可共享的动态 链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.
                          缓存文件默认为 /etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.)
               设置环境变量
                         系统配置文件 /etc/profile
                         用户环境变量 .bash_profile文件   后用source .bash_profile
                         在命令行直接设置(只对当前端口有效)比如设置当前路径为环境 export LD_LIBRARY_PATH=.
                          
     综合应用:
               输入两个数,计算两个数的和。
               要求:输入与计算两个的数的和封装成动态库调用
5. 动态库加载的原理                                                           
     动态库优点:二进制封装,程序小,反复调用。     
     linux/unix windows 全部封装了动态库
     libc.so库,这是标c的库
    unistd.h  unix的标准头文件
     libpthread.so 就是多线程库
         动态库中函数的查找已经封装成库libdl.so
          dlopen函数    打开一个动态库文件(有两种方式)
          dlsym函数     在通过名字在打开的动态库文件找一个函数
          dlclose函数   关闭动态库
          //dlerror函数   返回错误
          使用动态库的第2种方法由程序自己加载动态库,不需要环境变量了:可以查看man dlopen
将以上静态库提到的ku1.c  ku2.c 生成动态库libfun.so
#include <stdlib.h>
#include <dlfcn.h>  //必须引入的libdl.so的头文件
main()
{
void *handle=dlopen("./libfun.so", RTLD_LAZY); //RTLD  <==> runtime load
                                    动态库的路径 打开文件方式,RTLD_NOW是马上加载  RTLD_LAZY是运行时再加载
if(handle == NULL)  
{
 printf("%s\n",dlerror());
    exit(-1); //是标准c库里加载头文件<stdlib.h>
}
void(*fun)(int, int)=(void(*)(int, int))dlsym(handle,"and");
//在打开的文件中根据函数名找到函数地址,返回函数指针。c是弱类型语言可以去掉粉色部分,不做强制转换。
//sym<==>symbol符号
fun(5, 5);
dlclose(handle);
}

函数指针
          定义函数指针做法与上面的程序等价:
                              typedef void(*fun)(int, int);
                              fun dd=(fun)dlsym(handle,"and");
                              dd(5, 5);
                              
          若此时改为一个参数,实际函数声明的是两个参数:     
                              typedef void(*fun)(int);
                              fun dd=(fun)dlsym(handle,"and");
                              dd(5);
           gcc可以编译并运行,但结果未定。
           g++编译函数名的处理,函数名最后变为: _Z+函数名长度+函数名+函数所有参数类型首字母,用nm察看时就看不到原来的函数名而是编译器处理后的函数名(不同编译器函数名变异处理不同),这也是重载的处理,底层是没有什么重载的。
          所以用g++编译动态库的话,你调用dlsym(handle,"and"); 是找不到and函数的,因为名字已经变了,得用
          dlsym(handle,"_Z3andii")才能正确。一般不用g++编译动态库。

总结:
          1.编译连接动态库
          2.使用动态库的两种方法
          3.怎么配置让程序调用动态库
               1打开读取动态库文件,加载到内存
               2在内存中根据文件名查找函数的地址 (根据符号表(函数表)找到函数)
               3执行调用函数
               这些过程涉及到的4个函数被封装为动态库libdl.so
          4.掌握某些工具的使用(补充在本页最后):
                                   nm ldd lddconfig
                                   objdump
                                   strip  去掉多余的信息.
6.工具make的使用与makefile脚本
     背景:
               make 编译脚本解释
               编译脚本Makefile
               make -f  脚本文件  目标 (在windows里叫nmake)
                    举例: make -f demo.mk demo
     脚本文件
               1.文本文件
               2.基本构成语法:
                         基本单位目标target
                         目标名:依赖目标 (不是必须要依赖文件,依赖目标之间用空格隔开)
                              \t目标指令   #\t表示tab键,这里必须不能是空格
                              \t目标指令
makefile文件
compile: //以下各行前面都必须是tab键
     @gcc -c -fpic input.c      #@是表示不输出该条的打印到终端
     @gcc -c -fpic primer.c
lnk:compile
     gcc -shared -olibdemo.so input.o primer.o
demo:lnk  //形成一个依赖的调用,去除lnk则不能执行前面的语句
     gcc demo.c -ldemo -L. -omain
                                                  可执行文件的名字
 综合案例:
          1.输入一个整数判定是否素数
               a.input.c              
               primer.c              
               demo.c
               b.make脚本
               c.使用make
               d.执行程序
作业:
           1、输入一个整数,打印这个数的Goldbach分解
                    所有大于等于6的偶数都可以分解成两个奇素数的和。
                    要求:
                              封装成共享库。
                              使用make脚本编译

primer.c
decompose.c
int decompose(int a, int b[])
{
     if(a<=6 || a%2 != 0) return 0;
     if(isprimer(a-2) && isprimer(2))
     {
               b[0]=2;
               b[1]=a-2;
     }
     for(int i=3; i<a/2+1; i=i+2 )
     {
          if(isprimer(a-i) && isprimer(i))
          {
               b[0]=i;
               b[1]=a-i;
          }    
      }
     return 1;
     
}               
               2、已知"tom jack rose",写一个程序,统计单词个数
                         并且打印每个单词.                        
                         要求:使用共享库封装。
                                        使用make脚本
                                                 
               3、输出菜单,选择:1打印菱形,2打印矩形,
                              选择1     , 则输出菱形(半径4)
                              选择2 ,则输出矩形(半径4*4)              
                                       
 几种工具总结:
          ldconfig  查看环境变量缓冲中的内容
          /sbin/ldconfig -v 实际是刷新缓冲中so的搜索路径
          ldd          
          nm
          strip  给文件瘦身  strip libfun.so 文件变小
                   文件小的原因是strip去除指定目标文件与静态库、动态库中的调试信息 :strip  ch01_1.o  libmy.a
去掉以后就变成不可调试。无法再使用gdb进行调试。
          objdump 显示目标文件内部结构
                    显示目标文件的内部结构:objdump –f –h –EB  hello.o
               
                        Objdump –dS 可执行文件  转为汇编:
                    如:objdump –dS demo.o
                                 Objdump –dS main

          readelf   显示elf文件内部结构
                            
 登录ftp服务器:
          ftp ip 地址或者域名
          输入帐号与口令
          用!dir  !ls !pwd加!符号执行本地命令                                                 
          lcd 目录   切换本地下载目录。上传文件目录
          cd 目录   切换服务器的目录
          ls    查看服务器的内容
          get  文件   下载文件
          put  文件  上传文件
          mget 多个文件
          mput 多个文件
          quit  关闭                         
                                  
         zip 压缩文件名 被压缩文件列表
         unzip  -d 解压后的存放目录 压缩文件名

补充printf的格式
     printf("格式字符串“, 参数, 。。。。。。)
     1.转换指示符号
          d i 有符号的整数
          u o x X 无符号整数
          e E科学记数法小数
          f F小数
          g G小数(自动根据长度采用科学法)
          c 字符
          s 字符串
          p 指针
          n输出长度
          m输出错误 perror("error:%m");  错误errno是个全局变量
     2. 格式标记
          0 填充0
          - 左对齐
          + 输出正负号
     3.宽度
          数字
          *
     4.精度
          必须使用 . 符号开头
     5.输出的数据长度
           hh  一个字节
           h    两个字节
           l     8字节  long int   double 8字节, float 4字节
           ll    16字节 long long int
           举例:
                  double  d;
            scanf("%f", &d);   ⤬        
            printf("%f\n", d);
               结果: 输入 34.56  输出0.00000
               原因是double占8个字节,输入格式只占4字节。
               double  d;
               scanf("%lf", &d);           
               printf("%llf\n", d);  ⤬   
               结果: 输入 34.56  输出-0.00000
               原因是输入的格式是8个字节,但输出格式是16字节。
               double  d;
               scanf("%llf", &d);  ⤬            
               printf("%llf\n", d);
               结果: 输入 34.56  输出-0.00000
               原因格式不符。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值