Unix C (二)

13 篇文章 0 订阅
11 篇文章 1 订阅
库:便于团队开发程序,便于维护。    
    1、二进制形式目标模块的包。
            a.o   \
            b.o    ->  abc.a---库
            c.o   /
    2、库的分类:静态库和共享库(动态库)。
        静态库:扩展名.a。库中所封装的二进制代码,在链接阶段被复制到调用模块中。
        共享库:扩展名.so。库中所封装的二进制代码,在链接阶段不被复制到调用模块中,被嵌入到调用模块中的仅仅是调用函数在共享库中的相对地址。在运行时,根据这个地址动态的执行共享库代码。
    3、库的构建
        静态库
            1、编辑源程序:编写C程序及头文件。
            2、编译生成目标文件:gcc -c 所有C程序(除含有main函数的C源文件外),生成以.o为后缀名的目标文件。
            3、打包生成静态库文件:ar -r libxxx.a 所有以.o为后缀名的目标文件,生成libxxx.a的静态库,其中xxx为静态库名称。
                ar [参数] 静态库名 目标文件列表
                参数选项
                    -r       将目标文件插入倒静态库中,若已存在则更新
                    -q       将目标文件追加到静态库尾部
                    -d       从静态库中删除指定的目标文件
                    -t       列表显示静态库中的目标文件
                    -x       将静态库展开为目标文件(解压缩静态库)
            4、使用静态库:
                1、直接法:gcc 含有main函数的C源文件 libxxx.a
                2、参数法:gcc 含有main函数的C源文件 -lxxx -L静态库路径

                3、环境法:将export LIBRARY_PATH=$LIBRARY_PATH:.添加到~/.bash_profile文件中去

        实例:

                 有文件calc.c,calc.h,show.c,show.h和main.c。它们的代码如下所示:

                (1)calc.c的源代码:

                            #include "calc.h"

                            int add(int x, int y){
                                    return x + y;
                            }

                           int sub(int x, int y){
                                   return x - y;
                           }

                (2)calc,h的源代码:

                         #ifndef __CALC_H__
                         #define __CALC_H__
                         int add(int, int);
                         int sub(int, int);
                         #endif  //__CALC__

               (3)show.c的源代码:

                         #include <stdio.h>
                         #include "show.h"

                         void show(int x, char opr, int y, int z){
                                printf("%d %c %d = %d\n",x,opr,y,z);
                        }

                (4)show.h的源代码:

                         #ifndef __SHOW_H__
                         #define __SHOW_H__
                         void show(int, char, int, int);
                          #endif  //__SHOW_H__

                (5)main.c的源代码:

                         #include <stdio.h>
                         #include "show.h"
                         #include "calc.h"
 
                        int main(void){
                                printf("23 + 45 = %d\n",add(23,45));
                                printf("55 + 45 = %d\n",sub(55,45));
                               show(35,'+',41,35+41);
                               return 0;
                       }

                 (6)Makefile的内容:

                             result:main.c libmath.a
                                    gcc main.c libmath.a -o result
                             libmath.a:show.o calc.o
                                    ar -r libmath.a show.o calc.o
                             show.o calc.o:show.c calc.c
                                    gcc -c show.c calc.c
                             clean:
                                    rm *.o

                有了(1)(2)(3)(4)(5)的文件后,使用make指令执行Makefile脚本文件即可生成所需要的静态库libmath.a和可执行文件result。


        共享库
            1、编辑源程序:编写C程序及头文件
            2、编译生成目标模块:gcc -c -fpic 所有C程序(除含有main函数的C源文件外),生成以.o为后缀的目标文件。
                    (在-fpic中f是一个选项参数,pic是position independent code(位置无关码)的简写,可执行程序加载共享库时,可将其映射到其他地址空间的任何位置)
                    -fPIC    大模式,代码量大,速度慢,但是所有的平台都支持
                    -fpic    小模式,代码量少,速度快,但是只有少部分平台支持,是一种-fPIC的改进
            3、链接成共享库:gcc -shared 所有目标文件 -o  libxxx.so,生成以xxx为名的共享库文件libxxx.so。
            4、使用共享库:

                1、静态加载:所有的使用静态库的方法。运行时需要保证LD_LIBRARY_PATH环境变量中包含共享库所在的路径。

实例:

