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

2023-11-02

可执行文件只有装载到内存以后才能被CPU执行

1. 进程虚拟地址空间

程序和进程有什么区别:程序(或者狭义上讲可执行文件)是一个静态的概念,它就是一些预先编译好的指令和数据集合的一个文件;进程则是一个动态的概念,它是程序运行时的一个过程,很多时候把动态库叫做运行时(Runtime)也有一定的含义。

每个程序被运行起来以后,它将拥有自己独立的虚拟地址空间(Virtual Address Space),这个虚拟地址空间的大小由计算机的硬件平台决定,具体地说是由CPU的位数决定的。硬件决定了地址空间的最大理论上限,即硬件的寻址空间大小,比如32位的硬件平台决定了虚拟地址空间的地址为0到2^32-1,即0x00000000~0xFFFFFFFF,也就是我们常说的4GB虚拟空间大小;而64位的硬件平台具有64位寻址能力,它的虚拟地址空间达到了2^64字节,即0x0000000000000000~0xFFFFFFFFFFFFFFFF,总共17179869184GB。

从程序的角度看,我们可以通过判断C语言程序中的指针所占的空间来计算虚拟地址空间的大小。一般来说,C语言指针大小的位数与虚拟地址空间的位数相同,如32位平台下的指针为32位,即4字节;64位平台下的指针为64位,即8字节。当然有些特殊情况下,这种规则不成立。

在下文中以32位的地址空间为主,64位的与32位类似。

那么32位平台下的4GB虚拟空间,我们的程序是否可以任意使用呢?不行。因为程序在运行的时候处于操作系统的监管下,操作系统为了达到监控程序运行等一系列目的,进程的虚拟空间都在操作系统的掌握之中。进程只能使用那些操作系统分配给进程的地址,如果访问未经允许的空间,那么操作系统就会捕获到这些访问,将进程的这种访问当作非法操作,强制结束进程。我们经常在Windows下碰到”进程因非法操作需要关闭”或Linux下的”Segmentation fault”很多时候是因为访问了未经允许的地址。

PAE(Physical Address Extension):从硬件层面上来讲,原先的32位地址线只能访问最多4GB的物理内存。但是自从扩展至36位地址线之后,Intel修改了页映射的方式,使得新的映射方式可以访问到更多的物理内存。Intel把这个地址扩展方式叫做PAE。扩展的物理地址空间,对于普通应用程序来说正常情况下感觉不到它的存在,因为这主要是操作系统的事,在应用程序里,只有32位的虚拟地址空间。那么应用程序该如何使用这些大于常规的内存空间呢?一个很常见的方法就是操作系统提供一个窗口映射的方法,把这些额外的内存映射到进程地址空间中来。在Windows下,这种访问内存的操作方式叫做AWE(Address Windowing Extensions);而像Linux等UNIX类操作系统则采用mmap()系统调用来实现。

2. 装载的方式

程序执行时所需要的指令和数据必须在内存中才能够正常运行,最简单的方法就是将程序运行所需要的指令和数据全都装入内存中,这样程序就可以顺利运行,这就是最简单的静态装入的方法。但是很多情况下程序所需要的内存数量大于物理内存的数量,当内存的数量不够时,根本的解决办法就是添加内存。程序运行时是有局部性原理,所以我们可以将程序最常用的部分驻留在内存中,而将一些不太常用的数据存放在磁盘里面,这就是动态装入的基本原理。

覆盖装入(Overlay)和页映射(Paging)是两种很典型的动态装载方法,它们所采用的思想都差不多,原则上都是利用了程序的局部性原理。动态装入的思想就是程序用到哪个模块,就将哪个模块装入内存,如果不用就暂时不装入,存放在磁盘中

覆盖装入:在没有发明虚拟存储之前使用比较广泛,现在已经几乎被淘汰了。覆盖装入的方法把挖掘内存嵌入的任务交给了程序员,程序员在编写程序的时候必须手工将程序分割成若干块,然后编写一个小的辅助代码来管理这些模块何时应该驻留内存而何时应该被替换掉。这个小的辅助代码就是所谓的覆盖管理器(Overlay Manager)。覆盖装入是典型的利用时间换取空间的方法。

