深入理解计算机系统 --- 链接

2023-11-18

本章目的: 提供了关于链接各方面的全面讨论,从传统静态链接到加载时的共享库的动态链接,以及到运行时的共享库的动态链接

链接(linking)是将各种代码和数据片段收集并组合成一个单一文件的过程

这个文件可被加载(复制)到内存被并执行
链接可以执行于编译时,也就是在源代码被翻译成机器代码时
也可以执行于运行时,也就是应用程序执行

在早期,链接是手动执行的,在现代系统中,链接是由叫做链接器(linker)的程序自动执行的
在这里插入图片描述
不过,无论是什么样的操作系统、ISA或者目标文件格式、基本链接概念是通用的
细节可能不尽相同,但是概念是相同的

7 链接

7.1 编译器驱动程序

在这里插入图片描述
大多数编译系统提供 编译器驱动程序 , 它代表用户在需要时调用语言预处理器、编译器、汇编器和连接器

在shell使用命令:
在这里插入图片描述
在这里插入图片描述
上图概括了驱动程序在将示例从ASCLL码源文件翻译器成 可执行目标文件时的行为
在这里插入图片描述
如果想看详细步骤,可以用 –v 选项

它将C的源程序main.c 翻译成一个ASCLL码的中间文件 main.i
Cpp main.c main.i

然后,驱动程序运行C编译器(ccl),它将main.i翻译成一个ASCLL汇编语言文件main.s
Ccl main.i –Og –o main.s

然后,驱动程序运行汇编器(as),它将main.s翻译成一个可重定位目标文件main.o
As –o main.o main.s

驱动程序经过相同的过程生产sum.o ,最后,它运行连接器ld,将main.o和sum.o以及一些必要的系统目标文件组合起来,创建一个可执行目标文件 prog
Ld –o prog main.o sum.o

运行prog
./prog
Shell调用操作系统中一个叫做加载器的函数,它将可执行文件prog的代码和数据复制到内存,然后将控制转移到这个程序的开头

在这里插入图片描述
上图展示了 GCC 编译成不同的中间文件

7.2 静态链接
像Linux LD程序这样的静态链接器 (static linker) 以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出

输入的可重定位目标文件由各种不同的代码和数据节(section)组成
每一节都是一个连续的字节序列,指令在一节中,初始化了的全局变量在另一节中,而未初始化的变量由在另外一节中

为了构造可执行文件,链接器必须完成两个主要任务:
在这里插入图片描述

7.3 目标文件

目标文件有三种形式:
在这里插入图片描述

编译器和汇编器生成可重定位目标文件(包括共享目标文件)
链接器生成可执行目标文件

在技术上来说,一个目标模板(Object module)就是一个字节序列,而一个目标文件(Object file)就是一个以文件形式存放在磁盘中的目标模板

目标文件就是按照特定的目标文件格式来组织的,各个系统的目标文件格式都不相同
Windows使用可移植可执行(Protable Executable, PE)格式
现代x86-64 Linux和Unix系统使用可执行可链接格式(Executable and Linkable Format, ELF)
但不管哪种格式,基本概念是相似的

7.4 可重定位目标文件

在这里插入图片描述
ELF头(ELF header) 以一个16字节的序列开始,这个序列描述了生产该文件的系统的字的大小和字节顺序

ELF头剩下的部分包括帮助连接器语法分析和解释目标文件的信息
其中包括ELF头的大小、目标文件的类型、机器类型、节头部表中条目的大小和数量
不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目

包含在EFL头和节头部表之间的都是节,一个典型的ELF可重定位目标文件包含下面几个节:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

7.5 符号和符号表

每个可重定位目标模板m都有一个符号表,它包含m定义和引用的符号的信息,在连接器的上下文中,有三种不同的符号:
在这里插入图片描述
本地链接器符号和本地程序变量不同的, .symtab中的符号表不包含对应于本地非静态程序变量的任何符号,这些符号在运行时在栈中被管理

定义带有C static属性的本地过程变量是不在栈中被管理的,编译器在.data 或 .bss 中为每个定义分配空间,并在符号表中创建一个唯一名字的本地链接符号

符号表是由编译器构造的,使用编译器输出到汇编语言 .s 文件中的符号, .symtab节中包含ELF符号表,这张符号表包含一个条目的数据
在这里插入图片描述
在这里插入图片描述
每个符号都被分配到目标文件的某个节,由section字段表示,该字段也是到一个节头部表的索引

