【Linux/gcc】C/C++——头文件和库

目录

一、头文件

1、gcc查找头文件

2、gcc与g++的区别

二、库

1、简介

2、实验

2.1、静态库的编写

2.2、动态库的编写

2.3、库的使用

2.4、链接库


一、头文件

  • 头文件存在的目的是为了把接口和实现分离,便于多文件编程的组织,例如:
    • 在多文件的项目中,把函数声明都集中到若干头文件中,在源文件中引用它们,便于跨文件的函数调用。
    • 在使用库的时候,首先需要在源代码中引用头文件,然后在链接步骤中链接需要的库文件。
    • 在提供库的同时,也需要提供库的使用接口(头文件),通过头文件中的类和函数声明,用户可以知道如何使用该库。

1、gcc查找头文件

  • gcc在编译过程中的预处理环节需要处理 include的头文件,那它是如何找到头文件的呢?
  • gcc有专门的查找头文件的选项:-I path,path是一个路径。如果使用多个路径需要加多个 -I,例如:
    • gcc hello.c -I mudir1 -I mydir2
  • 如果直接写完整路径加文件名,就不存在查找文件的问题。但如果使用的是不完整的路径加文件名,则存在查找顺序的问题。 
  • 在 include语句中,双引号和尖括号引用头文件的查找顺序有一点区别:
    • ""双引号 include的查找顺序:
      • 使用 #include的源文件所在的路径。
      • -I指定的路径。
      • 环境变量 CPATH、C_INCLUDE_PATH或 CPLUS_INCLUDE_PATH包含的路径。
      • 内定路径。
    • <>尖括号 include的查找顺序:
      • -I指定的路径。
      • 环境变量CPATH、C_INCLUDE_PATH或CPLUS_INCLUDE_PATH包含的路径。
      • 内定路径。
    • 【注】查找顺序是重要的,如果两个路径下有同名的头文件,gcc会使用先找到的那个,这很可能导致 bug。
      • 环境变量 CPATH被 c/c++共用,C_INCLUDE_PATH或 CPLUS_INCLUDE_PATH只在处理相应的文件时使用。
      • 内定路径是由 gcc和平台决定的,我们无法改变。
  • 可以使用下面的命令查看当前 gcc 使用的头文件搜索路径(环境变量和内定路径):
    • # c
      `gcc -print-prog-name=cc1` -v
      # c++
      `gcc -print-prog-name=cc1plus` -v
      #  -v选项,会打印编译过程的详细信息。
  • 特别注意,include通常是递归的,即一个头文件可能还包括另一个头文件,例如:
    • A include B
      B include C
      C include D
    • 为了将内容完整地拷贝到 A中,需要首先完成 B的头文件查找,查找顺序是:
      • A所在的位置
      • 其它路径
    • 然后完成 C文件的查找,查找顺序是:
      • B所在的位置
      • A所在的位置
      • 其它路径
    • 然后完成 D文件的查找,查找顺序是:
      • C所在的位置
      • B所在的位置
      • A所在的位置
      • 其它路径

2、gcc与g++的区别

  • 在处理c/c++文件时,我们可以分别采用gcc或g++命令,两者效果类似,可以理解为g++命令在gcc命令上又针对c++进行了一层封装。因此它们的区别主要是对c/c++文件的处理细节,例如:
    • g++对于.c/.cpp结尾的文件全都默认当作c++文件处理。
    • gcc对于.c视作c文件,对于.cpp 视作c++文件处理。
    • 对于STL标准库,如果使用 g++会自动链接进来,如果使用gcc则需要加参数-lstdc++显式地完成链接,并且可能有细节差异。
    • 对于预定义宏,两者支持的宏不完全一样。
  • 下述案例展示了gcc和g++分别对c++文件处理时的差异。
    • 创建c++文件:
      • mkdir project02  # 创建存放c++文件的文件夹
        cd project02  # 进入文件夹
        vim hello.cpp  # 创建并编写c++文件
    • 编写hello.cpp: 
      • 按 i,进入插入模式,编辑文件。
      • 【Linux/gcc】C/C++——头文件和库_第1张图片
      • 编辑完毕后按回退键,输入 :wq,保存文件。
    • 使用gcc编译
      • gcc hello.cpp
        # 会报错iostream cout各种未定义
        
        gcc hello.cpp -lstdc++ -o hello
        # 链接STL后可以顺利进行
        
        ./hello
        #  执行目标文件
      • 【Linux/gcc】C/C++——头文件和库_第2张图片
    • 使用g++编译
      • g++ hello.cpp -o hello  # 编译正常
        
        ./hello  # 执行目标文件

二、库

