2-2-18-7 QNX 系统架构-动态链接

阅读前言

本文以QNX系统官方的文档英文原版资料为参考,翻译和逐句校对后,对QNX操作系统的相关概念进行了深度整理,旨在帮助想要了解QNX的读者及开发者可以快速阅读,而不必查看晦涩难懂的英文原文,这些文章将会作为一个或多个系列进行发布,从遵从原文的翻译,到针对某些重要概念的穿插引入,以及再到各个重要专题的梳理,大致分为这三个层次部分,分不同的文章进行发布,依据这样的原则进行组织,读者可以更好的查找和理解。


1. 动态链接

在一个典型的系统中,将运行许多程序。每个程序都依赖于许多函数,其中一些是“标准的”C库函数,比如printf(), malloc(), write()等。

如果每个程序都使用标准C库,则通常每个程序都会在其内部拥有该C库的唯一副本。不幸的是,这将会导致资源的浪费。由于C库是通用的,因此让每个程序引用该库的公共实例比让每个程序包含该库的副本更有意义。这种方法有几个优点,其中最重要的是节省了总的所需的系统内存。

在我们进一步讨论之前,我们应该先看看以下一些术语:

  • Linker

链接器,是一种工具,比如 ld,通常在编译程序后立即运行,以便组合对象文件【object files】和归档文件【archive files】,重新定位它们的数据,并解析符号引用【symbol reference】。

  • Runtime linker

运行时链接器,是一种在运行程序时查找并加载共享对象的工具。运行时链接器,也被称为动态链接器【dynamic linker】,但我们会使用运行时链接器【runtime linker】,而不是【dynamic linker】,从而避免与(非运行时)链接器所做的动态链接【dynamic linking】的概念相混淆。

运行时链接器的名称是ldd(同时ldd也是列举程序所需共享对象的 utility【实用工具】 的名称)。在ELF文件的.interp部分中,对于32位目标系统,它被称为/usr/lib/ldqx.so,对于64位目标系统,它被称为/usr/lib/ldqnx-64.so。您需要在OS映像中包含相应的版本;有关详细信息,请参阅Utilities Reference中的 mkifs 条目。

  • Statically linked

静态链接,表示程序和它所链接的特定的库,在链接时由链接器进行组合。

这意味着程序和特定库之间的绑定是固定的,并且在链接时就知道了(也就是在程序运行之前就知道了)。这也意味着我们不能改变这种绑定关系,除非我们用新版本的库重新链接程序。

如果您不确定库的正确版本是否在运行时可用,或者您正在测试库的新版本,而您还不想将其作为共享方式进行安装,则可以考虑静态链接该程序。

静态链接的程序是根据对象(库)的存档【archive】进行链接的,这些对象(库)的扩展名通常为.a。这种对象集合的一个例子是标准C库,libc.a。

  • Dynamically linked

动态链接,表示程序和它所引用的特定库,在链接时不会被链接器组合起来。

相反,链接器将信息放入可执行文件中,告诉加载器,代码位于哪个共享对象模块中,以及应该使用哪个运行时链接器来查找和绑定引用。这意味着程序和共享对象之间的绑定是在运行时完成的(在程序启动之前,找到并绑定适当的共享对象)。

这种类型的程序被称为部分绑定可执行程序【partially bound executable】,因为它不是完全解析的:链接器在链接时,并没有使程序中的所有引用符号与库中的特定代码相关联。相反,链接器只是说:“这个程序在一个特定的共享对象中调用一些函数,所以我将记录下这些函数在哪个共享对象中,然后继续。” 实际上,这将绑定操作延迟到运行时进行。

动态链接的程序,是针对具有.so扩展名的共享对象进行链接的。这种对象的一个例子是标准C库的共享对象版本,libc.so。

您可以使用编译器驱动程序 qcc 的命令行选项来告诉工具链,您是静态链接还是动态链接。然后该命令行选项就决定了所使用的扩展名(是.a还是.so)。

  • Augmenting code at runtime【在运行时扩充代码】