页映射:是虚拟存储机制的一部分,它随着虚拟存储的发明而诞生。与覆盖装入的原理相似,页映射也不是一下子就把程序的所有数据和指令都装入内存,而是将内存和所有磁盘中的数据和指令按照页(Page)”为单位划分成若干个页,以后所有的装载和操作的单位都是页。硬件规定页的大小有4096字节、8192字节、2MB、4MB

3. 从操作系统角度看可执行文件的装载

进程的建立:从操作系统的角度来看,一个进程最关键的特征是它拥有独立的虚拟地址空间,这使得它有别于其它进程。很多时候一个程序被执行同时都伴随着一个新的进程的创建。创建一个进程,然后装载相应的可执行文件并且执行。在有虚拟存储的情况下,上述过程最开始只需要做三件事情:

(1). 首先是创建虚拟地址空间:一个虚拟空间由一组页映射函数将虚拟空间的各个页映射至相应的物理空间,那么创建一个虚拟空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构。在i386的Linux下,创建虚拟地址空间实际上只是分配一个页目录(Page Directory)就可以了,甚至不设置页映射关系,这些映射关系等到后面程序发生页错误的时候再进行设置。

(2). 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系:上面那一步的页映射关系函数是虚拟空间到物理内存的映射关系,这一步所做的是虚拟空间与可执行文件的映射关系。当程序执行发生页错误时,操作系统将从物理内存中分配一个物理页,然后将该”缺页”从磁盘中读取到内存中,再设置缺页的虚拟页和物理页的映射关系,这样程序才得以正常运行。当操作系统捕获到缺页错误时,它应知道程序当前所需要的页在可执行文件中的哪一个位置。这就是虚拟空间与可执行文件之间的映射关系。从某种角度来看,这一步是整个装载过程中最重要的一步,也是传统意义上”装载”的过程。

由于可执行文件在装载时实际上是被映射的虚拟空间,所以可执行文件很多时候又被叫做映像文件(Image)

Linux中将进程虚拟空间中的一个段叫做虚拟内存区域(VMA, Virtual Memory Area),在Windows中将这个叫做虚拟段(Virtual Section),其实它们都是同一个概念。

(3). 将CPU指令寄存器设置成可执行文件入口,启动运行:操作系统通过设置CPU的指令寄存器将控制权转交给进程,由此进程开始执行。可执行文件入口即是ELF文件头中保存的入口地址。

页错误(Page Fault):随着进程的执行,页错误也会不断地产生,操作系统也会为进程分配相应的物理页来满足进程执行的需求。

4. 进程虚存空间分布

ELF文件链接视图和执行视图:在一个正常的进程中,可执行文件中包含的往往不止代码段,还有数据段、BSS等,所以映射到进程虚拟空间的往往不止一个段。当段的数量增多时,就会产生空间浪费的问题。ELF文件被映射时,是以系统的页长度作为单位的,那么每个段在映射时的长度应该都是系统页长度的整数倍;如果不是,那么多余部分也将占用一个页。一个ELF文件中往往有十几个段,那么内存空间的浪费是可想而知的。

ELF文件中,段的权限往往只有为数不多的几种组合,基本上是三种:(1).以代码段为代表的权限为可读可执行的段;(2).以数据段和BSS段为代表的权限为可读可写的段;(3).以只读数据段为代表的权限为只读的段。对于相同权限的段,把它们合并到一起当作一个段进行映射。

ELF可执行文件引入了一个概念叫做”Segment”,一个”Segment”包含一个或多个属性类似的”Section”。从链接的角度看,ELF文件是按”Section”存储的;从装载的角度看,ELF文件又可以按照”Segment”划分。”Segment”的概念实际上是从装载的角度重新划分了ELF的各个段。在将目标文件链接成可执行文件的时候,链接器会尽量把相同权限属性的段分配在同一空间。比如可读可执行的段都放在一起,这种段的典型是代码段;可读可写的段放在一起,这种段的典型是数据段。ELF中把这些属性相似的、又连在一起的段叫做一个”Segment”而系统正是按照”Segment”而不是”Section”来映射可执行文件的