有三个特殊的伪节(pseudosection),它们在节头部表中是没有条目的:
ABS 代表不被重定位的符号
UNDEF 代表未定义的符号,也就是在本目标模板中引用
COMMON 表示还未被分配位置的未初始化的数据目标
对于COMMON符号,value字段给出对齐要求,而size给出最小的大小

注意,只有可重定位目标文件才有这些伪节,可执行目标文件中是没有的

7.6 符号解析

链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来

对于那些和引用定义在相同模板中的局部符号的引用,符号解析式是非常简单明了的
编译器只允许每个模板中每个局部符号有一个定义,静态变量也会有本地链接器符号
编译器还要确保他们拥有唯一的名字

对于全局变量的引用解析就棘手得多,当编译器遇到一个不是在当前模板中定义的符号(变量或函数名)时,会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理
如果链接器在他任何输入模块中找不到这个被引用符号的定义,就输出一条错误信息
在这里插入图片描述

7.6.1 链接器如何解析多重定义的全局符号

链接器的输入时一组可重定位目标模块
每个模块定义一组符号,有些是局部(只对定义该符号的模块可见)
有些是全局(对其他模块也可见)

如果多个模块定义同名的全局符号,下面是Linux编译系统采用的方法:
在编译时,编译器向汇编器输出每个全局符号,或者是强(strong),或者是弱(weak)
而汇编器把这个信息隐含在可重定位目标文件的符号表里
函数和已初始化的全局变量是强符号
未初始化的全局变量是弱符号
在这里插入图片描述
假设试图编译和链接下面两个C模块:

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

7.6.2 与静态库链接

迄今为止,我们都是假设链接器读取一组可重定位目标文件,并把它们链接起来,形成一个输出的可执行文件
实际上,所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为 静态库(static library),它可以用做链接器的输入
当链接器构造一个输出的可执行文件时,它只复制静态库里被引用程序引用的模块

在Linux系统中,静态库以一种称为**存档(archive)**的特殊文件格式存放在磁盘中
存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置,存档文件由后缀 .a 标识

为了对库的讨论更加形象,参考下面例程:

在这里插入图片描述
要创建这些函数的一个静态库,使用ar工具:
在这里插入图片描述
为了使用这个库,我们可以编写一个应用程序:
在这里插入图片描述
在这里插入图片描述
//vector.h 是自己创建的一个头文件,里边包括那两个函数的声明
-static 参数告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件
它可以加载到内存并运行,在加载时无须更进一步的链接
-lvector 参数是 libvector.a 的缩写, -L. 参数告诉编译器在当前目录下查找libvector.a

在这里插入图片描述
上图概括了链接器的行为
当链接器运行时,它判定main2.o引用了addvec.o定义的addvec符号,所以复制addcec.o到可执行文件,因为程序不引用任何由 multvec.o 定义的符号,所以链接器就不会复制这个模块到可执行文件,链接器还会复制liba.a 中的printf.o 模块,以及许多C运行时系统中的其他模块

7.6.3 链接器如何使用静态库来解析引用

Linux 链接器使用它们解析外部引用的方式
在符号解析阶段,链接器从左到右按照它们在编译器驱动程序命令行上出现的顺序来扫描可重定位目标文件和存档文件
(驱动程序自动将命令行中所有的.c文件翻译成.o文件)在这次扫描中,链接器维护一个可重定位目标文件的集合E(在这个集合中的文件会被合并起来形成可执行文件)
一个未解析的符号(即引用了但是尚未定义的符号)集合U,以及一个在前面输入文件中已
定义的符号集合D,初始时,E、U、D均为空
在这里插入图片描述
在这里插入图片描述

7.7 重定位

一旦链接器完成了符号解析这一步,就把代码中的每个符号引用和正好一个符号定义
(即它的一个输入目标模块中的一个符号表条目)关联起来
此时,链接器就知道它的输入目标模块中的代码节和数据节的确切大小
现在就可以开始重定位步骤了,在这个步骤中,将合并输入模块,并为每个符号分配运行时地址,步骤:
在这里插入图片描述在这里插入图片描述

7.7.1 重定位条目

当汇编器生成一个目标模块时,它并不知道数据和代码最终将要放在内存的什么位置
也不知道这个模块引用的任何外部定义的函数或者全局变量的位置

