程序员的自我修养--链接、装载与库笔记:Linux共享库的组织

2023-11-10

共享库(Shared Library)概念:其实从文件结构上来讲,共享库和共享对象没什么区别,Linux下的共享库就是普通的ELF共享对象。由于共享对象可以被各个程序之间共享,所以它也就成为了库的很好的存在形式,很多库的开发者都以共享对象的形式让程序来使用,久而久之,共享对象和共享库这两个概念已经很模糊了,所以广义上我们可以将它们看作是同一个概念。

1. 共享库版本

共享库兼容性:共享库的开发者会不停地更新共享库的版本,以修正原有的Bug、增加新的功能或改进性能等。由于动态链接的灵活性,使得程序本身和程序所依赖的共享库可以分别独立开发和更新。但是共享库版本的更新可能会导致接口的更改或删除,这可能导致依赖于该共享库的程序无法正常运行。最简单的情况下,共享库的更新可以被分为两类:(1). 兼容更新:所有的更新只是在原有的共享库基础上添加一些内容,所有原有的接口都保持不变;(2). 不兼容更新:共享库更新改变了原有的接口,使用该共享库原有接口的程序可能不能运行或运行不正常。

接口这个词有着很广泛的含义,在软件的很多层次上都有所谓的”接口”。但是这里讨论的接口是二进制接口,即ABI(Application Binary Interface)共享库的ABI跟程序语言有着很大的关系,不同的语言对于接口的兼容性要求不同。ABI对于不同的语言来说,主要包括一些诸如函数调用的堆栈结构、符号命名、参数规则、数据结构的内存分布等方面的规则。对于一个C语言编写的共享库来说,什么样的更改会导致ABI变化呢?常见的更改方式,如下表所示:

导致C语言的共享库ABI改变的行为主要有如下4个:

(1). 导出函数的行为发生改变,也就是说调用这个函数以后产生的结果与以前不一样,不再满足旧版本规定的函数行为准则。

(2). 导出函数被删除。

(3). 导出数据的结构发生变化,比如共享库定义的结构体变量的结构发生改变:结构成员删除、顺序改变或其它引起结构体内存布局变化的行为(不过通常来讲,往结构体的尾部添加成员不会导致不兼容,当然这个结构体必须是共享库内部分配的,如果是外部分配的,在分配该结构体时必须考虑成员添加的情况)。

(4). 导出函数的接口发生变化,如函数返回值、参数被更改。

如果能够保证上述4种情况不发生,那么绝大部分情况下,C语言的共享库将会保持ABI兼容。注意,仅仅是绝大部分情况,要破坏一个共享库的ABI十分容易,要保持ABI的兼容却十分困难。很多因素会导致ABI的不兼容,比如不同版本的编译器、操作系统和硬件平台等,使得ABI兼容尤为困难。使用不同版本的编译器或系统库可能会导致结构体的成员对齐方式不一致,从而导致了ABI的变化。这种ABI不兼容导致的问题可能非常微妙,表面上看可能无关紧要,但是一旦发生故障,相关的Bug非常难以定位,这也是共享库很大的一个问题。

对于C++来说,ABI问题就更为严重了。由于C++非常复杂,它支持诸如模板等一些高级特性,这些特性对于ABI兼容来说简直就是灾难。因为C++标准对于C++的ABI没有做出规定,所以不同的编译器甚至同一个编译器的不同版本对于C++的一些特性的实现都有着各自的方案,而且相互不兼容,比如虚函数表、模板实例化、多重继承等。对于Linux来说,如果你要开发一个导出接口为C++的共享库,需要注意以下事项,以防止ABI不兼容(完全遵循以下准则还是不能保证ABI完全兼容):(1).不要在接口类中使用虚函数,万不得已要使用虚函数时,不要随意删除、添加或在子类中添加新的实现函数,这种会导致类的虚函数表结构发生变化;(2).不要改变类中任何成员变量的位置和类型;(3).不要删除非内嵌的public或protected成员函数;(4).不要将非内嵌的成员函数改变成内嵌成员函数;(5).不要改变成员函数的访问权限;(6).不要在接口中使用模板;(7).最重要的是,不要改变接口的任何部分或干脆不要使用C++作为共享库接口。

共享库版本命名:有几种办法可用于解决共享库的兼容性问题,有效办法之一就是使用共享库版本的方法。Linux有一套规则来命名系统中的每一个共享库,它规定共享库的文件名规则必须如下:libname.so.x.y.z