下面是一个很小的例子程序(SectionMapping.c):

#include <stdlib.h>

int main()
{
	while (1) {
		sleep(1000);
	}

	return 0;
}

使用静态链接的方式将其编译链接成可执行文件SectionMapping.elf,执行:

gcc -static SectionMapping.c -o SectionMapping.elf

使用readelf可以看到,可执行文件SectionMappint.elf中总共有31个段(Section),如下图所示:

正如描述”Section”属性的结构叫做段表,描述”Segment”的结构叫程序头(Program Header),它描述了ELF文件该如何被操作系统映射到进程的虚拟空间,执行结果如下图所示:

可以看到,这个可执行文件共有6个Segment。从装载的角度看,目前只关心两个”LOAD”类型的Segment,因为只有它是需要被映射的,其它的诸如”NOTE”、”TLS”、”GNU_STACK”都是在装载时起辅助作用的。所有相同属性的”Section”被归类到一个”Segment”,并且映射到同一个VMA。总的来说,”Segment”和”Section”是从不同的角度来划分同一个ELF文件。这个在ELF中被称为不同的视图(View),从”Section”的角度来看ELF文件就是链接视图(Linking View),从”Segment”的角度来看就是执行视图(Execution View)。当我们在谈到ELF装载时,”段”专门指”Segment”;而在其它的情况下,”段”指的是”Section”。

ELF可执行文件中有一个专门的数据结构叫做程序头表(Program Header Table)用来保存”Segment”的信息。因为ELF目标文件不需要被装载,所以它没有程序头表,而ELF的可执行文件和共享库文件都有。跟段表结构一样,程序头表也是一个结构体数组,它的结构体Elf32_Phdr或Elf64_Phdr(声明在/usr/include/elf.h)如下:

/* Program segment header.  */
typedef struct
{
  Elf32_Word    p_type;                 /* Segment type */
  Elf32_Off     p_offset;               /* Segment file offset */
  Elf32_Addr    p_vaddr;                /* Segment virtual address */
  Elf32_Addr    p_paddr;                /* Segment physical address */
  Elf32_Word    p_filesz;               /* Segment size in file */
  Elf32_Word    p_memsz;                /* Segment size in memory */
  Elf32_Word    p_flags;                /* Segment flags */
  Elf32_Word    p_align;                /* Segment alignment */
} Elf32_Phdr;

typedef struct
{
  Elf64_Word    p_type;                 /* Segment type */
  Elf64_Word    p_flags;                /* Segment flags */
  Elf64_Off     p_offset;               /* Segment file offset */
  Elf64_Addr    p_vaddr;                /* Segment virtual address */
  Elf64_Addr    p_paddr;                /* Segment physical address */
  Elf64_Xword   p_filesz;               /* Segment size in file */
  Elf64_Xword   p_memsz;                /* Segment size in memory */
  Elf64_Xword   p_align;                /* Segment alignment */
} Elf64_Phdr;

Elf32_Phdr或Elf64_Phdr结构体的几个成员与使用”readelf -l”打印文件头表显示的结果一一对应。结构体的各个成员的基本含义,如下表所示:

对于”LOAD”类型的”Segment”来说,p_memsz的值不可以小于p_filesz,否则就是不符合常理的。如果p_memsz大于p_filesz,就表示该”Segment”在内存中所分配的空间大小超过文件中实际的大小,这部分”多余”的部分则全部填充为”0”。这样做的好处是,我们在构造ELF可执行文件时不需要再额外设立BSS的”Segment”了,可以把数据”Segment”的p_memsz扩大,那些额外的部分就是BSS。因为数据段和BSS的唯一区别就是:数据段从文件中初始化内容,而BSS段的内容全都初始化为0。这也就是在前面的例子中只看到了两个”LOAD”类型的段,而不是三个,BSS已经被合并到了数据类型的段里面。