进一步来说,程序在运行之前可能并不知道需要调用哪些函数。虽然这初看有点奇怪(毕竟,一个程序怎么可能不知道它要调用什么函数呢?),但它确实是一个非常强大的特性。这是为什么。

比如我们来看一个“通用”磁盘驱动程序。启动,探测硬件,并检测到硬盘。然后,驱动程序动态加载 io-blk 代码来处理磁盘块,因为它找到了一个面向块【block-oriented】的设备。现在驱动程序开始以块级别访问磁盘,它发现磁盘上存在两个分区:一个DOS分区和一个Power-Safe分区。我们没有强制磁盘驱动程序,必须包含所有它可能遇到的,所有分区类型的文件系统驱动程序,而是尽量保持简单:它(磁盘驱动程序)没有任何的文件系统驱动程序!在运行时,磁盘驱动程序检测到两个分区,然后知道应该加载 fs-dos.so 和 fs-qnx6.so 文件系统代码,来处理这些分区。

通过推迟决定调用哪些函数,我们增强了磁盘驱动程序的灵活性(同时也减小了它的大小)。

1.1. 如何使用共享对象

为了理解程序如何使用共享对象,让我们首先看看可执行文件的格式,然后检查程序启动时发生的步骤。

ELF format

QNX Neutrino RTOS 使用 ELF(Executable and Linking Format)二进制格式。ELF不仅简化了创建共享库的任务,而且还增强了运行时模块的动态加载。

在下图中,我们展示了ELF文件的两个视图:链接视图和执行视图。链接视图在链接程序或链接库时使用,它处理对象文件中的节【sections】。节【sections】包含大量的对象文件信息:数据【data】、指令【instructions】、重定位信息【relocation information】、符号【symbols】、调试信息【debugging information】等。在程序运行时使用执行视图,执行视图则处理段【segments】。

在链接时,通过将具有相似属性的节【sections】合并为段【segments】来构建程序或库。通常,所有可执行数据节【executable data sections】和只读数据节【read-only data sections】,合并为单个文本段【text segment】,而 data 及 “BSS” 则合并为 data 段【data segment】。这些段称为加载段【load segments】,因为它们需要在进程创建时加载到内存中。其他部分,如符号信息【symbol information】和调试节【debugging sections】被合并到其他非加载段中。

对象文件格式:链接视图和执行视图
对象文件格式:链接视图和执行视图

ELF without COFF

大多数 ELF加载器 的实现,都派生自 COFF(Common Object File Format,通用对象文件格式)加载器;它们在加载时使用 ELF对象 的链接视图。这是低效的,因为程序加载器必须使用节加载可执行文件。一个典型的程序可能包含大量的节,每个节都必须位于程序中并分别加载到内存中。

然而,QNX Neutrino 完全不依赖于加载节【sections】的 COFF 技术。在开发我们的 ELF 实现时,我们直接根据 ELF 规范工作,并将效率放在了首位。该ELF加载器使用了程序的“执行视图”。通过这样做,加载程序的任务大大简化:它所要做的就是将程序或库的加载段【load segments】(通常是两个)复制到内存中。因此,进程创建和库的加载操作要比之前快得多。

1.2. 典型进程的内存布局

下图显示了一个典型进程的内存布局。进程加载段(对应于图中的 text段 和data段),在进程的基地址进行加载。主栈【main stack】位于正下方并向下扩展。任何被创建的其他线程都有它们自己的栈【stack】,位于主栈【main stack】的下方。每个栈【stack】由一个保护页【guard page】进行分隔,以检测栈溢出。堆【heap】位于进程加载段【load segments】上方并向上增长。(load segments,代表的是图中的data segment 和 text segment。)

在x86系统上的进程内存布局
在x86系统上的进程内存布局

在进程地址空间的中间,为共享对象保留了一个很大的区域。共享库位于地址空间的顶部并向下增长。