有文件calc.c,calc.h,show.c,show.h和main.c。它们的代码如下所示:

                (1)calc.c的源代码:

                            #include "calc.h"

                            int add(int x, int y){
                                    return x + y;
                            }

                           int sub(int x, int y){
                                   return x - y;
                           }

                (2)calc,h的源代码:

                         #ifndef __CALC_H__
                         #define __CALC_H__
                         int add(int, int);
                         int sub(int, int);
                         #endif  //__CALC__

               (3)show.c的源代码:

                         #include <stdio.h>
                         #include "show.h"

                         void show(int x, char opr, int y, int z){
                                printf("%d %c %d = %d\n",x,opr,y,z);
                        }

                (4)show.h的源代码:

                         #ifndef __SHOW_H__
                         #define __SHOW_H__
                         void show(int, char, int, int);
                          #endif  //__SHOW_H__

                (5)main.c的源代码:

                         #include <stdio.h>
                         #include "show.h"
                         #include "calc.h"
 
                        int main(void){
                                printf("23 + 45 = %d\n",add(23,45));
                                printf("55 + 45 = %d\n",sub(55,45));
                               show(35,'+',41,35+41);
                               return 0;
                       }

                 (6)Makefile文件内容:

                          result:main.c libmath.so
                                  gcc main.c libmath.so -o result
                          libmath.so:calc.o show.o
                                  gcc -shared  calc.o show.o -o libmath.so
                          calc.o show.o:calc.c show.c
                                  gcc -c -fpic calc.c show.c
                         clean:
                                  rm *.o

                        有了(1)(2)(3)(4)(5)的文件后,使用make指令执行Makefile脚本文件即可生成所需要的共享库libmath.so和可执行文件result。


                2、动态加载:包含#incude <dlfcn.h>预处理指令,编译时加上-ldl选项。
                    A、函数原型:void *dlopen(const char *filename, int flag);
                      filename:如果只给共享库文件名,则通过LD_LIBRARY_PATH环境变量搜索共享库。如果给共享库路径,则按路径加载,不使用环境变量。
                      flag:
                         RTLD_LAZY  延迟加载,什么时候使用共享库就什么时候加载。
                         RTLD_NOW     立即加载
                         成功返回共享库句柄,失败返回NULL.
                    B、获取函数地址
                        void *dlsym(void *handle/*共享库句柄*/, const char *symbool/*函数名*/);成功返回函数地址,失败返回NULL。
                    C、卸载共享库
                        int dlclose(void *handle/*共享库句柄*/);成功返回0,失败返回非零。
                    D、获取错误信息
                        char *dlerror(void);

                        返回错误信息字符串,没有错误信息返回NULL。

             实例:在已经有上面生成的共享库libmath.so的情况下,使用如下代码演示动态加载共享库:

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

                           typedef int (*PFUNC_CALC)(int, int);
                           typedef int (*PFUNC_SUB)(int, int);
                           typedef void (*PFUNC_SHOW)(int, char, int, int);

                           int main(void){
                                      //动态加载共享库
                                      void *handle = dlopen("./libmath.so",RTLD_NOW);     //寻找当前目录下的的动态库文件libmath.so,RTLD_NOW表示立即加载
                                       if(!handle){
                                                  printf("dlopen失败:%s\n",dlerror());     //获取错误信息
                                                  return -1;
                                        }

                                        //获取函数地址
                                        PFUNC_CALC add = (PFUNC_CALC)dlsym(handle,"add");
                                        if(!add){
                                                  printf("dlsym失败:%s\n",dlerror());
                                                  return -1;
                                         }
                                         PFUNC_SUB sub = (PFUNC_SUB)dlsym(handle,"sub");
                                         if(!sub){
                                                  printf("dlsym失败:%s\n",dlerror());
                                                  return -1;
                                          }
                                          PFUNC_SHOW show = (PFUNC_SHOW)dlsym(handle,"show");
                                          if(!show){
                                                  printf("dlsym失败:%s\n",dlerror());
                                                  return -1;
                                          }

                                           printf("23 + 45 = %d\n",add(23,45));
                                           printf("67 - 32 = %d\n",sub(67,32));
                                           show(34,'+',12,34+12);
                                           show(54,'-',21,54-21);

                                           //卸载共享库,dlclose数值减1
                                           dlclose(handle);

                                            return 0;
                                  }                



    4、查看可执行文件依赖的库文件:ldd 可执行文件,如ldd a.out。默认使用gcc编译程序时,使用的是动态库,可以使用ldd指令查看依赖的库;如果非要使用静态库编译,则需要加上-static选项。
    5、辅助工具:
        nm:查看目标文件,可执行文件,静态库,共享库中符号列表。
        ldd:查看可执行程序或共享库的动态依赖;但是如果采用动态加载共享库时,无法查看共享库的动态依赖。
        ldconfig:事先把共享库的路径信息写到/etc/ld.so.conf配置文件中,ldconfig根据该配置文件生成/etc/ld.so.cache缓冲文件,并将该缓冲文件读入内存,提高动态库的加载效率。系统启动时会自动执行ldconfig,若修改了共享库配置,则需要手动执行该程序,更新缓冲。(只在Linux平台中起作用)
        strip:通过删除符号表和调试信息给目标文件,可执行文件,库文件减肥。对可执行文件使用了strip命令,无法使用nm命令。
        objdump:对机器指令(可执行文件)做反汇编。比如:objdump a.out。(黑客常用指令)
        