堆和栈:在操作系统里面,VMA除了被用来映射可执行文件中的各个”Segment”以外,它还可以有其它的作用,操作系统通过使用VMA来对进程的地址空间进行管理。进程在执行的时候它还需要用到栈(Stack)、堆(Heap)等空间,事实上它们在进程的虚拟空间中的表现也是以VMA的形式存在的,很多情况下,一个进程中的栈和堆分别都有一个对应的VMA。

在Linux下,可以通过查看”/proc”来查看进程的虚拟空间分布,如下图所示:

上图的输出结果中:第一列是VMA的地址范围;第二列是VMA的权限,”r”表示可读,”w”表示可写,”x”表示可执行,”p”表示私有(COW, Copy on Write),”s”表示共享。第三列是偏移,表示VMA对应的Segment在映像文件中的偏移;第四列表示映像文件所在设备的主设备号和次设备号;第五列表示映像文件的节点号。最后一列是映像文件的路径。我们可以看到进程中有8个VMA,只有前两个是映射到可执行文件中的两个Segment。另外六个段的文件所在设备主设备号和次设备号及文件节点号都是0,则表示它们没有映射到文件中,这种VMA叫做匿名虚拟内存地址(Anonymous Virtual Memory Area)。我们可以看到有两个区域分别是堆(Heap)和栈(Stack),它们的大小分别为(0x0122d000-0x0120a000)/1024=140KB和(0x7ffc01c44000-0x7ffc01c23000)/1024=132KB。这两个VMA几乎在所有的进程中存在,我们在C语言程序里面最常用的malloc()内存分配函数就是从堆里面分配的,堆由系统库管理。栈一般也叫做堆栈,每个线程都有属于自己的堆栈,对于单线程的程序来讲,这个VMA堆栈就全都归它使用。另外有一个很特殊的VMA叫做”vdso”,它的地址已经位于内核空间了,事实上它是一个内核的模块,进程可以通过访问这个VMA来跟内核进行一些通信。

进程虚拟地址空间的概念:操作系统通过给进程空间划分出一个个VMA来管理进程的虚拟空间;基本原则是将相同权限属性的、有相同映像文件的映射成一个VMA;一个进程基本上可以分为如下几种VMA区域:(1). 代码VMA,权限只读、可执行;有映像文件。(2). 数据VMA,权限可读写、可执行;有映像文件。(3). 堆VMA,权限可读写、可执行;无映像文件,匿名,可向上扩展。(4). 栈VMA,权限可读写、不可执行;无映像文件,匿名,可向下扩展。当我们在讨论进程虚拟空间的”Segment”的时候,基本上就是指上面的几种VMA。

堆的最大申请数量:32位,Linux下虚拟地址空间分给进程本身的是3GB(Windows默认是2GB),一般程序中使用malloc()函数进行地址空间的申请,那么malloc的最大申请数量会受到操作系统版本、程序本身大小、用到的动态/共享库数量大小、程序栈数量大小等,甚至有可能每次最大可申请数量都会不同,因为有些操作系统使用了一种叫做随机地址空间分布的技术(主要是出于安全考虑,防止程序受恶意攻击),使得进程的堆空间变小。

段地址对齐可执行文件最终是要被操作系统装载运行的,这个装载的过程一般是通过虚拟内存的页映射机制完成的。在映射过程中,页是映射的最小单位。对于Intel 80x86系列处理器来说,默认的页大小为4096字节,也就是说,我们要映射将一段物理内存和进程虚拟地址空间之间建立映射关系,这段内存空间的长度必须是4096的整数倍,并且这段空间在物理内存和进程虚拟地址空间中的起始地址必须是4096的整数倍。由于有着长度和起始地址的限制,对于可执行文件来说,它应该尽量地优化自己的空间和地址的安排,以节省空间。在ELF文件中,对于任何一个可装载的”Segment”,它的p_vaddr除以对齐属性的余数等于p_offset除以对齐属性的余数。

进程栈初始化:进程刚开始启动的时候,须知道一些进程运行的环境,最基本的就是系统环境变量和进程的运行参数。很常见的一种做法是操作系统在进程启动前将这些信息提前保存到进程的虚拟空间的栈中(也就是VMA中的Stack VMA)。

5. Linux内核装载ELF过程简介

