【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,进入插入模式,编辑文件。
      • 编辑完毕后按回退键,输入 :wq,保存文件。
    • 使用gcc编译
      • gcc hello.cpp
        # 会报错iostream cout各种未定义gcc hello.cpp -lstdc++ -o hello
        # 链接STL后可以顺利进行./hello
        #  执行目标文件
    • 使用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、实验

  • 首先创建好目录结构。
    • |-bin
      |-includefoo.h
      |-lib|-all|-shared|-static
      |-srcfoo1.cppfoo2.cppmain.cpp
  • 创建一个名为foo的库,头文件为foo.h。
  • 【注】命令:cd.. 返回上一级目录。
  • 创建两个源文件,分别实现静态库和动态库。
    • foo1.cpp
    • foo2.cpp
    • 这里foo1.cpp和foo2.cpp都分别对头文件foo.h中的两个函数getnum()、sayhello()进行了实现。
  • 创建main.cpp引用头文件foo.h,用多种方式调用foo库。
  • 实验将在 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,而静态库通常没有,因此动态库中如果想要链接一个静态库会报错,解决办法可以是把静态库也添加选项,改为生成位置无关的代码。
  •  
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_1g++ -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_2g++ -Wl,-rpath=$MY_LD_LIBRARY_PATH src/main.o -Llib/all -Wl,-Bstatic -lfoo  -Wl,-Bdynamic -o test_static_3
  • 分别执行静态链接文件和动态链接文件。
  • 【注】对于一个可执行文件,可以使用 ldd命令查看运行它所需要的动态库,以及它们现在是否能被系统找到,如果显示有的动态库没有找到,直接运行就会报错,这个选项也可以查看找到的是否是正确的动态库。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/294825.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# 判断两个时间段是否重叠

public static bool IsOverlap(DateTime startTime1, DateTime endTime1, DateTime startTime2, DateTime endTime2){// 判断两个时间段是否有重叠return !(endTime1 < startTime2 || startTime1 > endTime2);//根据德摩根定律&#xff0c;等效为&#xff1a;endTime1 &g…

Go语言HTTP客户端编程实践

在互联网时代&#xff0c;HTTP客户端编程已经成为一项必备技能。而Go语言作为一种高效、简洁的编程语言&#xff0c;非常适合用来构建高效的HTTP客户端。下面&#xff0c;我们就来一起探讨如何使用Go语言进行HTTP客户端编程实践。 首先&#xff0c;让我们来看看如何使用Go语言…

步兵 cocos2dx 加密和混淆

文章目录 摘要引言正文代码加密具体步骤代码加密具体步骤测试和配置阶段IPA 重签名操作步骤 总结参考资料 摘要 本篇博客介绍了针对 iOS 应用中的 Lua 代码进行加密和混淆的相关技术。通过对 Lua 代码进行加密处理&#xff0c;可以确保应用代码的安全性&#xff0c;同时提高性…

论文阅读——BLIP-2

BLIP-2: Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models 1 模型 在预训练视觉模型和预训练大语言模型中间架起了一座桥梁。两阶段训练&#xff0c;视觉文本表示和视觉到语言生成学习。 Q-Former由两个转换器子模块组成&am…

python三大开发框架django、 flask 和 fastapi 对比

本文讲述了什么启发了 FastAPI 的诞生&#xff0c;它与其他替代框架的对比&#xff0c;以及从中汲取的经验。 如果不是基于前人的成果&#xff0c;FastAPI 将不会存在。在 FastAPI 之前&#xff0c;前人已经创建了许多工具 。 几年来&#xff0c;我一直在避免创建新框架。首先&…

SpringBoot集成opencc4j实现繁体中文转为简体中文

背景 繁体中文转为简体中文的需求非常常见&#xff0c;特别是在中文语境下的文本处理和翻译应用中。有很多现成的工具和库可以实现这个功能&#xff0c;比如 OpenCC 、 HanLP 等。从网上下载的 MySQL 版诗词数据库中的诗词数据都是繁体字&#xff0c;这里使用 SpringBoot 集成…

Java并发工具类---ForkJoin、countDownlatch、CyclicBarrier、Semaphore

一、Fork Join fork join是JDK7引入的一种并发框架&#xff0c;采用分而治之的思想来处理并发任务 ForkJoin框架底层实现了工作窃取&#xff0c;当一个线程完成任务处于空闲状态时&#xff0c;会窃取其他工作线程的任务来做&#xff0c;这样可以充分利用线程来进行并行计算&a…

C++哈希表的实现

C哈希表的实现 一.unordered系列容器的介绍二.哈希介绍1.哈希概念2.哈希函数的常见设计3.哈希冲突4.哈希函数的设计原则 三.解决哈希冲突1.闭散列(开放定址法)1.线性探测1.动图演示2.注意事项3.代码的注意事项4.代码实现 2.开散列(哈希桶,拉链法)1.概念2.动图演示3.增容问题1.拉…

C# WPF上位机开发(文件对话框和目录对话框)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 一个上位机软件在处理数据的时候&#xff0c;除了配置文件、数据文件之外&#xff0c;一般还需要使用选择对话框进行文件和目录的选取。如果不这样…

JavaWeb笔记之SVN

一、版本控制 软件开发过程中 变更的管理&#xff1b; 每天的新内容;需要记录一下&#xff1b; 版本分支;整合到一起&#xff1b; 主要的功能对于文件变更的追踪&#xff1b; 多人协同开发的情况下,更好的管理我们的软件。 大型的项目;一个团队来进行开发; 1: 代码的整合 2: 代…

如何在Linux下搭建接口自动化测试平台

我们今天来学习一下在Linux下如何搭建基于HttpRunner开发的接口自动化测试平台吧&#xff01; 需要在Linux上提前准备的环境&#xff08;下面是本人搭建时的环境&#xff09;&#xff1a; 1&#xff0c;Python 3.6.8 2&#xff0c;MySQL 5.7 一&#xff1a;下载HttpRunner…

SpringIOC之LocaleContext

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…