1、简介

  • 库的存在目的:
    • 为了把一些基础功能封装,在链接阶段或者运行阶段直接使用,方便功能复用。
    • 为了隐藏代码,使用二进制的库文件不需要直接暴露源代码,方便发布产品的同时隐藏功能实现的细节。
  • 根据库是否直接包含到可执行文件中,在运行期间是否需要,可以分为静态库和动态库。
  • 根据平台和编译器的不同,c/c++的静态库和动态库处理在windows+msvc,MinGW+gcc和Linux+gcc三种情景下各不相同,windows对动态库非常不友好。本文将基于Linux+gcc,实现基本的静态库和动态库的编写。
  • 【注】库文件的习惯命名。
    • 在 Linux 中,静态库通常名称为 libxxx.a,动态库通常名称为 libxxx.so.x.y.z。
    • 在 windows 中,静态库通常名称为 xxx.lib,动态库通常名称为 xxx.dll(DLL辅助部分 xxx.lib)。
    • 在mingw中,静态库通常名称为 libxxx.a,动态库通常名称为libxxx.dll(DLL辅助部分libxxx.dll.a)。

2、实验

  • 首先创建好目录结构。
    • 【Linux/gcc】C/C++——头文件和库_第3张图片
    • |-bin
      |-include
          foo.h
      |-lib
          |-all
          |-shared
          |-static
      |-src
          foo1.cpp
          foo2.cpp
          main.cpp
  • 创建一个名为foo的库,头文件为foo.h。
  • 【注】命令:cd.. 返回上一级目录。
  • 创建两个源文件,分别实现静态库和动态库。
    • foo1.cpp
      • 【Linux/gcc】C/C++——头文件和库_第4张图片
    • foo2.cpp
      • 【Linux/gcc】C/C++——头文件和库_第5张图片
    • 这里foo1.cpp和foo2.cpp都分别对头文件foo.h中的两个函数getnum()、sayhello()进行了实现。
  • 创建main.cpp引用头文件foo.h,用多种方式调用foo库。
    • 【Linux/gcc】C/C++——头文件和库_第6张图片
  • 实验将在 lib/static中存在静态库,在 lib/shared中存放动态库,在 lib/all存放动态库和静态库。
2.1、静态库的编写
  • 在 Linux中使用 gcc编写一个静态库,需要编译和打包两步。
    • # 编译获得目标文件
      g++ -c src/foo1.cpp -I include -o lib/static/foo1.o
      
      # 打包得到.a静态库,注意前面是输出文件,后面是输入文件(可以有多个输入)
      ar -crv lib/static/libfoo.a lib/static/foo1.o
      
      # 从lib/static复制一份到lib/all
      cp lib/static/libfoo.a lib/all/libfoo.a
2.2、动态库的编写
  • 在 Linux中使用 gcc编写一个动态库,也需要两步。
    • # 注意在生成目标文件的时候需要加-fPIC选项,生成位置无关的目标文件
      g++ -I include -fPIC -c src/foo2.cpp -o lib/shared/foo2.o
      # 注意得到动态库时,需要加-shared选项
      g++ -shared lib/shared/foo2.o -o lib/shared/libfoo.so
      
      # 两步可以合并为一步,效果一样
      g++ -fPIC -shared src/foo2.cpp -I include -o lib/shared/libfoo.so
      
      # 从lib/shared复制一份到lib/all
      cp lib/shared/libfoo.so lib/all/libfoo.so
  • 【注】从目标文件到动态库文件,必须使用 -shared选项。
    • 从源文件到目标文件,建议使用 -fPIC选项,可以生成位置无关的代码,此时动态库在内存中只需要加载一次,多个程序可以共同并且同时使用;否则只能相当于代码拷贝的方式,多个程序需要多次加载同一个动态库到内存中使用。
    • 相比于静态库,动态库生成之后的存放位置是很重要的,因为可执行程序在运行时,还需要找到动态库并一起加载到内存中。
    • 由于动态库通常有 -fPIC,而静态库通常没有,因此动态库中如果想要链接一个静态库会报错,解决办法可以是把静态库也添加选项,改为生成位置无关的代码。
  • 【Linux/gcc】C/C++——头文件和库_第7张图片 