当我们在Linux系统的bash下输入一个命令执行某个ELF程序时,首先在用户层面,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()系统调用执行指定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束,然后继续等待用户输入命令。execve()系统调用被声明在/usr/include/unistd.h中。Glibc对execve()系统调用进行了包装,提供了execl()、execlp()、execle()、execv()和execvp()等5个不同形式的exec系列API,它们只是在调用的参数形式上有所区别,但最终都会调用到execve()这个系统中。

在进入execve()系统调用之后,Linux内核就开始进行真正的装载工作。在内核中,execve()系统调用相应的入口是sys_execve(),sys_execve()进行一些参数的检查复制之后,调用do_execve()。do_execve()会首先查找被执行的文件,如果找到文件,则读取文件的前128个字节,目的是判断文件的格式,每种可执行文件的格式的开头几个字节都是很特殊的,特别是开头4个字节,常常被称做魔数(Magic Number),通过对魔数的判断可以确定文件的格式和类型。比如ELF的可执行文件格式的头4个字节为0x7F、’E’、’L’、’F’;而Java的可执行文件格式的头4个字节为’c’、’a’、’f’、’e’;如果被执行的是Shell脚本或perl、python等这种解释型语言的脚本,那么它的第一行往往是”#!/bin/sh”或”#!/usr/bin/perl”或”#!/usr/bin/python”,这时候前两个字节’#’和”!”就构成了魔数,系统一旦判断到这两个字节,就对后面的字符串进行解析,以确定具体的解释程序的路径。

当do_execve()读取了这128个字节的文件头部以后,然后调用search_binary_handle()去搜索和匹配合适的可执行文件装载处理过程。Linux中所有被支持的可执行文件格式都有相应的装载处理过程,search_binary_handle()会通过判断文件头部的魔数确定文件的格式,并且调用相应的装载处理过程。比如ELF可执行文件的装载处理过程叫做load_elf_binary();a.out可执行文件的装载处理过程叫做load_aout_binary();而装载可执行脚本程序的处理过程叫做load_script()。

load_elf_binary()的主要步骤是:

(1). 检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(Segment)的数量。

(2). 寻找动态链接的”.interp”段,设置动态链接器路径。

(3). 根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据、只读数据。

(4). 初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址。

(5). 将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点就是动态链接器。

当load_elf_binary()执行完毕,返回至do_execve()再返回sys_execve()时,上面的第5步中已经把系统调用的返回地址改成了被装载的ELF程序的入口地址了。所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到了ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件加载完成。

6. Windows PE的装载

PE文件的装载跟ELF有所不同,由于PE文件中,所有段的起始地址都是页的倍数,段的长度如果不是页的整数倍,那么在映射时向上补齐到页的整数倍,我们也可以简单地认为在32位的PE文件中,段的起始地址和长度都是4096字节的整数倍。由于这个特点,PE文件的映射过程会比ELF简单得多,因为它无须考虑如ELF里面诸多段地址对齐之类的问题,虽然这种会浪费一些磁盘和内存空间。PE可执行文件的段的数量一般很少,不像ELF中经常有十多个”Section”,最后不得不使用”Segment”的概念把它们合并到一起装载,PE文件中,链接器在生产可执行文件时,往往将所有的段尽可能地合并,所以一般只有代码段、数据段、只读数据段和BSS等为数不多的几个段。

PE里面很常见的术语叫做RVA(Relative Virtual Address),它表示一个相对虚拟地址,就是相当于文件中的偏移量的东西。它是相对于PE文件的装载基地址的一个偏移地址。比如,一个PE文件被装载到虚拟地址(VA)0x00400000,那么一个RVA为0x1000的地址就是0x00401000。每个PE文件在装载时都会有一个装载目标地址(Target Address),这个地址就是所谓的基地址(Base Address)。由于PE文件被设计成可以装载到任何地址,所以这个基地址并不是固定的,每次装载时都可能会变化。如果PE文件中的地址都使用绝对地址,它们都要随着基地址的变化而变化。但是,如果使用RVA这样一种基于基地址的相对地址,那么无论基地址怎么变化,PE文件中的各个RVA都保持一致。

装载一个PE可执行文件过程:

