mysql实现程序的动态链接_程序的链接和装入及Linux下动态链接的实现

链接器和装入器的基本工作原理

一个程序要想在内存中运行,除了编译之外还要经过链接和装入这两个步骤。从程序员的角度来看,引入这两个步骤带来的好处就是可以直接在程序中使用printf和errno这种有意义的函数名和变量名,而不用明确指明printf和errno在标准C库中的地址。当然,为了将程序员从早期直接使用地址编程的梦魇中解救出来,编译器和汇编器在这当中做出了革命性的贡献。编译器和汇编器的出现使得程序员可以在程序中使用更具意义的符号来为函数和变量命名,这样使得程序在正确性和可读性等方面都得到了极大的提高。但是随着C语言这种支持分别编译的程序设计语言的流行,一个完整的程序往往被分割为若干个独立的部分并行开发,而各个模块间通过函数接口或全局变量进行通讯。这就带来了一个问题,编译器只能在一个模块内部完成符号名到地址的转换工作,不同模块间的符号解析由谁来做呢?比如前面所举的例子,调用printf的用户程序和实现了printf的标准C库显然就是两个不同的模块。实际上,这个工作是由链接器来完成的。

为了解决不同模块间的链接问题,链接器主要有两个工作要做――符号解析和重定位:

符号解析:当一个模块使用了在该模块中没有定义过的函数或全局变量时,编译器生成的符号表会标记出所有这样的函数或全局变量,而链接器的责任就是要到别的模块中去查找它们的定义,如果没有找到合适的定义或者找到的合适的定义不唯一,符号解析都无法正常完成。

重定位:编译器在编译生成目标文件时,通常都使用从零开始的相对地址。然而,在链接过程中,链接器将从一个指定的地址开始,根据输入的目标文件的顺序以段为单位将它们一个接一个的拼装起来。除了目标文件的拼装之外,在重定位的过程中还完成了两个任务:一是生成最终的符号表;二是对代码段中的某些位置进行修改,所有需要修改的位置都由编译器生成的重定位表指出。

举个简单的例子,上面的概念对读者来说就一目了然了。假如我们有一个程序由两部分构成,m.c中的main函数调用f.c中实现的函数sum:

/* m.c */

int i = 1;

int j = 2;

extern int sum();

void main()