所以,无论何时汇编器遇到对最终位置未知的目标引用,它就会生产一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用

代码重定位条目放在 .rel.text中,已初始化数据的重定位条目放在 .rel.text中

在这里插入图片描述
上图展示了ELF重定位条目的格式

Offset 是需要被修改的引用的节偏移
Symbol 标识被修改引用应该指向的符号
Type 告诉链接器如何修改新的引用
Addend 是一个有符号常数,一些类型的重定位要被使用它对被修改引用的值做偏移调整

在这里插入图片描述
这两种重定位类型支持x86-64小型代码模型(small code model),该模型假设可执行目标文件中的代码和数据总体大小小于2GB,因此在运行时可以用32位PC相对地址来访问
GCC默认使用小型代码模型,大于2GB的程序可以用 –mcmodel = medium(中型代码模型)
和 –mcmodel = large(大型代码模型)标志来编译

7.7.2 重定位符号引用

在这里插入图片描述
上图展示了链接器的重定位算法的伪代码

1,2行在每个节s以及与每个节关联起来的重定位条目r上迭代执行
假设每个节s是一个字节数组,每个重定位条目r是一个类型为ELF64_Rela的结构(图7-9)
还假设当算法运行时,链接器已经为每个节 (ADDR(S)表示) 和每个符号都选择了运行时地址 (ADDR(r.symbol表示)

3行计算的是需要被重定位的4字节引用的数组s中的地址
如果这个引用是PC相对寻址,那就5-9行来重定位
如果该引用使用的是绝对寻址,那就通过11-13行来重定位

以本章最开始的实例程序看下链接器如何用这个算法重定位程序的引用
用objdump –d –x main.o 产生main.o 的反汇编代码
在这里插入图片描述
Main引用了两个全局符号: array和sum,为每个引用,汇编器产生一个重定位条目
显示在引用的后面一行

这些重定位条目告诉链接器对sum的引用要使用32位PC相对地址进行重定位,而对array的引用要使用32位绝对地址进行重定位

1.重定位PC相对引用
上图第6行中,函数main调用sum函数,sum函数是在模块sum.o中定义的
CALL指令开始于 节偏移0xe 的地方,包括1字节的操作码 0xe8
后面跟着是对目标sum的32位PC相对引用的占位符
相应的重定位条目r由4个字段组成:
r.offset = 0xf
r.symbol = sum
r.type = R_x86-64_PC32
r.addend = -4
这些字段告诉链接器修改开始于偏移量 0xf 处的32位PC相对引用,这样在运行时它会指向sum例程

假设链接器已经确定
ADDR(s) = ADDR(.text) = 0x4004e8

ADDR(r.symbol) = ADDR(sum) = 0x4004e8
使用图7-10中的算法,连接器首先计算出引用的运行时地址(7行):
Refaddr = ADDR(s) + r.offset = 0x4004d0 + 0xf = 0x4004df
然后更新该引用,使得它在运行时只想sum程序(8行):
*refptr = (unsigned)(ADDR(r.symbol) + r.addend – refaddr)
= (unsigned)(0x4004e8 + (-4) – 0x4004df)
= (unsigned)(0x5)
在得到的可执行目标文件中,call指令有如下重定位的形式:
4004de: e8 05 00 00 00 callq 4004e8
在这里插入图片描述

2.重定位绝对引用
相对简单,如图7-11的第4行,mov指令将array的地址(一个32位立即数值)复制到寄存器%edi中,mov指令开始于节偏移量0x9的位置,包括1字节操作码0xbf,后面紧跟着对array的32位绝对地址引用的占位符
在这里插入图片描述
这些字段告诉链接器要修改从偏移量0xa开始的绝对引用,这样在运行时它将会指向array的第一个字节,假设链接器已经确定
ADDR(r.symbol) = ADDR(array) = 0x601018
在这里插入图片描述
在这里插入图片描述

7.8 可执行目标文件

我们的示例C程序,开始时是一组ASCLL文本文件,现在已经被转化为一个二进制文件,且这个二进制文件包含加载程序到内存并运行它所需的所有信息
在这里插入图片描述
上图概括了一个典型的ELF可执行文件中的各类信息
可执行目标文件格式类似于可重定位目标文件格式,ELF头描述文件的总体格式
它还包括程序的入口点(entry point),也就是当程序运行时要执行的第一条指令的地址

.text .rodata .data 节与重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行内存地址以外

.init 节定义了一个小函数,叫做 _init ,程序的初始化代码会调用它
因为可执行文件是完全链接的(已被重定位), 所以它不再需要 .rel 节

ELF可执行文件被设计得很容易加载到内存,可执行文件的连续的片(chunk)被映射到连续的内存段
程序头部表(program header table)描述了这种映射关系
在这里插入图片描述
上图为可执行文件prog的程序头部表,本章开始的示例程序 (OBJDUMP显示)

从程序头部,我们会看到根据可执行目标文件的内容初始化两个内存段,第1,2行
第2行告诉我们第一个段(代码段)有 读/执行访问权限,开始于内存0x400000处,总共内存大小是0x69c字节,并且被初始化为可执行目标文件的头0x69c个字节,其中包括ELF头、程序头部表以及 .init .text .rodata节

在这里插入图片描述

7.9 加载可执行目标文件
要运行可执行目标文件prog,可以在Linux Shell 的命令行输入:
./prog

因为prog不是内置Linux命令,所以会被认为是一个可执行文件
通过调用某个驻留在存储器中称为加载器(loader)的操作系统代码来运行它
任何Linux程序都可以通过调用 Execve 函数来调用加载器
加载器将可执行目标文件中的代码和数据从磁盘复制到内存中
然后通过跳转到程序的第一条指令或入口点来运行该程序,将程序复制到内存并运行的过程叫做加载
在这里插入图片描述
每个Linux程序都有一个运行时内存映像,类似上图
在Linux x86-64系统中,代码段总是从地址0x400000处开始,后面是数据段

运行时堆在数据段之后,通过调用malloc库往上增长
堆后面的区域是为共享模块保留的
用户栈总是从最大的合法用户地址开始,向较小内存地址增长
栈上的区域,从地址2^48开始,是为内核中代码和数据保留的,所谓内核就是操作系统驻留在内存的部分

当加载器运行时,它创建类似于上图所示的内存映像,在程序头部表的引导下,加载器将可执行文件的片(chunk)复制到代码段和数据段
接下来跳转到程序的入口点,也就是 _start 函数的地址,这个函数是在系统目标文件 ctrl.o中定义的,对所有C程序都是一样的
_start函数调用系统启动函数 __libc_start_main,由该函数定义在libc.so中
它初始化执行环境,调用用户层的main函数,处理main函数的返回值,并且在需要的时候把控制返回给内核

在这里插入图片描述

7.10 动态链接共享库

静态库和所有的软件都一样,需要定期维护和更新
如果应用程序员想要使用一个库的最新版本,必须以某种方式了解到该库的更新情况
然后显式地将他们的程序与更新了的库重新链接

几乎每个C程序都使用标准I/O函数,比如printf 和 scanf
在运行时,这些函数的代码会被复制到每个运行进程的文本段中
在一个运行上百个进程的典型系统上,这将是对稀缺的内存系统资源极大浪费

共享库( shared library )是致力于解决静态库缺陷的一个现代创新产物
共享库是一个目标模板,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来,这个过程称为动态链接( dynamic linking ),是由一个叫做动态链接器的程序来执行的

共享库也称为共享目标( shared object ),在Linux系统中常用 .so 后缀来表示
Windows中,他们称为DLL(动态链接库)

共享库是以两种不同的方式来“共享”的
首先,在任何给定的文件系统中,对于一个库只有一个 .so 文件
所有引用该库的可执行目标文件共享这个 .so 文件中的代码和数据
而不是像静态库的内容那样被复制和嵌入到引用他们的可执行文件中

其次,在内存中,一个共享库的.text节的一个副本可以被不同的正在运行的进程共享
在这里插入图片描述
示例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
-fpic 选项指示编译器生成与位置无关的代码
-shared 选项指示链接器创建一个共享的目标文件

一旦创建了这个库,随后就要将它链接到示例程序中,于上图shell程序中第二行

这样就创建了一个可执行目标文件 prog ,而此文件形式使得它在运行时可以和libdemo.so链接
基本思路就是当创建可执行文件时,静态执行一些链接,然后再程序加载时,动态完成链接过程
此时,没有任何libdemo.so的代码和数据节被复制到可执行文件prog中
反之,链接器复制了一些重定位和符号表信息,它们使得运行可执行文件prog时,可以解析对libdemo.so中代码和数据的引用

当加载器加载和运行可执行文件prog时,加载部分链接的可执行文件prog
接着,它注意到prog包含一个 .interp节,这一节包含动态链接器的路径名
动态链接器本身就是一个共享目标,加载器不会像它通常所做地那样将控制传递给应用
而是加载和运行这个动态链接器,然后动态链接器通过执行下面这些重定位完成链接任务:
在这里插入图片描述

7.11 从应用程序中加载和链接共享库

应用程序还可能在它运行时要求动态链接器加载和链接某个共享库
而无需在编译时将那些库链接到应用中
在这里插入图片描述
Linux系统为动态链接器提供了一个简单的接口,允许应用程序在运行时加载和链接共享库:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
示例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.12 位置无关代码

在这里插入图片描述
1.PIC数据引用
编译器通过运用以下这个有趣的事实来生成对全局变量的PIC引用:
无论我们在内存中的何处加载一个目标模板,数据段与代码段的距离总是保持不变
因此,数据段中任何指令和数据段中任何变量之间的距离都是一个运行时常量,与代码段和数据段的绝对内存位置是无关的

想要生成对全局变量PIC引用的编译器利用了这个事实,它在数据段开始的地方创建了一个表,叫做 全局偏移表(Global Offset Tab, GOT)
在GOT中,每个被这个目标模块引用的全局数据目标都有一个8字节条目
编译器还为每个条目生成一个重定位记录,在加载时动态链接器会重定位GOT中dem
在加载时,动态链接器会重定位GOT中的每个条目,使它包含目标的正确的绝对地址
在这里插入图片描述
上图展示的是 编译于 7.6.2小节的代码
这里的关键思想是对GOT[3]的PC相对引用中的偏移量是一个运行时变量

因为addcnt是由libvector.so模块定义的,编译器可以利用代码段和数据段之间不变的距离,产生对addcnt的直接PC相对引用,并增加一个重定位,让链接器在构造这个共享模块时解析它

2.PIC函数调用
在这里插入图片描述
使用延迟绑定的动机是对于一个像libc.so这样的共享库输出的成百上千函数中
一个典型的引用程序只会使用其中很少的一部分
把函数地址的解析推迟到它实际被调用的地方,能避免动态链接器在加载时进行成百上千个其实并不需要的重定位,第一次调用过程的开销很大,之后的每次调用都只会花费一条指令和一个间接的内存引用

延迟绑定是通过两个数据结构之间简洁但又有些复杂的交互实现的
GOT和过程链接表(PLT)
如果有一个目标模块调用定义在共享库中的任何函数,那么它就有自己的GOT和PLT
GOT是数据段的一部分,PLT是代码段的一部分
在这里插入图片描述
上图展示了PLT和GOT如何协作在运行时解析函数的地址
在这里插入图片描述

7.13 库打桩机制

库打桩,它允许你截获对共享库函数的调用,取而代之执行自己的代码
在这里插入图片描述
打桩可以发生在编译时、链接时、当前程序被加载和执行的运行时

7.13.1 编译时打桩

在这里插入图片描述
上图展示了如何使用C预处理器在编译时打桩

使用下面这样编译和链接这个程序:
在这里插入图片描述
由于使用 -I 参数,所以会进行打桩,他告诉C预处理器在搜索通常的系统目录之前,先在当前目录中查找malloc.h,注意mymalloc.c中的包装函数是使用标准 malloc.h头文件编译的

运行结果:
在这里插入图片描述

7.13.2 链接时打桩

在这里插入图片描述
Linux静态链接器支持用 –wrap f标志进行链接时打桩
这个标志告诉编译器 把对符号f的引用解析成 __wrap_f
还要把符号 __real_f的引用解析成 f
在这里插入图片描述
在这里插入图片描述
-Wl, option标志把 option 传递给链接器
Option中的每个逗号都要替换为一个空格
–wrap,malloc 就把 –wrap malloc 传递给链接器,以类似的方式传递
-Wl,–wrap,free

结果:
在这里插入图片描述

7.13.3 运行时打桩

运行时打桩,它只需要能够访问可执行目标文件
这个很厉害的机制基于动态链接器的LD_PRELOAD环境变量
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.14 处理目标文件工具

在这里插入图片描述

小结

在这里插入图片描述
在这里插入图片描述

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

深入理解计算机系统 --- 链接 的相关文章

  • Pandas 数据结构之 DataFrame使用教程

    Pandas 数据结构 DataFrame 简介 DataFrame 实例化 行数据的选择 1 按位置选择行数据 单行选择 2 按位置选择行数据 多行选择 类似于切片 3 按索引值选择单 多 行数据 4 按条件 bool 选择指定的行数据
  • Tomcat的简单使用

    简单使用Tomcat 1 Tomcat Web容器 1 1安装 1 2 Tomcat文件夹功能讲解 3 启动Tomcat 4 发布网页 1 Tomcat Web容器 Tomcat是用来发布前端站点或者后端程序的 1 1安装 Tomcat 的
  • kotlin_基础_枚举和密封类(sealed class)

    转载自 https blog csdn net deng hui long article details 108173544 写这篇文章之前 做了很多调研 查阅了很多资料 文章也反复推敲打磨了很多遍 为什么我要去做这么多的调研工作 因为
  • 深入理解计算机系统-链接篇

    在linux系统下 很多开源C C 程序的编译规则都是以makefile文件的形式给出 我刚开始学习makefile规则是看陈浩的 跟我一起学makefile 后来看 深入理解计算机系统 这本书 对编译链接有了更加深入的了解 本文主要介绍程
  • 队列基础使用示例与通过队列实现线程通信

    目录 一 队列基础解释 二 ConcurrentLinkedDeque 并发非阻塞式队列 三 BlockingQueue 阻塞队列 ArrayBlockingQueue LinkedBlockingQueue PriorityBlockin
  • SpringBoot 基础

    1 认识Spring Boot Spring 不同于一般框架 它是一个聚合的框架 通过Spring 框架可以使Java 更为便捷和系统化 Java web 中最为使用的框架为 Spring Framework Spring boot 是 S
  • 负载均衡入门

    提纲 ADC 行业现状 ADC 原理 ADC 的实现方式 为什么是 ADC 而不是负载均衡 功能的扩展 解决了什么问题 可用性 Availability 伸缩性 Scalability 性能 End user performance 数据中
  • 将矩阵&概率画成图

    任何一个矩阵都能画成一个图 更严谨的来说 每个矩阵对应一个加权二分图 所谓图是指点和线的集合 二分是指两种不同的类型 加权是指每条线上都有一个数字标记 上图的三个绿点代表三行 两个红点代表两列 若对应矩阵值非零 则在绿点和红点间画一条线连接
  • 线程通信基础示例(synchronized 与 Lock + Condition实现线程通信)

    目录 一 synchronized 实现线程通讯 代码示例 二 Lock Condition 实现线程通讯 代码示例 Lock Condition 实现线程通讯的优点 一 synchronized 实现线程通讯 什么是线程通讯 可以将线程分
  • 【软件测试】自动化测试战零基础教程——Python自动化从入门到实战(一)

    第一章 自动化测试基础 第一节 软件测试分类 关于软件测试领域名词颇多 发现有许多测试新手混淆概念 从不同的角度可以将软件测试有不同的分类的方法 所以 这里汇总常见软件测试的相关名词 对软件测试领域有个概括的了解 根据项目流程阶段划分软件测
  • 2021哈工大深入理解计算机系统Lab5(linklab)

    2021哈工大计算机系统lab5 linklab 实验目的 实验环境与工具 硬件环境 软件环境 开发工具 实验内容 LinkBomb程序框架 phase1 全局变量 数据节 phase2 指令 代码节 phase3 符号解析 phase4
  • 【软件测试】自动化测试战零基础教程——Python自动化从入门到实战(九)

    整理不易 希望对各位学习软件测试能带来帮助 软件测试知识持续更新 第八章 自动化测试高级应用 第一节 自动发邮件功能 8 1 1 文件形式的邮件 8 1 2 HTML 形式的邮件 8 1 3 获取测试报告 8 1 4 整合自动发邮件功能 第
  • ZooKeeper 分布式协调工具

    目录 一 ZooKeeper 概述 二 ZooKeeper Windows 单机版安装 三 ZooKeeper 集群环境下选举过程 四 ZooKeeper 存储数据的过程 五 ZooKeeper 监听 六 java 操作 ZooKeeper
  • ubuntu使用教程与常用命令

    ubuntu使用教程 一 Ubuntu简介 Ubuntu 乌班图 是一个基于Debian的以桌面应用为主的Linux操作系统 据说其名称来自非洲南部祖鲁语或科萨语的 ubuntu 一词 意思是 人性 我的存在是因为大家的存在 是非洲传统的一
  • CSAPP Lab5- MallocLab

    实验目标 本实验需要用c语言实现一个动态的存储分配器 也就是你自己版本的malloc free realloc函数 实验步骤 tar xvf malloclab handout tar解压文件 我们需要修改的唯一文件是mm c 包含如下几个
  • 牛客每日刷题

    作者简介 我是18shou 一名即将秋招的java实习生 个人主页 18shou 系列专栏 牛客刷题专栏 在线刷题面经模拟面试 目录 题目 思路 题解 题目 给定一个长度为 n 的字符串 请编写一个函数判断该字符串是否回文 如果是回文请返回
  • java file类总结

    直入正题 代码 自己可以复制去看 里面主要 介绍了文件的File类的新建 删除 重命等操作 以及File文件的属性方法 package com gx iodemo import java awt BufferCapabilities Fli
  • oracle如修改表字段的类型(表中有数据)

    如何在数据表有数据的情况下 修改字段类型 看到如何修改表字段类型 我想大多数人都觉得直接用修改语句 ALTER TABLE 表名 MODIFY 列名 类型 如果是修改多个字段就在后面继续 modify ALTER TABLE 表名 MODI
  • Android-CMakeLists.txt 链接第三方库(动态或者静态库)到自己的生成库中

    最近在做关于NDK开发的项目 编译方式通过cmake 如何将第三方动态链接库连接到自己生成的动态库中 按照以下步骤 1 首先看目录结构 首先将第三方库复制到jniLibs下 并创建对应的CUP平台目录 2 CMakeLists txt 方式
  • 设计模式概述

    设计模式的重要性 以实际工作举例 给用户开 开发完成后客户增加新的功能 例如原本程序适配两个产品 增加第三个产品 程序可扩展性 程序开发完成后的后续维护 规范性 可读性 总结 高内聚 低耦合 可维护性 可扩展性 类与类之间的关系 依赖 类A

随机推荐

  • layui下拉框select通过Ajax动态加载数据后,动态渲染选中的值

    其中分为两种 1 select中的数据是在页面中写死的 需要从后端取值后判断选中哪一个 选择select的id 直接赋值 source val data obj source layui form render select 2 动态渲染s
  • nonce, timestamp, signatrue在Http安全协议中的作用

    OAuth协议 OAuth请求头里的nonce 随机数 timestamp 时间戳 signatrue 签名 Basic认证及其安全问题 Basic认证是一个流程比较简单的协议 整个过程可以分为以下三个步骤 客户端使用GET方法向服务器请求
  • [Binospace] Google-MegaStore的解读

    MegaStore是Google在BigTable之上实现了一个跨机房高可用的数据库 它提供了类似DB的数据分布 索引的功能 实现了在EntityGroup内部以及EntityGroup之间的事务性 并且通过Paxos协议实现在DC之间多备
  • 解决Uncaught SyntaxError: Unexpected reserved word

    解决思路 首先 我运行项目报错 我查看了一下node版本 是否太低 如果是14版本的话 那么node需要升级 目前 node已经升级到19 升级到16即可 无需太高 更新完node版本之后 发现它还是报错 然后接着从网上搜报错 经历无数次的
  • 【I2C】Linux使用GPIO模拟I2C

    文章目录 1 I2C GPIO系统架构简介 2 如何使能I2C GPIO驱动 2 1 config配置 2 2 dts配置 2 3 测试验证 3 简单分析i2c gpio c驱动 3 1 解析设备树 3 2 配置SDA和SCL 3 3 注册
  • vue进度条

  • 金九银十之面试闲谈

    文章目录 前言 面试流程 资料总结 刷题指南 个人经验总结 寄语 前言 今年的金九银十带着几分不确定性来了 加上各个大厂hc的收紧 今年的金九银十很难恢复往日的 荣光 不过肯定还是有很多毕业生或者其他原因的朋友们出来找工作 面试流程 面试流
  • Sharding-JDBC分布式事务总结(四)之BASE事务(Seat框架中——AT模式的介绍以及理解)

    Sharding分布式事务之BASE事务 Seat框架中 AT模式 1 什么是BASE事务 2 Seata框架的AT模式 2 1介绍 2 2原理 2 3特性 写隔离与读隔离 AT模式的 写隔离 读隔离 2 4优势 相较于XA事务 2 5启动
  • Windows设置本地DNS域名解析Hosts文件的方法

    我们需要先了解DNS解析查询的顺序 在用户输入域名之后 DNS解析查询的顺序是下面这样的 1 浏览器会首先查看自身的缓存 如果浏览器缓存中有对应的解析记录 直接返回结果 2 如果浏览器没有缓存 电脑会查看本地操作系统的缓存 如果有记录 直接
  • 2021我们相约一起用.NET改变Windows软件世界

    目录 成为C 版主 互联网启示录 改变 NET桌面应用 从替换Application Run开始 现在 让我们开始吧 令人惊讶的FirstApp exe 新起点从第一个Web页面开始 成为C 版主 不管最终是出于什么原因 我成为了C 论坛版
  • Basic Level 1034 有理数四则运算 (20分)

    题目 本题要求编写程序 计算 2 个有理数的和 差 积 商 输入格式 输入在一行中按照 a1 b1 a2 b2 的格式给出两个分数形式的有理数 其中分子和分母全是整型范围内的整数 负号只可能出现在分子前 分母不为 0 输出格式 分别在 4
  • MEM工程管理硕士的含金量与就业前景?

    MEM工程管理硕士的含金量与就业前景 修改 13年7月毕业 工作半年 想知道MEM现在的含金量怎么样 比起普通硕士而言呢 毕业前景如何 社会认可度高不高 我现在就比较想考这个 想在多学习学习 还有没有其他较好的选择 修改 举报 1 条评论
  • [4G&5G专题-123]:5G培训部署篇-1-5G网络架构与关键技术

    作者主页 https blog csdn net HiWangWenBing 文章出处 https blog csdn net HiWangWenBing article details 118437789 目录 第1部分 5G概述 第2部
  • HDMI之EDID使用说明

    Q1 为什么要写这篇文章 A1 在最近的工作中遇到了不少问题 其中很多都是和EDID相关的 可以说 作为一家以 显示 为生的企业 我们时时刻刻在与EDID打交道 EDID这东西很简单 但是如果不了解其基本原理和概念的话 会给我们的工作带来不
  • 服务器内存占用率76%,IT运维常见问题之一:服务器内存占有率高

    登录服务器一看 服务器也很卡 打开任务管理器 一看内存占有率99 了 在仔细一查看是 数据库占用了大量内存 打开数据库一看是部署的时候没有对数据库实例设置 最大服务器内存 下面就分享一下SQL Server数据库占用过高内存的处理方法 一
  • extern详解

    extern 关键字 extern是C语言中的一个关键字 一般用在变量名前或函数名前 作用是用来说明 此变量 函数是在别处定义的 要在此处引用 extern这个关键字大部分读者应该是在变量的存储类型这一类的内容中 遇到的 下面先分析C语言不
  • java获取接口的流_Java请求Http协议接口,流式请求,流式接收

    package com test gov supervision processor gd gz import com alibaba fastjson JSON import com alibaba fastjson JSONArray
  • 2022年 hust OJ 最新搭建方式

    文章目录 一 准备环境 二 hust oj 搭建 三 踩坑 一 准备环境 1核2G 服务器一台 腾讯云 阿里云均可 现在可能买不到了 2核2G 的也可以 xshell windterm 连接服务器的工具 二 hust oj 搭建 HustO
  • 【Python百日基础系列】Day77 - Pandas可视化Cufflinks图表库(一)

    文章目录 一 Cufflinks入门 1 1 Cufflinks简介 1 2 前置安装plotly 1 3 Cufflinks安装 1 4 Pycharm解决AttributeError 1 4 1 错误现象 1 4 2 解决方法 1 4
  • 深入理解计算机系统 --- 链接

    本章目的 提供了关于链接各方面的全面讨论 从传统静态链接到加载时的共享库的动态链接 以及到运行时的共享库的动态链接 链接 linking 是将各种代码和数据片段收集并组合成一个单一文件的过程 这个文件可被加载 复制 到内存被并执行 链接可以