(1). 先读取文件的第一个页,在这个页中,包含了DOS头、PE文件头和段表。

(2). 检查进程地址空间中,目标地址是否可用,如果不可用,则另外选一个装载地址。这个问题对于可执行文件来说基本不存在,因为它往往是进程第一个装入的模块,所以目标地址不太可能被占用。主要是针对DLL文件的装载而言的。

(3). 使用段表中提供的信息,将PE文件中所有的段一一映射到地址空间中相应的位置。

(4). 如果装载地址不是目标地址,则进行Rebasing。

(5). 装载所有PE文件所需要的DLL文件。

(6). 对PE文件中的所有导入符号进行解析。

(7). 根据PE头中指定的参数,建立初始化栈和堆。

(8). 建立主线程并且启动进程。

PE文件中,与装载相关的主要信息都包含在PE扩展头(PE Optional Header)和段表。

GitHubhttps://github.com/fengbingchun/Messy_Test

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

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

  • Segment Routing—BGP-LS

    BGP LS概况 BGP LS是学习多个IGP区域和区域拓扑的首选机制 BGP LS使用BGP xff0c 以可扩展的方式分发网络信息 重要的是 xff0c BGP LS还承载了IGP没有分发的信息 xff08 例如BGP对等体互联链路 x
  • C++11中enum class的使用

    枚举类型 enumeration 使我们可以将一组整型常量组织在一起 和类一样 每个枚举类型定义了一种新的类型 枚举属于字面值常量类型 C 包含两种枚举 限定作用域的和不限定作用域的 这里主要介绍限定作用域的 不限定作用域的使用可以参考 h
  • C++中static_cast/const_cast/dynamic_cast/reinterpret_cast的区别和使用

    C风格的强制转换较简单 如将float a转换为int b 则可以这样 b int a 或者b int a C 类型转换分为隐式类型转换和显示类型转换 隐式类型转换又称为标准转换 包括以下几种情况 1 算术转换 在混合类型的算术表达式中 最
  • C和C++安全编码笔记:动态内存管理

    4 1 C内存管理 C标准内存管理函数 1 malloc size t size 分配size个字节 并返回一个指向分配的内存的指针 分配的内存未被初始化为一个已知值 2 aligned alloc size t alignment siz
  • C++11容器中新增加的emplace相关函数的使用

    C 11中 针对顺序容器 如vector deque list 新标准引入了三个新成员 emplace front emplace和emplace back 这些操作构造而不是拷贝元素 这些操作分别对应push front insert和p
  • 概率论中高斯分布(正态分布)介绍及C++11中std::normal_distribution的使用

    高斯分布 最常用的分布是正态分布 normal distribution 也称为高斯分布 Gaussian distribution 正态分布N x 2 呈现经典的 钟形曲线 的形状 其中中心峰的x坐标由 给出 峰的宽度受 控制 正态分布由
  • C++/C++11中引用的使用

    引用 reference 是一种复合类型 compound type 引用为对象起了另外一个名字 引用类型引用 refer to 另外一种类型 通过将声明符写成 d的形式来定义引用类型 其中d是声明的变量名 一 一般引用 一般在初始化变量时
  • Linux下遍历指定目录的C++实现

    之前在 https blog csdn net fengbingchun article details 51474728 给出了在Windows遍历指定文件夹的C 实现 这里给出在Linux下遍历目录的实现 Windows和Linux下的
  • C++中的内存对齐介绍

    网上有很多介绍字节对齐或数据对齐或内存对齐的文章 虽然名字不一样 但是介绍的内容大致都是相同的 这里以内存对齐相称 注 以下内容主要来自网络 内存对齐 通常也称为数据对齐 是计算机对数据类型合法地址做出了一些限制 要求某种类型对象的地址必须
  • json11库的使用

    JSON JavaScript Object Notation 是一种轻量级的文本数据交换格式 易于让人阅读 同时也易于机器解析和生成 尽管JSON是Javascript的一个子集 但JSON是独立于语言的文本格式 并且采用了类似于C语言家
  • C++中插件使用举例

    插件并不是在构建时链接的 而是在运行时发现并加载的 因此 用户可以利用你定义好的插件API来编写自己的插件 这样他们就能以指定方式扩展API的功能 插件库是一个动态库 它可以独立于核心API编译 在运行时根据需要显示加载 不过插件也可以使用
  • 概率论中伯努利分布(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
  • C/C++中#pragma once的使用

    在C C 中 为了避免同一个文件被include多次 有两种方式 一种是 ifndef方式 一种是 pragma once方式 在头文件的最开始加入 ifndef SOME UNIQUE NAME HERE define SOME UNIQ
  • C++/C++11中头文件iterator的使用

  • 提高C++性能的编程技术笔记:单线程内存池+测试代码

    频繁地分配和回收内存会严重地降低程序的性能 性能降低的原因在于默认的内存管理是通用的 应用程序可能会以某种特定的方式使用内存 并且为不需要的功能付出性能上的代价 通过开发专用的内存管理器可以解决这个问题 对专用内存管理器的设计可以从多个角度
  • 使用 ggplot2 仅将线段添加到一个方面

    作为一个例子 我有这个数据框 称为my data Groups FactorA FactorB FactorC N value sd se ci 1 Control Condition1 Condition1 Condition1 3 92
  • 如何引用 Visual Studio 项目中用户定义段的开头?

    我正在努力转换与 gnu 工具链的 ld 链接的 C 程序 使其编译为 Visual studio 2005 项目 该程序将 data symbol 放在不同的段中并在 初始化阶段它在段之间复制数据 指向的指针 段的开始和结束在 ld 链接
  • 如何使用 gstreamer 从较长源的片段中制作音频剪辑?

    我想使用 gstreamer 将一个音频文件中的任意剪辑保存到一个新文件中 比如原著中1分钟到2分钟的片段 我该怎么做 你需要gnonlin http wiki pitivi org wiki GNonLin documentation S
  • ORG指令后设置段寄存器

    我目前正在关注操作系统开发教程 http brokenthorn com Resources OSDev4 html 其中包括有关引导加载程序的讨论 我的引导加载程序当前处于 16 位实模式 因此 我能够使用提供的 BIOS 中断 例如 V
  • cytoscape.js。对于边缘线段,将坐标转换为线段距离和线段权重

    我想与社区分享一个有用的函数 该函数从坐标 PointX PointY 返回线段距离和线段权重 我从工具 例如draw io 创建图表 并且在使用多个路点制作边缘 线段样式 时 该免费软件通过其坐标提供路点 不幸的是 最新版本的 cytos

随机推荐

  • Qt 命令行编译,VC命令行编译,调用nsis

    QT bat脚本 rd q s dist mkdir dist qmake exe helloworld pro spec win32 g CONFIG qtquickcompiler o dist cd dist mingw32 make
  • make[1]: *** Waiting for unfinished jobs....

    安装node js环境时候 make报错 报错信息 make 1 Waiting for unfinished jobs rm 490f1fcf42b2afac71d1c00fb593c736d4a65552 intermediate ma
  • Python执行JS代码的三种方式

    1 使用 js2py 基本操作 import js2py 执行单行js语句 js2py eval js console log abcd gt gt gt abcd 执行js函数 add js2py eval js function add
  • 基于session和token的身份认证方案

    一 基于session的身份认证方案 1 方案图示 2 比较通用的鉴权流程实现如下 在整个流程中有两个拦截器 第一个拦截器 AuthInteceptor是为了每一次的请求的时候都先去session中取user对象 如果session中有 就
  • 组合排列——回溯法的实践

    一 模板 对于回溯问题 可以给一个模板 result def backtracking 参数 if 终止条件 result add 路径 return for 选择 本层集合中元素 树中节点孩子的数量就是集合的大小 处理节点 backtra
  • 微信小程序 组件间关系

    完整微信小程序 Java后端 技术贴目录清单页面 必看 定义和使用组件间关系 有时需要实现这样的组件
  • Linux 压缩、解压文件的 4 种方式。tar、gzip、gunzip、zip、unzip、7z命令使用方法

    Linux 压缩 解压文件的 4 种方式 tar gzip gunzip zip unzip 7z命令使用方法 文章目录 Linux 压缩 解压文件的 4 种方式 tar gzip gunzip zip unzip 7z命令使用方法 1 t
  • JS 读写文件

    用js不能直接读取文件 但是可以利用浏览器提供的activex来实现读写文件的方法 只在IE下测试过 其他浏览器下的activex对象不太清楚 可以网上搜一下 具体读写文件的代码如下 function createAndReadFile v
  • UE4 - 海洋材质水下效果的修改

    屏幕前的污渍MASK修改位置如下 水下扭曲效果 强度修改位置如下 这里改0 1是无效的 只有0和1的区别 如果要调整波纹强度 需要到材质里修改 500的强度改为200 或者100 就很弱了 镜头光晕增加的地方
  • ThreadLocal与InheritableThreadLocal的实现原理

    文章目录 ThreadLocal介绍 使用方式 set 问题 InheritableThreadLocal介绍 源码 方案 ThreadLocal介绍 threadLocal的特点就是与线程绑定 一般通过这种隐式传参的方式来传递上下文 比如
  • vue实现高德地图点聚合功能

    效果截图展示 高德地图点聚合功能 1 创建地图 new AMap Map 示例 this map new AMap Map container resizeEnable true 是否监控地图容器尺寸变化 center 105 34 初始化
  • 终于来了!耗时268天,7大模块、2983页58万字,Android开发核心知识笔记!对标阿里P7!

    版权声明 本文为博主原创文章 未经博主允许不得转载 https www jianshu com u 3348b92f77a4 前言 转眼就快到 金九银十 又是个面试求职的黄金期 近来许多网友都在求一份完整 系统的学习资料和最新的大厂面试真题
  • Objective-C中的@dynamic

    Objective C中的 dynamic 一 dynamic与 synthesize的区别 property有两个对应的词 一个是 synthesize 一个是 dynamic 如果 synthesize和 dynamic都没写 那么默认
  • 【Linux】线程池

    文章目录 1 线程池概念 2 线程池的优点 3 线程池的应用场景 4 线程池的实现 5 STL和智能指针和线程安全 5 1其他常见锁 5 2读写锁 1 线程池概念 线程池是一种线程使用模式 线程过多会带来调度开销 进而影响缓存局部性和整体性
  • Linux UDP编程流程

    文章目录 UDP编程流程 UDP协议无连接的特点 UDP协议数据报的特点 UDP编程流程 UDP 提供的是无连接 不可靠的 数据报服务 服务器端和客户端没有什么本质上的区别 编程流程如下 socket 用来创建套接字 使用 udp 协议时
  • 友盟埋点详解

    数据埋点让产品或运营等相关人员能按照具体的需求 定制性地统计较为复杂的用户数据 例如想要追踪用户的行为 观察页面相关点击数据 关键路径转化率 分析某个事件活动效果时 就需要事先进行数据埋点 关于友盟的初始化配置不是此文的重点 网上一搜一堆的
  • Pycharm配置——解释器(interpreter)

    今天打开pycharm运行一段代码 结果遇到了这个问题 以上应该是没有配置解释器的问题 那我是怎么解决这个问题的呢 1 打开文件 File 2 打开设置 Setting 3 打开新project的默认设置 4点击project Interp
  • vue3使用高德地图api,海量点,多边形围栏,热力图,轨迹线(二)

    五 MassMarks海量点标记 let mass 此处我定义了6种海量点图标 此处url我用的是项目中本地引入的图片 也可以填在线url链接 props infoList是项目后端返回的所有点的信息数组 其中每个点对象有个style字段
  • ArcGIS Pro 一打开Notebook笔记本工具软件就崩溃(停止运行)

    先说结论 如果对于ArcGIS Pro的笔记本工具 包括新建 添加 打开等一切操作 只要一点开 ArcGIS Pro就停止运行 一个可能的原因是Temp文件夹或者是其他文件的路径中有中文 可以尝试将路径修改为全英文 再次运行 其实这个问题是
  • 程序员的自我修养--链接、装载与库笔记:可执行文件的装载与进程

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