{

int s;

s = sum(i, j);

/* f.c */

int sum(int i, int j)

{

return i + j;

}

在Linux用gcc分别将两段源程序编译成目标文件:

$ gcc -c m.c

$ gcc -c f.c

我们通过objdump来看看在编译过程中生成的符号表和重定位表:

$ objdump -x m.o

……

SYMBOL TABLE:

……

00000000 gO .data 00000004 i

00000004 gO .data 00000004 j

00000000 gF .text 00000021 main

00000000 *UND* 00000000 sum

RELOCATION RECORDS FOR [.text]:

OFFSET TYPE VALUE

00000007 R_386_32 j

0000000d R_386_32 i

00000013 R_386_PC32 sum

首先,我们注意到符号表里面的sum被标记为UND(undefined),也就是在m.o中没有定义,所以将来要通过ld(Linux下的链接器)的符号解析功能到别的模块中去查找是否存在函数sum的定义。另外,在重定位表中有三条记录,指出了在重定位过程中代码段中三处需要修改的位置,分别位于7、d和13。下面以一种更加直观的方式来看一下这三个位置:

$ objdump -dx m.o

Disassembly of section .text:

00000000 :

0: 55push %ebp

1: 89 e5mov %esp,%ebp

3: 83 ec 04sub $0x4,%esp

6: a1 00 00 00 00mov 0x0,%eax

7: R_386_32 j

b: 50push %eax

c: a1 00 00 00 00mov 0x0,%eax

d: R_386_32 i

11: 50push %eax

12: e8 fc ff ff ffcall 13

13: R_386_PC32 sum

17: 83 c4 08add $0x8,%esp

1a: 89 c0mov %eax,%eax

1c: 89 45 fcmov %eax,0xfffffffc(%ebp)

1f: c9leave

20: c3ret

以sum为例,对函数sum的调用是通过call指令实现的,使用IP相对寻址方式。可以看到,在目标文件m.o中,call指令位于从零开始的相对地址12的位置,这里存放的e8是call的操作码,而从13开始的4个字节存放着sum相对call的下一条指令add的偏移。显然,在链接之前这个偏移量是不知道的,所以将来要来修改13这里的代码。那现在这里为什么存放着0xfffffffc(注意Intel的CPU使用little endian的编址方式)呢?这大概是出于安全的考虑,因为0xfffffffc正是-4的补码表示(读者可以在gdb中使用p /x -4查看),而call指令本身占用了5个字节,因此无论如何call指令中的偏移量不可能是-4。我们再看看重定位之后call指令中的这个偏移量被修改成了什么:

$ gcc m.o f.o

$ objdump -dj .text a.out | less

Disassembly of section .text:

……

080482c4 :

……

80482d6: e8 0d 00 00 00call 80482e8

80482db: 83 c4 08add $0x8,%esp

……

080482e8 :

……

可以看到经过重定位之后,call指令中的偏移量修改成0x0000000d了,简单的计算告诉我们:0x080482e8-0x80482db=0xd。这样,经过重定位之后最终的可执行程序就生成了。

可执行程序生成后,下一步就是将其装入内存运行。Linux下的编译器(C语言)是cc1,汇编器是as,链接器是ld,但是并没有一个实际的程序对应装入器这个概念。实际上,将可执行程序装入内存运行的功能是由execve(2)这一系统调用实现的。简单来讲,程序的装入主要包含以下几个步骤:

读入可执行文件的头部信息以确定其文件格式及地址空间的大小;

以段的形式划分地址空间;

将可执行程序读入地址空间中的各个段,建立虚实地址间的映射关系;

将bbs段清零;

创建堆栈段;

建立程序参数、环境变量等程序运行过程中所需的信息;

启动运行。

链接和装入技术的发展史

一个程序要想装入内存运行必然要先经过编译、链接和装入这三个阶段,虽然是这样一个大家听起来耳熟能详的概念,在操作系统发展的过程中却已经经历了多次重大变革。简单来讲,可以将其划分为以下三个阶段:

1. 静态链接、静态装入

这种方法最早被采用,其特点是简单,不需要操作系统提供任何额外的支持。像C这样的编程语言从很早开始就已经支持分别编译了,程序的不同模块可以并行开发,然后独立编译为相应的目标文件。在得到了所有的目标文件后,静态链接、静态装入的做法是将所有目标文件链接成一个可执行映象,随后在创建进程时将该可执行映象一次全部装入内存。举个简单的例子,假设我们开发了两个程序Prog1和Prog2,Prog1由main1.c、utilities.c以及errhdl1.c三部分组成,分别对应程序的主框架、一些公用的辅助函数(其作用相当于库)以及错误处理部分,这三部分代码编译后分别得到各自对应的目标文件main1.o、utilities.o以及errhdl1.o。同样,Prog2由main2.c、utilities.c以及errhdl2.c三部分组成,三部分代码编译后分别得到各自对应的目标文件main2.o、utilities.o以及errhdl2.o。值得注意的是,这里Prog1和Prog2使用了相同的公用辅助函数utilities.o。当我们采用静态链接、静态装入的方法,同时运行这两个程序时内存和硬盘的使用情况如图1所示:

可以看到,首先就硬盘的使用来讲,虽然两个程序共享使用了utilities,但这并没有在硬盘保存的可执行程序映象上体现出来。相反,utilities.o被链接进了每一个用到它的程序的可执行映象。内存的使用也是如此,操作系统在创建进程时将程序的可执行映象一次全部装入内存,之后进程才能开始运行。如前所述,采用这种方法使得操作系统的实现变得非常简单,但其缺点也是显而易见的。首先,既然两个程序使用的是相同的utilities.o,那么我们只要在硬盘上保存utilities.o的一份拷贝应该就足够了;另外,假如程序在运行过程中没有出现任何错误,那么错误处理部分的代码就不应该被装入内存。因此静态链接、静态装入的方法不但浪费了硬盘空间,同时也浪费了内存空间。由于早期系统的内存资源十分宝贵,所以后者对早期的系统来讲更加致命。

2. 静态链接、动态装入

既然采用静态链接、静态装入的方法弊大于利,我们来看看人们是如何解决这一问题的。由于内存紧张的问题在早期的系统中显得更加突出,因此人们首先想到的是要解决内存使用效率不高这一问题,于是便提出了动态装入的思想。其想法是非常简单的,即一个函数只有当它被调用时,其所在的模块才会被装入内存。所有的模块都以一种可重定位的装入格式存放在磁盘上。首先,主程序被装入内存并开始运行。当一个模块需要调用另一个模块中的函数时,首先要检查含有被调用函数的模块是否已装入内存。如果该模块尚未被装入内存,那么将由负责重定位的链接装入器将该模块装入内存,同时更新此程序的地址表以反应这一变化。之后,控制便转移到了新装入的模块中被调用的函数那里。

动态装入的优点在于永远不会装入一个使用不到的模块。如果程序中存在着大量像出错处理函数这种用于处理小概率事件的代码,使用这种方法无疑是卓有成效的。在这种情况下,即使整个程序可能很大,但是实际用到(因此被装入到内存中)的部分实际上可能非常小。

仍然以上面提到的两个程序Prog1和Prog2为例,假如Prog1运行过程中出现了错误而Prog2在运行过程中没有出现任何错误。当我们采用静态链接、动态装入的方法,同时运行这两个程序时内存和硬盘的使用情况如图2所示:

图 2采用静态链接、动态装入方法,同时运行Prog1和Prog2时内存和硬盘的使用情况

可以看到,当程序中存在着大量像错误处理这样使用概率很小的模块时,采用静态链接、动态装入的方法在内存的使用效率上就体现出了相当大的优势。到此为止,人们已经向理想的目标迈进了一部,但是问题还没有完全解决――内存的使用效率提高了,硬盘呢?

3. 动态链接、动态装入

采用静态链接、动态装入的方法后看似只剩下硬盘空间使用效率不高的问题了,实际上内存使用效率不高的问题仍然没有完全解决。图2中,既然两个程序用到的是相同的utilities.o,那么理想的情况是系统中只保存一份utilities.o的拷贝,无论是在内存中还是在硬盘上,于是人们想到了动态链接。

在使用动态链接时,需要在程序映象中每个调用库函数的地方打一个桩(stub)。stub是一小段代码,用于定位已装入内存的相应的库;如果所需的库还不在内存中,stub将指出如何将该函数所在的库装入内存。

当执行到这样一个stub时,首先检查所需的函数是否已位于内存中。如果所需函数尚不在内存中,则首先需要将其装入。不论怎样,stub最终将被调用函数的地址替换掉。这样,在下次运行同一个代码段时,同样的库函数就能直接得以运行,从而省掉了动态链接的额外开销。由此,用到同一个库的所有进程在运行时使用的都是这个库的同一份拷贝。

下面我们就来看看上面提到的两个程序Prog1和Prog2在采用动态链接、动态装入的方法,同时运行这两个程序时内存和硬盘的使用情况(见图3)。仍然假设Prog1运行过程中出现了错误而Prog2在运行过程中没有出现任何错误。

图 3采用动态链接、动态装入方法,同时运行Prog1和Prog2时内存和硬盘的使用情况

图中,无论是硬盘还是内存中都只存在一份utilities.o的拷贝。内存中,两个进程通过将地址映射到相同的utilities.o实现对其的共享。动态链接的这一特性对于库的升级(比如错误的修正)是至关重要的。当一个库升级到一个新版本时,所有用到这个库的程序将自动使用新的版本。如果不使用动态链接技术,那么所有这些程序都需要被重新链接才能得以访问新版的库。为了避免程序意外使用到一些不兼容的新版的库,通常在程序和库中都包含各自的版本信息。内存中可能会同时存在着一个库的几个版本,但是每个程序可以通过版本信息来决定它到底应该使用哪一个。如果对库只做了微小的改动,库的版本号将保持不变;如果改动较大,则相应递增版本号。因此,如果新版库中含有与早期不兼容的改动,只有那些使用新版库进行编译的程序才会受到影响,而在新版库安装之前进行过链接的程序将继续使用以前的库。这样的系统被称作共享库系统。

Linux下动态链接的实现

如今我们在Linux下编程用到的库(像libc、QT等等)大多都同时提供了动态链接库和静态链接库两个版本的库,而gcc在编译链接时如果不加-static选项则默认使用系统中的动态链接库。对于动态链接库的原理大多数的书本上只是进行了泛泛的介绍,在此笔者将通过在实际系统中反汇编出的代码向读者展示这一技术在Linux下的实现。

下面是个最简单的C程序hello.c:

#include

int main()

{

printf("Hello, world\n");

return 0;

}

在Linux下我们可以使用gcc将其编译成可执行文件a.out:

$ gcc hello.c

程序里用到了printf,它位于标准C库中,如果在用gcc编译时不加-static的话,默认是使用libc.so,也就是动态链接的标准C库。在gdb中可以看到编译后printf对应如下代码 :

$ gdb -q a.out

(gdb) disassemble printf

Dump of assembler code for function printf:

0x8048310 : jmp *0x80495a4

0x8048316 : push $0x18

0x804831b : jmp 0x80482d0 <_init>

这也就是通常在书本上以及前面提到的打桩(stub)过程,显然这并不是真正的printf函数。这段stub代码的作用在于到libc.so中去查找真正的printf。

(gdb) x /w 0x80495a4

0x80495a4 <_global_offset_table_>: 0x08048316

可以看到0x80495a4处存放的0x08048316正是pushl $0x18这条指令的地址,所以第一条jmp指令没有起到任何作用,其作用就像空操作指令nop一样。当然这是在我们第一次调用printf时,其真正的作用是在今后再次调用printf时体现出来的。第二条jmp指令的目的地址是plt,也就是procedure linkage table,其内容可以通过objdump命令查看,我们感兴趣的就是下面这两条对程序的控制流有影响的指令:

$ objdump -dx a.out

……

080482d0 >.plt>:

80482d0: ff 35 90 95 04 08 pushl 0x8049590

80482d6: ff 25 94 95 04 08 jmp *0x8049594

……

第一条push指令将got(global offset table)中与printf相关的表项地址压入堆栈,之后jmp到内存单元0x8049594中所存放的地址0x4000a960处。这里需要注意的一点是,在查看got之前必须先将程序a.out启动运行,否则通过gdb中的x命令在0x8049594处看到的结果是不正确的。

(gdb) b main

Breakpoint 1 at 0x8048406

(gdb) r

Starting program: a.out

Breakpoint 1, 0x08048406 in main ()

(gdb) x /w 0x8049594

0x8049594 <_global_offset_table_>: 0x4000a960

(gdb) disassemble 0x4000a960

Dump of assembler code for function _dl_runtime_resolve:

0x4000a960 <_dl_runtime_resolve>: pushl %eax

0x4000a961 <_dl_runtime_resolve>: pushl %ecx

0x4000a962 <_dl_runtime_resolve>: pushl %edx

0x4000a963 <_dl_runtime_resolve>: movl 0x10(%esp,1),%edx

0x4000a967 <_dl_runtime_resolve>: movl 0xc(%esp,1),%eax

0x4000a96b <_dl_runtime_resolve>: call 0x4000a740

0x4000a970 <_dl_runtime_resolve>: popl %edx

0x4000a971 <_dl_runtime_resolve>: popl %ecx

0x4000a972 <_dl_runtime_resolve>: xchgl %eax,(%esp,1)

0x4000a975 <_dl_runtime_resolve>: ret $0x8

0x4000a978 <_dl_runtime_resolve>: nop

0x4000a979 <_dl_runtime_resolve>: leal 0x0(%esi,1),%esi

End of assembler dump.

前面三条push指令执行之后堆栈里面的内容如下:

下面将0x18存入edx,0x8049590存入eax,有了这两个参数,fixup就可以找到printf在libc.so中的地址。当fixup返回时,该地址已经保存在了eax中。xchg指令执行完之后堆栈中的内容如下:

最妙的要数接下来的ret指令的用法,这里ret实际上被当成了call来使用。ret $0x8之后控制便转移到了真正的printf函数那里,并且清掉了堆栈上的0x18和0x8049584这两个已经没用的参数,这时堆栈便成了下面的样子:

而这正是我们所期望的结果。应该说这里ret的用法与Linux内核启动后通过iret指令实现由内核态切换到用户态的做法有着异曲同工之妙。很多人都听说过中断指令int可以实现用户态到内核态这种优先级由低到高的切换,在接受完系统服务后iret指令负责将优先级重新降至用户态的优先级。然而系统启动时首先是处于内核态高优先级的,Intel i386并没有单独提供一条特殊的指令用于在系统启动完成后降低优先级以运行用户程序。其实这个问题很简单,只要反用iret就可以了,就像这里将ret当作call使用一样。另外,fixup函数执行完还有一个副作用,就是在got中与printf相关的表项(也就是地址为0x80495a4的内存单元)中填上查找到的printf函数在动态链接库中的地址。这样当我们再次调用printf函数时,其地址就可以直接从got中得到,从而省去了通过fixup查找的过程。也就是说got在这里起到了cache的作用。

一点感想

其实有很多东西只要勤于思考,还是能够自己悟出一些道理的。国外有一些高手就是通过能够大家都能见到的的一点点资料,自己摸索出来很多不为人知的秘密。像写《Undocument Dos》和《Undocment Windows》的作者,他就为我们树立了这样的榜样!

学习计算机很关键的一点在于一定要富于探索精神,要让自己做到知其然并知其所以然。侯先生在《STL源码剖析》一书开篇题记中写到"源码之前,了无秘密",当然这是在我们手中掌握着源码的情况下,如若不然,不要忘记Linux还为我们提供了大量的像gdb、objdump这样的实用工具。有了这些得力的助手,即使没有源码,我们一样可以做到"了无秘密"。

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

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

相关文章

python 二进制流_Python中对字节流/二进制流的操作:struct模块简易使用教程

前言前段时间使用Python解析IDX文件格式的MNIST数据集&#xff0c;需要对二进制文件进行读取操作&#xff0c;其中我使用的是struct模块。查了网上挺多教程都写的挺好的&#xff0c;不过对新手不是很友好&#xff0c;所以我重新整理了一些笔记以供快速上手。注&#xff1a;教程…

react 图片放在src里面还是public_手写Webpack从0编译Vue/React项目

当前前端开发&#xff0c;90%的项目都是Vue和React&#xff0c;然而70%的同学都基于脚手架创建项目&#xff0c;因为脚手架会包含项目基本框架、webpack配置、scss/sass/less解析、babel配置、DevServer、JSX/Vue文件解析、CSS前缀等&#xff0c;我们要做的就是开发功能模块&am…

python union函数_如何掌握Python union()方法及怎么用?

不断学习python的过程里&#xff0c;总能遇到各种形形色色的函数或者方法&#xff0c;本章给大家带来python union的用法&#xff0c;具体内容如下&#xff1a;union()方法描述&#xff1a;union() 取并集&#xff0c;效果等同于 | &#xff0c;重复元素只会出现一次&#xff0…

输入分钟输出小时python_输出键,值对如何使1小时内的时间在使用Python的MapReduce中的reducer中结束?...

这是一个策略&#xff1a;来自Mapper的&#xff1a;发出每个记录的三个副本并使用二级排序&#xff1a;((复合键)&#xff0c;值)((消息小时 - 一小时&#xff0c;当前消息的精确时间)&#xff0c;消息)((消息小时&#xff0c;消息的准确时间)&#xff0c;消息)((消息小时1小时…

python 在线预览文件_用Python PyQt写一个在线预览图片的GUI

在爬完网上一篇帖子&#xff0c;并得到其中的所有图片链接后&#xff0c;写一个GUI来实现在线预览是一个很自然的想法&#xff0c; 相当于实现一个python版的图片浏览器&#xff0c; 通过这个练习&#xff0c;可以让我们更熟悉PyQt这个库。这里我用的是PyQt4。以下是我的写的程…

python怎样安装模块_python中如何安装模块

下面介绍几种安装Python模块的几种方式方法1&#xff1a;easy_install 方式先下载ez_setup.py,运行python ez_setup 进行easy_install工具的安装&#xff0c;之后就可以使用easy_install进行安装package了。本文安装的是Python 2.7.13版本&#xff0c;已经自带了easy_install。…

java rt_java中rt包中源码了解

javap –verbose class名 查看class文件的具体内容javap -c class名继续看io类接口 java.io.Closeable功能&#xff1a;关闭流和相应的资源java.io.console功能&#xff1a;使用字节控制台&#xff0c;与当前的java virtual machine 相关java.io.DataInput功能&#xff1a;从二…

google 确定某点海拔高_一份“高投资回报率”的用户体验度量方法指南

本文核心就是介绍体验度量方法&#xff0c;以及如何在商业项目中如何发起一个具有高ROI(投资回报率)的用户体验量化流程。 下面文章将分为解读高投资回报和拆解体验度量、实际案例讲解三部分。一、解读高投资回报率高ROI(投资回报率)来定义体验度量流程的原因&#xff1f;3-5年…

md5 java代码_JAVA简单实现MD5注册登录加密实例代码

开发环境&#xff1a;jdk1.7&#xff0c;eclipse框架&#xff1a;springmvc&#xff0c;mybatis工具&#xff1a;maven以下代码复制即可实现MD5加密创建一个mave项目&#xff0c;加web。不懂得可以搜索一下就有了。注册用户的JSP页面代码如下。pageEncoding"utf-8"%&…

一维卷积神经网络_序列特征的处理方法之二:基于卷积神经网络方法

前言上一篇文章介绍了基本的基于注意力机制方法对序列特征的处理&#xff0c;这篇主要介绍一下基本的基于卷积神经网络方法对序列特征的处理&#xff0c;也就是TextCNN方法。序列特征的介绍&#xff0c;背景以及应用可以参考上一篇的详细介绍&#xff0c;这里简单回顾一下定义&…

java socket 阻塞模式_(四) 如何将socket设置为非阻塞模式

1. windows平台上无论利用socket()函数还是WSASocket()函数创建的socket都是阻塞模式的&#xff1a;SOCKET WSAAPI socket( _In_ int af, _In_ int type, _In_ int protocol ); SOCKET WSASocket( _In_ int af, _In_ int t…

python中的pygame模块使用方法_Pygame的基本使用

Pygame有很多模块&#xff0c;每个模块又有很多方法&#xff0c;在此不能够逐一讲解&#xff0c;所以&#xff0c;我们通过一个实例来学习Pygame&#xff0c;然后再分解代码&#xff0c;讲解代码中的模块。例&#xff1a;制作一个跳跃的小球游戏。创建一个游戏窗口&#xff0c;…

java mongodb 插入数据_mongoDB 插入数据 用java实现

import java.net.UnknownHostException;import com.mongodb.BasicDBObject;import com.mongodb.DB;import com.mongodb.DBCollection;import com.mongodb.DBObject;import com.mongodb.Mongo;/** *用java 往mongoDB插入数据 * author wwd* */public class InsertData {publi…

python的特征提取实验一_Spark 2.1.0 入门:特征抽取 — TF-IDF(Python版)

这一部分我们主要介绍和特征处理相关的算法&#xff0c;大体分为以下三类&#xff1a;特征抽取&#xff1a;从原始数据中抽取特征特征转换&#xff1a;特征的维度、特征的转化、特征的修改特征选取&#xff1a;从大规模特征集中选取一个子集特征提取TF-IDF (HashingTF and IDF)…

java addlast_Java中的LinkedList addLast()方法: java.util.LinkedList.addLast() - Break易站

Java中的java.util.LinkedList.addLast()方法用于在LinkedList的末尾插入特定元素。句法&#xff1a;void addLast(Object element)参数&#xff1a;此函数接受单个参数元素&#xff0c;如上面的语法所示。此参数指定的元素将附加在列表的末尾。返回值&#xff1a;此方法不返回…

macos降级_iOS12.3 beta2更新了什么 iOS12.3测试版2新特性与升降级方法

4月0日凌晨&#xff0c;苹果发布了iOS12.3 beta2&#xff0c;作为iOS12.3第二个测试版&#xff0c;相比前一个版本&#xff0c;发布时间间隔近2周&#xff0c;这次依然是小版本更新&#xff0c;不过相对良心一些&#xff0c;主要是多了一些与国内用户相关的东西。iOS12.3 beta …

java技术教程视频_Spring开发视频教程高级篇+源码(400M)33讲

Spring开发视频教程高级篇源码(400M)33讲01_全面阐释Spring及其各项功能.rar 02_搭建与测试Spring的开发环境.rar 03_编码剖析Spring管理Bean的原理.rar 04_Spring的三种实例化Bean的方式.rar 05_配置Spring管理的bean的作用域.rar 06_Spring管理的Bean的生命周期.rar 07_编码剖…

python的常见矩阵除法_Numpy矩阵除法返回所有零

我对下面的矩阵有个除法错误。我想用行和的101向量除以1010matrix。在[[5731, 3, 20, 8, 12, 54, 46, 8, 39, 2],[ 2, 6472, 47, 24, 7, 44, 7, 11, 116, 12],[ 55, 36, 5296, 104, 84, 27, 106, 53, 183, 14],[ 50, 49, 132, 5312, 2, 253, 36, 58, 142, 97],[ 16, 28, 36, 9,…

java rc2加密_急求java RC2加密算法

下面是一段C RC2加密 要求要用java 重写 能互相加密解密QSBEncryptRc2::QSBEncryptRc2(){EncryKey "DingXin Communication Key 20080613";}//解密失败时返回失败描述AnsiString QSBEncryptRc2::GetDecryptErrMsg(){int ErrorCode;AnsiString ErrMsg;ErrorCode …

linux配置usb主从_杂集:浅谈关于Mongodb数据库主从复制

Linux下Mongodb数据库主从复制配置Mongodb的三种集群搭建的方式&#xff1a;Master-Slaver&#xff1a;主从[目前被副本集取代]。Replica Set&#xff1a;副本集。Sharding&#xff1a;切片。Mongodb单实例缺点&#xff1a;适合简易开发时使用&#xff0c;生产使用不行&#xf…