最前面使用前缀”lib”、中间是库的名字和后缀”.so”,最后面跟着的是三个数字组成的版本号。”x”表示主版本号(Major Version Number),”y”表示次版本号(Minor Version Number),”z”表示发布版本号(Release Version Number)。三个版本号的含义不一样。主版本号表示库的重大升级,不同主版本号的库之间是不兼容的,依赖于旧的主版本号的程序需要改动相应的部分,并且重新编译,才可以在新版的共享库中运行;或者系统必须保留旧版的共享库,使得那些依赖于旧版共享库的程序能够正常运行。次版本号表示库的增量升级,即增加一些新的接口符号,且保持原来的符号不变。在主版本号相同的情况下,高的次版本号的库向后兼容低的次版本号的库。一个依赖于旧的次版本号共享库的程序,可以在新的次版本号共享库中运行,因为新版中保留了原来所有的接口,并且不改变它们的定义和含义。发布版本号表示库的一些错误的修正、性能的改进等,并不添加任何新的接口,也不对接口进行更改。相同主版本号、次版本号的共享库,不同的发布版本号之间完全兼容,依赖于某个发布版本号的程序可以在任何一个其它发布版本号中正常运行,而无需做任何修改。当然现在Linux中也存在不少不遵循上述规定的”顽固分子”,比如最基本的C语言库Glibc就不使用这种规则。

SO-NAME:对于Solaris和Linux,普遍采用一种叫做SO-NAME的命名机制来记录共享库的依赖关系。每个共享库都有一个对应的”SO-NAME”,这个SO-NAME即共享库的文件名去掉次版本号和发布版本号,保留主版本号。很明显,”SO-NAME”规定了共享库的接口,”SO-NAME”的两个相同共享库,次版本号大的兼容次版本号小的。Linux系统中,系统会为每个共享库在它所在的目录创建一个跟”SO-NAME”相同的并且指向它的软链接(Symbol Link)。比如系统中有存在一个共享库”/lib/libfoo.so.2.6.1”,那么Linux中的共享库管理程序就会为它产生一个软链接”/lib/libfoo.so.2”指向它。由于历史原因,动态链接器和C语言库的共享对象文件名规则不按Linux标准的共享库命名方法。

那么以”SO-NAME”为名字建立软链接有什么用处呢?实际上这个软链接会指向目录中主版本号相同、次版本号和发布版本号最新的共享库。这样保证了所有的以SO-NAME为名的软链接都指向系统中最新版的共享库。建立以SO-NAME为名字的软链接目的是,使得所有依赖某个共享库的模块,在编译、链接和运行时,都使用共享库的SO-NAME,而不使用详细的版本号。当共享库进行升级的时候,如果只是进行增量升级,即保持主版本号不变,只改变次版本号或发布版本号,那么我们可以直接将新版的共享库替换掉旧版,并且修改SO-NAME的软链接指向新版本共享库,即可实现升级;当共享库的主版本号升级时,系统中就会存在多个SO-NAME,由于这些SO-NAME并不相同,所以已有的程序并不会受影响。总之,SO-NAME表示一个库的接口,接口不向后兼容,SO-NAME就发生变化,这是基本的原则。Linux中提供了一个工具叫做”ldconfig”,当系统中安装或更新一个共享库时,就需要运行这个工具,它会遍历所有的默认共享库目录,比如/lib、/usr/lib等,然后更新所有的软链接,使它们指向最新版的共享库;如果安装了新的共享库,那么ldconfig会为其创建相应的软链接

链接名:当我们在编译器里面使用共享库的时候(比如使用GCC的”-l”参数链接某个共享库),我们使用了更为简洁的方式,比如需要链接一个libXXX.so.2.6.1的共享库,只需要在编译器命令行里面指定-lXXX即可,可省略所有其它部分。编译器会根据当前环境,在系统中的相关路径(往往由-L参数指定)查找最新版本的”XXX”库。这个”XXX”又被称为共享库的链接名(Link Name)。不同类型的库可能会有同样的链接名,比如C语言运行库有静态版本(libc.a)和动态版本(libc.so.x.y.z)的区别,如果在链接时使用参数”-lc”,那么链接器会根据输出文件的情况(动态/静态)来选择适合版本的库。比如ld使用”-static”参数时,”-lc”会查找libc.a;如果使用”-Bdynamic”(这也是默认情况),它会查找最新版本的libc.so.x.y.z。

2. 符号版本