2.3、库的使用
  • 【注】库的名称需要满足一定的命名规范,对于静态库通常命名为 libxxx.a,对于动态库的名称通常为 libxxx.so.x.y.z ,还需要附带动态库的版本号。
    • 在遵循这种命名规范的前提下,gcc 可以使用 -lxxx选项链接相应的库,如果命名不规范则需要给出库文件的完整名称。
  • gcc 相应的选项
    • -l:链接指定的库,例如 -lxxx为链接名为 xxx的库,可能是静态也可能是动态链接。
    • -L:指出在编译时,库文件的优先查找目录,例如 -Llib会在 ./lib目录下查找。缺省目录时 -L代表当前目录,-L只能后接一个目录,多个目录使用多个-L选项。
    • -Wl, rpath=your_dir:指出在运行时,动态库文件的优先查找目录,可以接多个目录,用 :分隔不同目录,类似于环境变量中的路径分隔。(在 gcc 中,-Wl之类的选项会传递给链接程序,-Wa之类的选项会传递给汇编程序)
  • 【注】选项 -l不能过早出现,因为 gcc从左到右检索,记住在源文件中没找到的符号,在对应的库当中查找并提取,对于没用到的部分,静态库可能丢弃剔除。因此库的链接选项最好在用到的源代码之后,在库的查找路径之后。
  • 在这里要区分两个概念:编译时和运行时。
    • 编译时,gcc 需要找到库文件,然后完成二进制文件的生成,这对于静态链接和动态链接都是必须的。
    • 运行时,可执行文件需要找到它需要的动态库文件,然后一起加载执行。由于静态链接的库文件在运行时是不需要的,因此我们只有使用动态链接时,才有在运行时找到动态库文件的需求。
  • -L选项告诉 gcc编译器,编译时在哪找到库文件,但这个信息通常不会留在可执行文件上。如果使用 -Wl, rpath=选项,则这个信息会留存在可执行文件上,明白在运行时首先去哪找到动态库文件。这两个路径的区分是必要的,因为编译时的开发环境和运行时的生产环境,动态库存放的位置很可能是不一样的。
  • 注意到 -lxxx选项并没有告诉 gcc用静态链接还是动态链接,使用哪种链接的选择逻辑如下:
    • 如果 gcc只找到了静态库版本,则使用静态链接。
    • 如果 gcc只找到了动态库版本,则使用动态链接。
    • 如果 gcc在同一目录下同时找到了静态库版本和动态库版本,则默认使用动态链接。默认行为可以被下列选项所更改:
      • -static这个选项强制让所有的链接都使用静态链接,这很可能会在链接中报错,因为 glibc,libstdc++等最基础的库,通常在系统中只有动态库版本,没有静态库版本,并且这基础库并不适合静态链接。
      • 更精细的强制链接选项:-Wl, -Bstatic指示跟在后面的 -l选项都使用静态链接,-Wl, -Bdynamic指示跟在后面的 -l选项都使用动态链接。注意这种用法需要保证在最后生效的是 -Wl, -Bdynamic,这是为了最后动态链接 glibc等基础库而准备的。
  • 【注】对于静态库,其实并不是必须要以 -lxxx格式调用它,也可以简单粗暴地直接把 .o或者 .a文件和源文件放在一起编译。
2.4、链接库
  • 先将 main.cpp转换为目标文件。 
    • g++ -c src/main.cpp -Iinclude -o src/main.o
  • 设置 MY_LD_LIBRARY_PATH环境变量,它要要写入 rpath中,这是最优先的查找顺序。(不设置的话,执行动态链接文件会找不到库文件的位置)
    • export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/用户名/c++/project02/libtest/lib/all
  • 在 lib/static只找到静态库时,使用静态链接:
    • g++ -Wl,-rpath=$MY_LD_LIBRARY_PATH src/main.o -Llib/static -lfoo -o test_static_1
      
      g++ -Wl,-rpath=$MY_LD_LIBRARY_PATH src/main.o lib/static/libfoo.a -o test_static_2
  • 在 lib/shared只找到动态库时,使用动态链接:
    • g++ -Wl,-rpath=$MY_LD_LIBRARY_PATH src/main.o -Llib/shared -lfoo -o test_shared_1
  • 在 lib/all同时找到两种库的时候,默认动态链接,可以使用选项改成静态链接:
    • g++ -Wl,-rpath=$MY_LD_LIBRARY_PATH src/main.o -Llib/all -lfoo -o test_shared_2
      
      g++ -Wl,-rpath=$MY_LD_LIBRARY_PATH src/main.o -Llib/all -Wl,-Bstatic -lfoo  -Wl,-Bdynamic -o test_static_3
  • 【Linux/gcc】C/C++——头文件和库_第8张图片
  • 分别执行静态链接文件和动态链接文件。
  • 【注】对于一个可执行文件,可以使用 ldd命令查看运行它所需要的动态库,以及它们现在是否能被系统找到,如果显示有的动态库没有找到,直接运行就会报错,这个选项也可以查看找到的是否是正确的动态库。
    • 【Linux/gcc】C/C++——头文件和库_第9张图片

你可能感兴趣的:(C/C++,linux,c语言,c++)