C语言中的错误处理
    1、通过函数返回值表示错误
               1)、返回合法值表示成功,返回非法值表示失败。
               2)、返回有效指针表示成功,返回空指针(NULL/0xFFFFFFFF)表示失败。
               3)、返回0表示成功,返回-1表示失败。如果有需要返回给调用者的数据,可以通过指针型参数向其输出。
                4)、如果一个函数永远不会失败,也没有数据需要提供给调用者,可以没有返回值。比如exit函数。
    2、通过错误码获得函数失败的原因,需要包含头文件#include <errno.h> //extern int errno;
               1)、直接通过errno全局变量获取错误原因。
               2)、将errno转换成一个字符串(错误信息):
                        (1)、包含#include <string.h>头文件,使用char *strerror(int errnum)函数输出,printf("%s\n",strerror(errno));所有的错误码都不为0,没有错误,错误码为0.
                        (2)、包含#include <stdio.h>头文件,使用void perror(const char *s)函数输出,perror("malloc");
                        (3)、errno在函数执行成功的情况下不会被修改,因此不能以errno非零作为发生错误的判断依据,除非在函数调用前人为的将其复位为0(在线程中不适用).
                        (4)、errno是一个全局变量,其值随时都有可能发生改变,在线程中不安全。
            
            
环境变量
    1、环境表
        (1)、每个进程都会接收一张环境表,是一个以NULL指针结尾的字符指针数组。
        (2)、全局变量environ保存了环境表的首地址。
        (3)、main函数的第三个参数就是环境变量表的首地址。
    2、环境变量函数
        #include <stdlib.h>
        环境变量:<name>=<value>
        getenv    根据name获得value
        putenv    以<name>=<value>形式设置环境变量。如果name不存在,则会添加;存在则修改原来的value。
        setenv    根据name设置value,若name存在,根据参数决定是否覆盖原value。
        unsetenv  删除环境变量

        clearenv  清空环境变量,environ = NULL


实例:

        #include <stdio.h>
        #include <stdlib.h>

       extern char **environ;
       void printenv(){
                  printf("----环境变量----\n");
                  char **env;
                  for(env = environ; env && *env; env++){
                         printf("%s\n",*env);
                  }
                  printf("----------------\n");
         }

         int main(int argc, char *argv[], char *envp[]){
                   //添加环境变量
                  putenv("MYNAME=哈哈");
                  printenv();

                   //修改环境变量
                   putenv("MYNAME=呵呵");
                   printenv();

                    //获取环境变量
                   printf("%s\n",getenv("MYNAME"));
                   setenv("MYNAME","噢噢",0);  //如果标志为0,表示存在不修改,不存在添加
                   printenv();
                   setenv("MYNAME","嗯嗯",1);  //如果标志为1,表示存在就修改,不存在添加
                   printenv();

                   //删除环境变量
                   unsetenv("MYNAME");
                   printenv();

                   //清空环境变量
                   clearenv();
                   printenv();

                   printf("%p, %p\n",environ,envp);

                   return 0;
          }

        
        
内存管理
    层次结构,逐步深入(越来越底层):
        用户层
            (以下C++程序员要学的)
            Boost/ACE/MFC/...
            STL:内存分配器
            C++:new,delete,析构,构造
            
            (以下C程序员要学的)
            标准C中:malloc,calloc,realloc,free
            POSIX:brk,sbrk
            Linux:mmap,munmap
            
        系统层(以下嵌入式要学的)
            内核:kmalloc,vmalloc
            驱动:get_free_page
            硬件实现
进程映像
    1)、程序是保存在磁盘上的可执行文件。如a.out,ls,gcc,QQ.exe。
    2)、运行程序时,需要把磁盘上的可执行文件加载到内存中(由加载器loder完成,之后调用main函数产生进程),形成进程。
    3)、一个程序(文件)可以同时存在多个进程(内存)。
    4)、进程在内存空间中的布局就是进程映像。从低地址到高地址依次是:代码区(text)->数据区(data)->BSS区(bss)->堆区(heap)->栈区(stack)->命令行参数和环境变量区
        代码区(text):存放可执行指令,子面值常量,具有常属性且初始化的全局变量和静态变量。该区只读。
        数据区(data):存放不具常属性且初始化的全局和静态变量。
        BSS区(bss):存放未初始化的全局和静态变量。进程一加载此区即被清0.
        堆区(heap):存放动态内存分配的变量。
        栈区(stack):存放非静态局部变量。
        命令行参数和环境变量区:存放命令行参数和环境变量。
        

        注意:在堆区和栈区之间会留有一段空隙,一方面为堆和栈的增长预留空间,同时共享库、共享内存也会占用这个区域。