基于符号的版本机制(Symbol Versioning):基本思路是让每个导出和导入的符号都有一个相关联的版本号,它的实际做法类似于名称修饰的方法。与以往简单地将某个共享库的版本号重新命名不同(比如将libfoo.so.1.2升级到libfoo.so.1.3),当我们将libfoo.so.1.2升级至1.3时,仍然保持libfoo.so.1这个SO-NAME,但是给在1.3这个新版中添加的那些全局符号打上一个标记,比如”VERS_1.3”。那么,如果一个共享库每一次次版本号升级,我们都能给那些在新的次版本号中添加的全局符号打上相应的标记,就可以清楚地看到共享库中的每个符号都拥有相应的标签,比如”VERS_1.1”、”VERS_1.2”、”VERS_1.3”、”VERS_1.4”。

Linux中的符号版本:Linux系统下共享库的符号版本机制并没有被广泛应用,主要使用共享库符号版本机制的是Glibc软件包中所提供的20多个共享库。这些共享库比较有效地利用了符号版本机制来表示符号的版本演化及利用范围机制来屏蔽一些不希望暴露给共享库使用者的符号。

GCC对Solaris符号版本机制的扩展:GCC在Solaris系统中的符号版本机制的基础上还提供了两个扩展。第一个扩展是,除了可以在符号版本脚本中指定符号的版本之外,GCC还允许使用一个叫做”.symver”的汇编宏指令来指定符号的版本,这个汇编宏指令可以被用在GAS汇编中,也可以在GCC的C/C++源代码中以嵌入汇编指令的模式使用。第二个扩展是GCC允许多个版本的同一个符号存在于一个共享库中,也就是说,在链接层面提供了某种形式的符号重载机制。Linux下的符号版本机制允许同一个名称的符号存在多个版本。当某个符号在新的共享库版本中接口被更改或符号的含义被改变,那么共享库可以保留原来的版本符号。

3. 共享库系统路径

目前大多数包括Linux在内的开源操作系统都遵守一个叫做FHS(File Hierarchy Standard)的标准,这个标准规定了一个系统中的系统文件应该如何存放,包括各个目录的结构、组织和作用,这有利于促进各个开源操作系统之间的兼容性。FHS规定,一个系统中主要有两个存放共享库的位置,它们分别为

(1). /lib:这个位置主要存放系统最关键和基础的共享库,比如动态链接器、C语言运行库、数学库等,这些库主要是那些/bin和/sbin下的程序所需要用到的库,还有系统启动时需要的库;

(2). /usr/lib:这个目录下主要保存的是一些非系统运行时所需要的关键性的共享库,主要是一些开发时用到的共享库,这些共享库一般不会被用户的程序或shell脚本直接用到。这个目录下面还包含了开发时可能会用到的静态库、目标文件等;

(3). /usr/local/lib:这个目录用来放置一些跟操作系统本身并不十分相关的库,主要是一些第三方的应用程序的库。GNU的标准推荐第三方的程序应该默认将库安装到/usr/local/lib下。

所以总体来看,/lib和/usr/lib是一些很常用的、成熟的,一般是系统本身所需要的库;而/usr/local/lib是非系统所需的第三方程序的共享库。

4. 共享库查找过程

在Linux系统中,动态链接器是/lib/ld-linux.so.X(X是版本号),程序所依赖的共享对象全部由动态链接器负责装载和初始化。任何一个动态链接的模块所依赖的模块路径保存在”.dynamic”段里面,由DT_NEED类型的项表示。动态链接器对于模块的查找有一定的规则:如果DT_NEED里面保存的是绝对路径,那么动态链接器就按照这个路径去查找;如果DT_NEED里面保存的是相对路径,那么动态链接器会在/lib、/usr/lib和由/etc/ld.so.conf配置文件指定的目录中查找共享库。为了程序的可移植行和兼容性,共享库的路径往往是相对的。ld.so.conf是一个文本配置文件,它可能包含其它的配置文件,这些配置文件中存放着目录信息。

如果动态链接器在每次查找共享库时都去遍历这些目录,那将会非常耗费时。所以Linux系统中都有一个叫做ldconfig的程序,这个程序的作用是为共享库目录下的各个共享库创建、删除或更新相应的SO-NAME(即相应的符号链接),这样每个共享库的SO-NAME就能够指向正确的共享库文件;并且这个程序还会将这些SO-NAME收集起来,集中存放到/etc/ld.so.cache文件里面,并建立一个SO-NAME的缓存。当动态链接器要查找共享库时,它可以直接从/etc/ld.so.cache里面查找。而/etc/ld.so.cache的结构是经过特殊设计的,非常适合查找,所以这个设计大大加快了共享库的查找过程。如果动态链接器在/etc/ld.so.cache里面没有找到所需要的共享库,那么它还会遍历/lib和/usr/lib这两个目录,如果还是没有找到,就宣告失败。