创建新进程时,进程管理器首先将可执行文件中的两个段映射到内存中。然后对程序的ELF头进行解码。如果程序头表明可执行文件链接到了共享库,则进程管理器将从程序头中提取动态解释器【dynamic interpreter】的名称。动态解释器指向包含了运行时链接器【runtime linker】代码的共享库【shared library】。进程管理器将在内存中加载此共享库【shared library】,然后将控制权传递给此共享库中的运行时链接器代码。

1.3. 运行时链接器

当启动指向共享对象的程序时,或程序请求动态加载共享对象时,将调用运行时链接器【runtime linker】。此链接器包含在C运行时库中。

运行时链接器【runtime linker】在加载共享库(.so文件)时,执行如下几个任务:

如果请求的共享库尚未加载到内存中,则运行时链接器将会加载它:

如果共享库名称是完全限定的【fully qualified】(即以斜杠开头,类似于“绝对路径”),则直接从指定位置加载它。如果在那里找不到,则不再执行进一步的搜索。

如果它不是一个完全限定的路径名,链接器将按照如下方式搜索它:

如果可执行文件的动态节【dynamic section】包含 DT_RPATH 标记,则搜索 DT_RPATH 指定的路径。

如果没有找到共享库,运行时链接器将在 LD_LIBRARY_PATH 指定的目录中搜索它。

如果仍然没有找到共享库,那么链接器将搜索由 LD_LIBRARY_PATH 环境变量 指定的默认库搜索路径(即 CS_LIBPATH 配置字符串)。如果没有指定,则默认库路径设置为映像文件系统的路径。

一旦找到请求的共享库,就将其加载到内存中。对于 ELF 共享库,运行时链接器只需要使用两次 mmap() 调用,来将两个加载段映射到内存中,是一个非常高效的操作。

然后将共享库添加到进程已加载的所有库的内部列表中。运行时链接器【runtime linker】负责维护这个列表。

然后运行时链接器【runtime linker】对共享对象的动态节【dynamic section】进行解码。

此动态节【dynamic section】向链接器提供有关此库所链接的其他库的信息。它还提供了有关需要应用的重定位信息和需要解析的外部符号的信息。运行时链接器,将会首先加载任何其他的所需共享库(这些共享库本身可能也会引用再其他的共享库)。然后,它将处理每个库的重定位。其中一些重定位是库的本地重定位,而另一些重定位则需要运行时链接器解析全局符号。在后一种情况下,链接器将在库列表中搜索该符号。在ELF文件中,使用哈希表进行符号查找,因此哈希表查找非常快。在库中搜索符号的顺序非常重要,我们将在后面的 “Symbol name resolution” 章节中看到这一点。

一旦应用了所有重定位,就会调用在共享库的 init section 中注册的所有初始化函数。在某些 c++ 实现中,也用于调用全局构造函数。

1.4. 在运行时加载共享库

进程可以通过使用 dlopen() 调用,在运行时加载共享库,该调用会指示运行时链接器【runtime linker】加载此库。一旦加载了此库,程序就可以通过使用 dlsym() 调用来确定其地址,从而调用该库中的任何函数。

程序还可以通过使用 dladdr() 调用来确定与给定地址相关联的符号。最后,当进程不再需要共享库时,它可以调用 dlclose() 从内存中卸载库。

1.5. 符号名称解析

当运行时链接器加载共享库时,必须解析该库中的符号。符号解析的顺序和范围很重要。如果一个共享库调用的函数恰好在程序加载的几个库中以相同的名称存在,那么在这些库中搜索该符号的顺序是至关重要的。这就是为什么OS会定义多个可以在加载库时所使用的选项。

所有具有全局作用域的对象(可执行文件【executables】和库【bibraries】)都存储在一个内部列表(全局列表)中。默认情况下,任何全局作用域对象都会使其所有符号对加载的任何共享库可用。全局列表最初包含了在程序启动时所加载的可执行文件和所有库。

默认情况下,当使用 dlopen() 调用加载新的共享库时,该库中的符号将通过按以下顺序搜索来进行解析:

由LD_PRELOAD环境变量 指定的库列表。您可以在运行程序时,使用此环境变量来添加或更改功能。

共享库

全局列表

共享库所引用的任何依赖对象(即,共享库链接到的任何其他库)

当使用dlopen()打开共享库时,运行时链接器【runtime linker】的作用域范围,可以通过两种方式改变:

当程序加载一个新库时,它可以通过将RTLD_GLOBAL标志传递给dlopen()调用的方式,来指示运行时链接器将库的符号放在全局列表中。这将使得该库的符号对随后加载的任何库都可用。

对共享库中符号进行解析时,会搜索对象列表,可以使用修改该对象列表的方式。如果将RTLD_GROUP标志传递给dlopen(),那么只有该库直接引用的对象才会在其中进行符号搜索。如果传递RTLD_WORLD标志,则只搜索全局列表中的对象。

2. 梳理理解与总结

在一个典型的系统中,将运行许多程序。每个程序都依赖于许多函数,其中一些是“标准的”C库函数,比如printf(), malloc(), write()等。

如果每个程序都使用标准C库,则通常每个程序都会在其内部拥有该C库的唯一副本。不幸的是,这将会导致资源的浪费。由于C库是通用的,因此让每个程序引用该库的公共实例比让每个程序包含该库的副本更有意义。这种方法有几个优点,其中最重要的是节省了总的所需的系统内存。

Linker

链接器,是一种工具,比如 ld,通常在编译程序后立即运行,以便组合对象文件【object files】和归档文件【archive files】,重新定位它们的数据,并解析符号引用【symbol reference】。

Runtime linker

运行时链接器,是一种在运行程序时查找并加载共享对象的工具。运行时链接器,也被称为动态链接器【dynamic linker】,但我们会使用运行时链接器【runtime linker】,而不是【dynamic linker】,从而避免与(非运行时)链接器所做的动态链接【dynamic linking】的概念相混淆。

运行时链接器的名称是ldd(同时ldd也是列举程序所需共享对象的 utility【实用工具】 的名称)。

Statically linked

静态链接,表示程序和它所链接的特定的库,在链接时由链接器进行组合。

这意味着程序和特定库之间的绑定是固定的,并且在链接时就知道了(也就是在程序运行之前就知道了)。这也意味着我们不能改变这种绑定关系,除非我们用新版本的库重新链接程序。

如果您不确定库的正确版本是否在运行时可用,或者您正在测试库的新版本,而您还不想将其作为共享方式进行安装,则可以考虑静态链接该程序。

静态链接的程序是根据对象(库)的存档【archive】进行链接的,这些对象(库)的扩展名通常为.a。这种对象集合的一个例子是标准C库,libc.a。

Dynamically linked

动态链接,表示程序和它所引用的特定库,在链接时不会被链接器组合起来。

相反,链接器将信息放入可执行文件中,告诉加载器,代码位于哪个共享对象模块中,以及应该使用哪个运行时链接器来查找和绑定引用。这意味着程序和共享对象之间的绑定是在运行时完成的(在程序启动之前,找到并绑定适当的共享对象)。

这种类型的程序被称为部分绑定可执行程序【partially bound executable】,因为它不是完全解析的:链接器在链接时,并没有使程序中的所有引用符号与库中的特定代码相关联。相反,链接器只是说:“这个程序在一个特定的共享对象中调用一些函数,所以我将记录下这些函数在哪个共享对象中,然后继续。” 实际上,这将绑定操作延迟到运行时进行。

动态链接的程序,是针对具有.so扩展名的共享对象进行链接的。这种对象的一个例子是标准C库的共享对象版本,libc.so。

您可以使用编译器驱动程序 qcc 的命令行选项来告诉工具链,您是静态链接还是动态链接。然后该命令行选项就决定了所使用的扩展名(是.a还是.so)。

2.1. 如何使用共享对象

ELF format