实例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

const int const_global = 10;    //常全局变量
int init_global = 10;           //初始化全局变量
int uninit_global;              //未初始化全局变量

int main(int argc, char *argv[]){
    //常静态变量
    const static int const_static = 10;

    static int init_static = 10;   //初始化静态变量
    static int uninit_static;      //未初始化静态变量
    const int const_local = 10;    //常局部变量
    int prev_local;                //前局部变量
    int next_local;                //后局部变量

    //前堆变量
    int *prev_heap = (int *)malloc(sizeof(int));
    //后堆变量
    int *next_heap = (int *)malloc(sizeof(int));

    const char *literal = "literal";       //子面值常量
    extern char **environ;         //环境变量

    printf("--------命令行参数和环境变量--------\n");
    printf("          环境变量:%p\n",environ);
    printf("        命令行参数:%p\n",argv);
    printf("---------------栈区-----------------\n");
    printf("        常局部变量:%p\n",&const_local);
    printf("        前局部变量:%p\n",&prev_local);
    printf("        后局部变量:%p\n",&next_local);
    printf("---------------堆区-----------------\n");
    printf("          后堆变量:%p\n",next_heap);
    printf("          前堆变量:%p\n",prev_heap);
    printf("---------------BSS区----------------\n");
    printf("未初始化的全局变量:%p\n",&uninit_global);
    printf("未初始化的静态变量:%p\n",&uninit_static);
    printf("--------------数据区----------------\n");
    printf("  初始化的静态变量:%p\n",&init_static);
    printf("  初始化的全局变量:%p\n",&init_global);
    printf("--------------代码区----------------\n");
    printf("        常静态变量:%p\n",&const_static);
    printf("        子面值常量:%p\n",literal);
    printf("        常全局变量:%p\n",&const_global);
    printf("              函数:%p\n",main);

    printf("PID = %d\n",getpid());
    getchar();

    return 0;
}

在64为Ubuntu Kylin 14.04下运行结果为:



知识补充:一个内存分页是4096(4k)个字节,它是一个内存映射单位。可以使用getpagesize()函数得到系统默认分页的大小。
        使用size命令可以看到可执行文件在代码区(text),数据区(data)和BSS区(bss)的大小。比如size a.out。
 
虚拟内存
    1、在32位系统中每个进程都有各自独立的4G字节的虚拟内存空间。
    2、虚拟内存中的地址只有映射到实际内存中才能使用。
    3、用户程序中使用的都是虚拟地址空间中的地址,永远无法直接访问实际物理内存地址。
    4、虚拟内存到物理内存的映射由操作系统动态维护。系统管理着内存分页表(内存映射表)。
    5、虚拟内存一方面保护了操作系统的安全,另一方面允许应用程序使用比物理内存更大的地址空间。4G的进程空间分为两部分:0~3G-1为用户空间;3G~4G-1为内核空间。
    6、用户空间中代码不能直接访问内核空间中的代码和数据,但是可以通过系统调用进入内核态,间接地与内核交互。
    7、对内存的越权访问,或访问未建立映射的虚拟内存,将会导致段错误。比如:int *p_num = NULL; *p = 100; printf("%d\n",*p);
    8、用户空间对应进程,进程一切换,用户空间随机变化。内核空间由操作系统内核使用,不会随进程切换而变换。内核空间由内核根据独立且唯一的页表init_mm.pgd进行映射,而用户空间的页表则每个进程一份。
    9、每个进程的内存空间完全独立,因此在不同进程之间交换虚拟内存的地址毫无意义。
    10、标准库的内存分配函数(malloc/calloc/realloc)需要用一套数据结构维护动态分配的内存,因此会分配比实际要求多12个字节的内存,用于存储某些控制信息。该信息一旦被破坏,将导致后续操作出现异常。如free会异常。
    11、虚拟内存到物理内存的映射以页(4096字节)为单位。通过malloc函数首次分配内存,至少映射33页。在"/proc/进程号/maps"文件中可以查看映射表。即使通过free函数,释放掉全部动态分配的内存,最初的33页仍然保留,直到进程退出这33页才会真正被解除映射。
    12、可以使用下面的函数得到系统默认的分页大小。
        #include <unistd.h>
        int getpagesize(vid);    //返回内存页的字节数
        
                char * pc = (char *)malloc(sizeof(char));
                             |<--------------------33页------------------------>|
                    ---- --+-------+-----------+-----------------------------------+---------------------------
                              |1字节|控制信息|                                                |
                    -------+------+------------+-----------------------------------+---------------------------

                        ^        ^           ^                    ^                                                           ^

赋值后果:  段错误   OK  后续错误  不稳定(被后面的赋值所覆盖)         段错误   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值