所以理论上讲,如果我们在系统指定的共享库目录下添加、删除或更新任何一个共享库,或者我们更改了/etc/ld.so.conf的配置,都应该运行ldconfig这个程序,以便调整SO-NAME和/etc/ld.so.cache。很多软件包的安装程序在往系统里面安装共享库以后都会调用ldconfig

5. 环境变量

LD_LIBRARY_PATH:Linux系统提供了很多方法来改变动态链接器装载共享库路径的方法,通过使用这些方法,我们可以满足一些特殊的需求,比如共享库的调试和测试、应用程序级别的虚拟等。改变共享库查找路径最简单的方法是使用LD_LIBRARY_PATH环境变量,这个方法可以临时改变某个应用程序的共享库查找路径,而不会影响系统中的其它程序Linux系统中,LD_LIBRARY_PATH是一个由若干个路径组成的环境变量,每个路径之间由冒号分割。默认情况下,LD_LIBRARY_PATH为空。如果我们为某个进程设置了LD_LIBRARY_PATH,那么进程在启动时,动态链接器在查找共享库时,会首先查找由LD_LIBRARY_PATH指定的目录。这个环境变量可以很方便地让我们测试新的共享库或使用非标准的共享库。

Linux中还有一种方法可以实现与LD_LIBRARY_PATH类似的功能,那就是直接运行动态链接器来启动程序。

有了LD_LIBRARY_PATH之后,再来总结动态链接器查找共享库的顺序。动态链接器会按照下列顺序依次装载或查找共享对象(目标文件):(1).由环境变量LD_LIBRARY_PATH指定的路径;(2).由路径缓存文件/etc/ld.so.cache指定的路径;(3).默认共享库目录,先/usr/lib,然后/lib。

LD_PRELOAD:这个文件中我们可以指定预先装载的一些共享库或目标文件。在LD_PRELOAD里面指定的文件会在动态链接器按照固定规则搜索共享库之前装载,它比LD_LIBRARY_PATH里面所指定的目录中的共享库还要优先。无论程序是否依赖于它们,LD_PRELOAD里面指定的共享库或目标文件都会被装载。由于全局符号介入这个机制的存在,LD_PRELOAD里面指定的共享库或目标文件中的全局符号就会覆盖后面加载的同名全局符号,这使得我们可以很方便地做到改写标准C库中的某个或某几个函数而不影响其它函数,对于程序的调试或测试非常有用。与LD_LIBRARY_PATH一样,正常情况下应该尽量避免使用LD_PRELOAD。系统配置文件中有一个文件是/etc/ld.so.preload,它的作用于LD_PRELOAD一样。这个文件里面记录的共享库或目标文件的效果跟LD_PRELOAD里面指定的一样,也会被提前装载。

LD_DEBUG:这个变量可以打开动态链接器的调试功能,当我们设置这个变量时,动态链接器会在运行时打印出各种有用的信息,对于我们开发和调试共享库有很大的帮助。如执行Program1,程序代码见https://blog.csdn.net/fengbingchun/article/details/101120761,执行结果如下图所示:

LD_DEBUG设置值的作用

(1). “files”:动态链接器打印整个装载过程,显示程序依赖于哪个共享库并且按照什么步骤装载和初始化,共享库装载时的地址等。

(2). “bindings”:显示动态链接的符号绑定过程。

(3). “libs”:显示共享库的查找过程。

(4). “versions”:显示符号的版本依赖关系。

(5). “reloc”:显示重定位过程。

(6). “symbols”:显示符号表查找过程。

(7). “statistics”:显示动态链接过程中的各种统计信息。

(8). “all”:显示以上所有信息。

(9). “help”:显示上面的各种可选值的帮助信息。

6. 共享库的创建和安装

共享库的创建创建共享库的过程跟创建一般的共享对象的过程基本一致,最关键的是使用GCC的两个参数,即”-shared”和”-fPIC”。”-shared”表示输出结果是共享库类型的;”-fPIC”表示使用地址无关代码(Position Independent Code)技术来生产输出文件。另外还有一个参数是”-Wl”,这个参数可以将指定的参数传递给链接器,比如当我们使用”-Wl,-soname,my_soname”时,GCC会将”-soname my_soname”传递给链接器,用来指定输出共享库的SO-NAME。如果我们不使用-soname来指定共享库的SO-NAME,那么该共享库默认就没有SO-NAME,即使用ldconfig更新SO-NAME的软链接时,对该共享库也没有效果。