QNX Neutrino RTOS 使用 ELF(Executable and Linking Format)二进制格式。ELF不仅简化了创建共享库的任务,而且还增强了运行时模块的动态加载。

在下图中,我们展示了ELF文件的两个视图:链接视图和执行视图。链接视图在链接程序或链接库时使用,它处理对象文件中的节【sections】。节【sections】包含大量的对象文件信息:数据【data】、指令【instructions】、重定位信息【relocation information】、符号【symbols】、调试信息【debugging information】等。在程序运行时使用执行视图,执行视图则处理段【segments】。

在链接时,通过将具有相似属性的节【sections】合并为段【segments】来构建程序或库。通常,所有可执行数据节【executable data sections】和只读数据节【read-only data sections】,合并为单个文本段【text segment】,而 data 及 “BSS” 则合并为 data 段【data segment】。这些段称为加载段【load segments】,因为它们需要在进程创建时加载到内存中。其他部分,如符号信息【symbol information】和调试节【debugging sections】被合并到其他非加载段中。

对象文件格式:链接视图和执行视图
对象文件格式:链接视图和执行视图

2.2. 典型进程的内存布局

下图显示了一个典型进程的内存布局。进程加载段(对应于图中的 text段 和data段),在进程的基地址进行加载。主栈【main stack】位于正下方并向下扩展。任何被创建的其他线程都有它们自己的栈【stack】,位于主栈【main stack】的下方。每个栈【stack】由一个保护页【guard page】进行分隔,以检测栈溢出。堆【heap】位于进程加载段【load segments】上方并向上增长。

在x86系统上的进程内存布局

在进程地址空间的中间,为共享对象保留了一个很大的区域。共享库位于地址空间的顶部并向下增长。

创建新进程时,进程管理器首先将可执行文件中的两个段映射到内存中。然后对程序的ELF头进行解码。如果程序头表明可执行文件链接到共享库,进程管理器将从程序头中提取动态解释器的名称。动态解释器指向包含运行时链接器代码的共享库。进程管理器将在内存中加载此共享库,然后将控制权传递给此库中的运行时链接器代码。

2.3. 运行时链接器

当启动指向共享对象的程序时,或程序请求动态加载共享对象时,将调用运行时链接器【runtime linker】。此链接器包含在C运行时库中。

运行时链接器【runtime linker】在加载共享库(.so文件)过程请参考:- Runtime linker

2.4. 在运行时加载共享库

进程可以通过使用 dlopen() 调用,在运行时加载共享库,该调用会指示运行时链接器【runtime linker】加载此库。一旦加载了此库,程序就可以通过使用 dlsym() 调用来确定其地址,从而调用该库中的任何函数。

程序还可以通过使用 dladdr() 调用来确定与给定地址相关联的符号。最后,当进程不再需要共享库时,它可以调用 dlclose() 从内存中卸载库。

2.5. 动态链接库使用总结

两种动态链接库的使用方式:

2.5.1. 用程序编译时,通过编译工具链gcc指定和使用动态链接库

创建动态链接库

假设你有一个名为 libmylib.so 的动态链接库,它包含了一个函数 int add(int a, int b);。你需要编写这个库的源代码,例如 mylib.c:

然后,你可以使用 gcc 来编译和创建 .so 文件:

编写使用动态链接库的应用程序

现在,你有一个使用 libmylib.so 库的应用程序。你需要包含相应的头文件(如果有的话),并在编译时指定链接到该库。假设你没有为 add 函数创建头文件,但通常你会这样做。

app.c(使用动态链接库的应用程序):

注意:在实际开发中,你应该在头文件中声明函数 add,并在 app.c 中包含这个头文件。

编译应用程序并链接到动态链接库

编译应用程序时,你需要使用 -L 选项来指定库文件的搜索路径,并使用 -l 选项来指定库名(不包括前导的 lib 和后缀的 .so)。

这里 -L. 指定了当前目录为库文件的搜索路径(假设 libmylib.so 在当前目录下),而 -lmylib 指定了要链接的库名为 mylib。

运行应用程序