几个值得注意的事项:(1).不要把输出共享库中的符号和调试信息去掉,也不要使用GCC的”-fomit-frame-pointer”选项,这样做虽然不会导致共享库停止运行,但是会影响调试共享库,给后面的工作带来很多麻烦。(2).在开发过程中,你可能要测试新的共享库,但是又不希望影响现有的程序正常运行,可以用LD_LIBRARY_PATH指定共享库的查找路径。还有一种方法是使用链接器的”-rpath”选项(或者GCC的-Wl,-rpath),这种方法可以指定链接产生的目标程序的共享库查找路径。(3).默认情况下,链接器在产生可执行文件时,只会将那些链接时被其它共享模块引用到的符号放到动态符号表,这样可以减少动态符号表的大小。也就是说,在共享模块中反向引用主模块中的符号时,只有那些在链接时被共享模块引用到的符号才会被导出。有一种情况是,当程序使用dlopen()动态加载某个共享模块,而该共享模块需反向引用主模块的符号时,有可能主模块的某些符号因为在链接时没有被其它共享模块引用而没有被放到动态符号表里面,导致了反向引用失败。ld链接器提供了一个”-export-dynamic”的参数,这个参数表示链接器在生产可执行文件时,将所有全局符号导出到动态符号表,以防止出现上述问题。我们也可以在GCC中使用”-Wl,-export-dynamic”将该参数传递给链接器。

清除符号信息:正常情况下编译出来的共享库或可执行文件里面带有符号信息和调试信息,这些信息在调试时非常有用,但是对于最终发布的版本来说,这些符号信息用处并不大,并且使得文件尺寸变大。我们可以使用一个叫”strip”的工具清除掉共享库或可执行文件的所有符号和调试信息(“strip”是binutils的一部分):$ strip libfoo.so

去除符号和调试信息以后的文件往往比之前要小很多。除了使用”strip”工具,我们还可以使用ld的”-s”和”-S”参数,使得链接器生成输出文件时就不产生符号信息。”-s”和”-S”的区别是:”-S”消除调试符号信息,而”-s”消除所有符号信息。我们也可以在GCC中通过”-Wl,-s”和”-Wl,-S”给ld传递这两个参数。

 共享库的安装:最简单的办法就是将共享库复制到某个标准的共享库目录,如/lib、/usr/lib等,然后运行ldconfig即可。不过上述方法往往需要系统的root权限,如果没有,则无法往/lib、/usr/lib等目录添加文件,也无法运行ldconfig程序。也可以通过建立相应的SO-NAME软链接方法,并告诉编译器和程序如何查找该共享库等,以便于编译器和程序都能够正常运行。建立SO-NAME的办法也是使用ldconfig,只不过需要指定共享库所在的目录。在编译程序时,也需要指定共享库的位置,GCC提供了两个参数”-L”和”-l”,分别用于指定共享库搜索目录和共享库。也可以使用”-rpath”参数。

共享库构造和析构函数:很多时候你希望共享库在被装载时能够进行一些初始化工作,比如打开文件、网络连接等,使得共享库里面的函数接口能够正常工作。GCC提供了一种共享库的构造函数,只要在函数声明时加上”__attribute__((constructor))”的属性,即指定该函数为共享库构造函数,拥有这种属性的函数会在共享库加载时被执行,即在程序的main函数之前执行。如果我们使用dlopen()打开共享库,共享库构造函数会在dlopen()返回之前执行。与共享库构造函数相对应的是析构函数,我们可以使用在函数声明时加上”__attribute__((destructor))”的属性,这种函数会在main()函数执行完毕之后执行(或者是程序调用exit()时执行)。如果共享库是在运行时加载的,那么我们使用dlclose()来卸载共享库时,析构函数将会在dlclose()返回之前执行。值得注意的是,如果我们使用了这种析构或构造函数,那么必须使用系统默认的标准运行库和启动文件,即不可以使用GCC的”-nostartfiles”或”-nostdlib”这两个参数。因为这些构造和析构函数是在系统默认的标准运行库或启动文件里面被运行的,如果没有这些辅助构造,它们可能不会被运行。另外还有一个问题是,如果我们有多个构造函数,那么默认情况下,它们被执行的顺序是没有规定的。如果我们希望构造和析构函数能够按照一定的顺序执行,GCC为我们提供了一个参数叫做优先级,我们可以指定某个构造或析构函数的优先级。对于构造函数来说,属性中优先级数字越小的函数将会在优先级大的函数之前运行;而对于析构函数来讲,则刚好相反。这种安排有利于构造函数和析构函数能够匹配,比如某一对构造函数和析构函数分别用来申请和释放某个资源,那么它们可以拥有一样的优先级。这样做的结果往往是先申请的资源后释放,符合资源释放的一般规则。

共享库脚本:前面所提到的共享库都是动态链接的ELF共享对象文件(.so),事实上,共享库还可以是符合一定格式的链接脚本文件。通过这种脚本文件,我们可以把几个现有的共享库通过一定的方式组合起来,从用户的角度看就是一个新的共享库。这里的脚本与LD的脚本从语法和命令上来讲没什么区别,它们的作用也相似,即将一个或多个输入文件以一定的格式经过变换以后形成一个输出文件。我们也可以将这种共享库脚本叫做动态链接脚本,因为这个链接过程是动态完成的,也就是运行时完成的。

GitHubhttps://github.com/fengbingchun/Messy_Test

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

程序员的自我修养--链接、装载与库笔记:Linux共享库的组织 的相关文章

  • C++11中std::condition_variable的使用

  • C++11中enum class的使用

    枚举类型 enumeration 使我们可以将一组整型常量组织在一起 和类一样 每个枚举类型定义了一种新的类型 枚举属于字面值常量类型 C 包含两种枚举 限定作用域的和不限定作用域的 这里主要介绍限定作用域的 不限定作用域的使用可以参考 h
  • C和C++安全编码笔记:整数安全

    5 1 整数安全导论 整数由包括0的自然数 0 1 2 3 和非零自然数的负数 1 2 3 构成 5 2 整数数据类型 整数类型提供了整数数学集合的一个有限子集的模型 一个具有整数类型的对象的值是附着在这个对象上的数学值 一个具有整数类型的
  • C++/C++11中头文件algorithm的使用

  • 程序员的自我修养--链接、装载与库笔记:可执行文件的装载与进程

    可执行文件只有装载到内存以后才能被CPU执行 1 进程虚拟地址空间 程序和进程有什么区别 程序 或者狭义上讲可执行文件 是一个静态的概念 它就是一些预先编译好的指令和数据集合的一个文件 进程则是一个动态的概念 它是程序运行时的一个过程 很多
  • C++11中头文件atomic的使用

    原子库为细粒度的原子操作提供组件 允许无锁并发编程 涉及同一对象的每个原子操作 相对于任何其他原子操作是不可分的 原子对象不具有数据竞争 data race 原子类型对象的主要特点就是从不同线程访问不会导致数据竞争 因此从不同线程访问某个原
  • C++/C++11中引用的使用

    引用 reference 是一种复合类型 compound type 引用为对象起了另外一个名字 引用类型引用 refer to 另外一种类型 通过将声明符写成 d的形式来定义引用类型 其中d是声明的变量名 一 一般引用 一般在初始化变量时
  • C和C++安全编码笔记:文件I/O

    C和C 程序通常会对文件进行读写 并将此作为它们正常操作的一部分 不计其数的漏洞正是由这些程序与文件系统 其操作由底层操作系统定义 交互方式的不规则性而产生的 这些漏洞最常由文件的识别问题 特权管理不善 以及竞争条件导致 8 1 文件I O
  • C语言中signal函数简介及使用

    signal h是C标准函数库中的信号处理部分 定义了程序执行时如何处理不同的信号 信号用作进程间通信 报告异常行为 如除零 用户的一些按键组合 如同时按下Ctrl与C键 产生信号SIGINT C 中的对应头文件是csignal C语言标准
  • 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数 reference counting 基本思想是将销毁对象的职责从客户端代码转移到对象本身 对象跟踪记录自身当前被引用的数目 在引用计数达到零时自行销毁 换句话说 对象不再被使用时自行销毁 引用计数和执行速度之间的关系是与上下文紧
  • C++中的内存对齐介绍

    网上有很多介绍字节对齐或数据对齐或内存对齐的文章 虽然名字不一样 但是介绍的内容大致都是相同的 这里以内存对齐相称 注 以下内容主要来自网络 内存对齐 通常也称为数据对齐 是计算机对数据类型合法地址做出了一些限制 要求某种类型对象的地址必须
  • 程序员的自我修养--链接、装载与库笔记:Linux共享库的组织

    共享库 Shared Library 概念 其实从文件结构上来讲 共享库和共享对象没什么区别 Linux下的共享库就是普通的ELF共享对象 由于共享对象可以被各个程序之间共享 所以它也就成为了库的很好的存在形式 很多库的开发者都以共享对象的
  • C++中插件使用举例

    插件并不是在构建时链接的 而是在运行时发现并加载的 因此 用户可以利用你定义好的插件API来编写自己的插件 这样他们就能以指定方式扩展API的功能 插件库是一个动态库 它可以独立于核心API编译 在运行时根据需要显示加载 不过插件也可以使用
  • C++中vector的使用

    向量std vector是一种对象实体 能够容纳许多各种类型相同的元素 包括用户自定义的类 因此又被称为序列容器 与string相同 vector同属于STL Standard Template Library 中的一种自定义的数据类型 可
  • C++中typeid的使用

    RTTI Run TimeType Information 运行时类型信息 它提供了运行时确定对象类型的方法 在C 中 为了支持RTTI提供了两个操作符 dynamic cast和typeid The typeid operator pro
  • 程序员的自我修养--链接、装载与库笔记:目标文件里有什么

    编译器编译源代码后生成的文件叫做目标文件 目标文件从结构上讲 它是已经编译后的可执行文件格式 只是还没有经过链接的过程 其中可能有些符号或有些地址还没有被调整 其实它本身就是按照可执行文件格式存储的 只是跟真正的可执行文件在结构上稍有不同
  • 概率论中伯努利分布(bernoulli distribution)介绍及C++11中std::bernoulli_distribution的使用

    Bernoulli分布 Bernoulli distribution 是单个二值随机变量的分布 它由单个参数 0 1 给出了随机变量等于1的概率 它具有如下的一些性质 P x 1 P x 0 1 P x x x 1 1 x Ex x Var
  • 开源库jemalloc简介

    jemalloc是通用的malloc 3 实现 它强调避免碎片和可扩展的并发支持 它的源码位于https github com jemalloc jemalloc 最新稳定版本为5 2 1 glibc的内存分配算法是基于dlmalloc实现
  • C++14中binary literals的使用

    一个形如42的值被称作字面值常量 literal 这样的值一望而知 每个字面值常量都对应一种数据类型 字面值常量的形式和值决定了它的数据类型 我们可以将整型字面值写作十进制 基数为10 八进制 基数为8 或十六进制 基数为16 数的形式 以
  • C++/C++11中变长参数的使用

    C C 11中的变长参数可以应用在宏 函数 模板中 1 宏 在C99标准中 程序员可以使用变长参数的宏定义 变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号 而预定义宏 VA ARGS 则可以在宏定义的实现部分替换省略号所代表的