在运行应用程序之前,确保动态链接库 libmylib.so 在系统的库路径中,或者在运行应用程序时指定其位置。如果库在当前目录下,你可能需要设置 LD_LIBRARY_PATH 环境变量来包含当前目录:

2.5.2. 直接在应用程序代码中运行时动态加载共享对象(即.so文件)

假设你有一个共享对象文件 libmylib.so,它包含了一个函数 int add(int a, int b);。该函数的实现和创建 .so 文件的过程与前面的示例相同。

编写一个 C 应用程序,该程序在运行时动态加载 libmylib.so 并调用其中的 add 函数。

编译应用程序时,你需要链接 dl 库,因为 dlopen、dlsym 等函数都定义在这个库中。

执行./myapp运行你的应用程序。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PPT不能编辑,按钮都是灰色,怎么办?

PPT文件打开之后,发现无法编辑,再仔细查看发现工具栏中的功能按钮都是灰色的,无法使用,这是什么原因?该如何解决? 原因:无法编辑PPT文件,并且功能按钮都是灰色,这是因为…

PMP–一、二、三模、冲刺–分类–8.质量管理

文章目录 技巧五、质量管理 一模8.质量管理--质量管理计划--质量管理计划包括项目采用的质量标准,到底有没有满足质量需求,看质量标准即可。6、 [单选] 自项目开始以来,作为项目经理同事的职能经理一直公开反对该项目,在讨论项目里…

深度学习中的生成对抗网络(GAN)原理与应用

引言 生成对抗网络(Generative Adversarial Network,简称GAN)是由Ian Goodfellow等人在2014年提出的一种深度学习模型,它通过对抗训练的方式生成与真实数据分布相似的假数据。GAN的出现极大地推动了深度学习和生成模型的研究&…

【CSS in Depth 2 精译_063】10.2 深入理解 CSS 容器查询中的容器

当前内容所在位置(可进入专栏查看其他译好的章节内容) 【第十章 CSS 容器查询】 ✔️ 10.1 容器查询的一个简单示例 10.1.1 容器尺寸查询的用法 10.2 深入理解容器 ✔️ 10.2.1 容器的类型 ✔️10.2.2 容器的名称 ✔️10.2.3 容器与模块化 CSS ✔️ 10.3…

macOS无法打开未验证安装包的解决方案:无法打开‘XXX.pkg’,因为无法验证其是否包含可能危害Mac安全或泄漏隐私的恶意软件

macOS无法打开未验证安装包的解决方案:无法打开‘XXX.pkg’,因为无法验证其是否包含可能危害Mac安全或泄漏隐私的恶意软件 在macOS Ventura及以上版本中,系统安全性进一步加强,默认情况下不允许运行未验证或未签名的应用程序。当…

Springboot项目搭建(8)-用户登出与个人中心修改

1.提要信息 1.1 catch和then方法 then和catch是JavaScript中Promise对象的两个方法,用于处理异步操作的成功(成功回调)和失败(失败回调)情况。这两个方法通常与async/await语法一起使用,但也可以单独使用…

Android Studio 使用插件Database Navigation 连接 sqlite数据库

文章目录 Database Navigation 简介一,Database Navigation 下载二,将sqlite数据库文件存放到本地三,连接sqlite数据库四,使用SQL语句查看数据 Database Navigation 简介 Database Navigation 是一款在 Android Studio 开发环境中…

springboot kafka在kafka server AUTH变动后consumer自动销毁

前言 笔者使用了kafka用来传输数据,笔者在今年10月写了文章,怎么使用配置化实现kafka的装载:springboot kafka多数据源,通过配置动态加载发送者和消费者-CSDN博客 不过在实际运行中,kafka broker是加密的&#xff0c…

ansible使用说明