随机推荐

  • hdu 5818 Joint Stacks 2016 Multi-University 7

    Problem acm hdu edu cn showproblem php pid 5818 官方题解 bestcoder hdu edu cn blog 2016 multi university training contest 7
  • error:Target dll has been cancelled.debugger aborted

    MDK4 23调试裸机程序 出现这个问题先记下 以前成功调试过的 这次用又不会了 找一下以前的文章 今天开始摸索MDK高版本调试S3C2440裸机程序 3 4版本的实质上是借用Jlink的软件JLinkARM dll和JLinkRDI dl
  • org.yaml.snakeyaml.scanner.ScannerException: mapping values are not allowed here

    在springCloud网关启动时报了这样一个错 大概意思是 映射不允许在这 最后发现时yml文件里 处id uri predicates 以及filter没有对齐 他们是同级的 报这个错一般是配置文件里路由没配置好
  • qt exec 跟show的区别

    1 要理清两个函数的区别 首先需要理解窗口模式 模式窗口 窗口会原地阻塞 只能操作该窗口 其余窗口不能再操作 只有关闭该窗口后 代码处才会获得返回值 阻塞停止 就可以操作其它界面了 半模式窗口 窗口会原地伪阻塞 虽然也是只能操作该窗口 其余
  • vue3中一个组件调用另外一个组件的方法

    vue3中一个组件调用另外一个组件的方法 1 组件化开发是vue的比较常用的 这里简单写一个vue3的组件化开发demo案例 组件1
  • C语言中的强符号和弱符号

    一 强弱符号 强弱符号针对的是处于同一工程下在不同源文件下定义的全局变量符号 链接器只处理global的符号而不处理local的符号 链接的核心是符号的重定位 在符号引用的地方找到符号定义的地方 包括函数产生的符号和全局变量产生的符号 强符
  • 刷脸支付是当下科技发展的主流旋律

    科技发展的初衷是为人民服务 而更快捷便利的为人民服务 则是当下科技发展的主流旋律 就拿超市结账来说 以前是排着队 等收银员一件件扫完商品码之后 再掏出现金进行结账 这样不仅效率慢 而且收银员还辛苦 后来进化到刷卡 扫描二维码 效率虽然提升了
  • 学术文献也有身份证?

    关注 心仪脑 查看更多脑科学知识的分享 关键词 科普散文 干货分享 生活中 证明你是你很简单 只需拿出你的身份证或护照 其实 学术文献也有如假包换 具有唯一性的身份证 对于经常需要下载文献的同学们来说 肯定再熟悉不过了 那就是文献的DOI号
  • OpenGL渲染结果发生了奇怪的拉伸和奇怪的彩条

    部分代码如下 Load create texture and generate mipmaps int width height unsigned char image SOIL load image 1 jpg width height
  • 【分享】docker引发的172.17.x.x网段无法访问

    前言 想搭建一个测试环境 折腾vmware虚拟机 发现公司的172 17网段怎么都访问不了 使用traceroute 发现 一直走172 17 0 1 无论是怎么更改配置 都是如此 查阅资料发现 当 Docker 启动时 会自动在主机上创建
  • jquery-migrate-1.2.1.min.js 作用

    在网上下载一些 js 插件使用的时候 经常出现这种情况 但是更换低版本 jquery 比如 使用 1 7 版本的 jquery 却不会报错 能正常使用 经过一番搜索 发现是因为高版本 1 9版本以后 不兼容以前的 需要引入一个 jquery
  • linux虚拟机安装后查不到IPV4地址的解决办法

    由于选择了桥接方式 网络配置设置不正确导致 编辑network配置文件 解决此问题 cd etc sysconfig network scripts vi enp0s3 若为做特殊修改 此名字是你打ifconfig查看显示的名字 改成如下配
  • AntDesign Pro安装过程

    详细资料请到官网查看文档 Ant Design Pro相关系列文章 一 AntDesign Pro安装过程 二 基于Ant DesignPro实现通过SpringBoot后台加载自定义菜单 前端部分 三 基于Ant DesignPro实现通
  • Excel数据过大,导出超时解决方案

    问题 随着业务量的增加 数据存储也越来越大 当我们从数据库读取数据生成excel时 往往会出现超时情况 尝试解决方案 考虑直接更改该请求的超时时长 但发现 随着数据增长 依然会出现超时情况 考虑采用多线程的方式 理论可以加快数据的读取效率
  • 二叉树17:路径总和

    主要是我自己刷题的一些记录过程 如果有错可以指出哦 大家一起进步 转载代码随想录 原文链接 代码随想录 leetcode链接 112 路径总和 112 路径总和 题目 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum
  • 【C进阶】深度剖析数据在内存中的存储

    目录 一 数据类型的介绍 1 类型的意义 2 类型的基本分类 二 整形在内存中的存储 1 原码 反码 补码 2 大小端介绍 3 练习 三 浮点型在内存中的存储 1 一个例子 2 浮点数存储规则 一 数据类型的介绍 前面我们已经学习了基本的内
  • 《kubernetes-1.8.0》20-examples-Deployments

    kubernetes 1 8 0 20 examples Deployments kubernetes 1 8 0 测试环境安装部署 时间 2017 12 19 一 基础知识 Deployment 为 Pod 和 ReplicaSet 提供
  • AI 机器学习实践总结

    机器学习基础 什么是机器学习 机器学习是一种从数据生成规则 发现模型 来帮助我们预测 判断 分组和解决问题的技术 机器学习是一种从数据中生产函数 而不是程序员直接编写函数的技术 说起函数就涉及到自变量和因变量 在机器学习中 把自变量叫做特征
  • 力扣(LeetCode)算法_C++——存在重复元素 II

    存在重复元素 II 给你一个整数数组 nums 和一个整数 k 判断数组中是否存在两个 不同的索引 i 和 j 满足 nums i nums j 且 abs i j lt k 如果存在 返回 true 否则 返回 false 示例 1 输入
  • 程序员的自我修养--链接、装载与库笔记:Linux共享库的组织

    共享库 Shared Library 概念 其实从文件结构上来讲 共享库和共享对象没什么区别 Linux下的共享库就是普通的ELF共享对象 由于共享对象可以被各个程序之间共享 所以它也就成为了库的很好的存在形式 很多库的开发者都以共享对象的