将安装包拷贝到主控端主机 在主控端主机安装ansible,sh setup.sh 确认安装成功后,编辑hosts文件(按步骤逐个添加主机组,不要一开始全部配置好) [site-init]下的主机列表为被控制的主机(按照当前ai建模方案…

5G学习笔记之PRACH

即使是阴天,也要记得出门晒太阳哦 目录 1. 概述 2. PRACH Preamble 3. PRACH Preamble 类型 3.1 长前导码 3.2 短前导码 3.3 前导码格式与小区覆盖 4. PRACH时频资源 4.1 小区所有可用PRACH资源 4.2 SSB和RACH的关系 4.3 PRACH时频资源配置 1. 概述 随机接入…

单点登录深入详解之技术方案总结

技术方案之CAS认证 概述 CAS 是耶鲁大学的开源项目,宗旨是为 web 应用系统提供一种可靠的单点登录解决方案。 CAS 从安全性角度来考虑设计,用户在 CAS 输入用户名和密码之后通过ticket进行认证,能够有效防止密码泄露。 CAS 广泛使用于传统应…

不开流也可以知道文件大小(File类)file.length():long

但是文件的toString是这个东西,所以当你把一个文件对象转json,大概率只有paXXXXX” 这一个key,想要自动转成输出其他的文件大小或者文件名什么的,就自己封装file类,封装fiel的方法

数据结构 (16)特殊矩阵的压缩存储

前言 特殊矩阵的压缩存储是数据结构中的一个重要概念,它旨在通过找出特殊矩阵中值相同的矩阵元素的分布规律,把那些呈现规律性分布的、值相同的多个矩阵元素压缩存储到一个存储空间中,从而节省存储空间。 一、特殊矩阵的定义 特殊矩阵是指具有…

试题转excel;试题整理工具;试卷转excel;word转excel

一、问题描述 我父亲是一名教师,偶尔会需要将试卷转excel,方便管理处理一些特别重要的题目 于是,就抽空写一个专门将试题转excel的工具,便于各位教师从业者和教育行业的朋友更好的整理试题,减少一点重复枯燥的工作 …

CSP/信奥赛C++语法基础刷题训练(36):洛谷P11229:[CSP-J 2024] 小木棍

CSP/信奥赛C语法基础刷题训练(36):洛谷P11229:[CSP-J 2024] 小木棍 题目描述 小 S 喜欢收集小木棍。在收集了 n n n 根长度相等的小木棍之后,他闲来无事,便用它们拼起了数字。用小木棍拼每种数字的方法如…

【NLP 4、数学基础】

此去经年,应是良辰美景虚设 —— 24.11.28 一、线性代数 1.标量和向量 ① 标量 Scalar 一个标量就是一个单独的数 ② 向量 Vector 一个向量是一列数 可以把向量看作空间中的点,每个元素是不同坐标轴上的坐标 向量中有几个数,就叫作几维…

IOC控制反转DI依赖注入(Java EE 学习笔记06)

1 IoC 控制反转 控制反转(Inversion of Control,缩写为IoC)是面向对象编程中的一个设计原则,用来降低程序代码之间的耦合度。在传统面向对象编程中,获取对象的方式是用new关键字主动创建一个对象,也就是说…

68 mysql 的 临键锁

前言 我们这里来说的就是 我们在 mysql 这边常见的 一种锁, 行临键锁 虽然 在平时我们用到的不是很多, 我们这里 主要是 讲一下 它的主要的触发的场景 行临键锁 等价于 行锁 间隙锁, 行间隙锁是一个 左开右开的区间, 行临键锁 是一个左开右闭的区间 但是 它 和 行锁的差异…

(数据结构与算法)如何提高学习算法的效率?面试算法重点有哪些?面试需要哪些能力?

面试官眼中的求职者 通过对你算法的考察!!!! 缩进太多!!一般不要超过三层!!!缩进越少,bug越少;逻辑比较复杂,把这些包装成为函数&…

设计模式-适配器模式-注册器模式

设计模式-适配器模式-注册器模式 适配器模式 如果开发一个搜索中台,需要适配或接入不同的数据源,可能提供的方法参数和平台调用的方法参数不一致,可以使用适配器模式 适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至…