【C语言与汇编】简单学学C到汇编代码

news2024/11/16 7:36:53

C语言与汇编

部分过程可参考C++ primer plus

image-20221210172857771

本书余下的篇幅讨论源代码文件中的内容;本节讨论创建源代码文 件的技巧。有些C++实现(如Microsoft Visual C++、Embarcadero C++ Builder、Apple Xcode、Open Watcom C++、Digital Mars C++和Freescale CodeWarrior)提供了集成开发环境(integrated development environments,IDE),让您能够在主程序中管理程序开发的所有步骤, 包括编辑。有些实现(如用于UNIX和Linux的GNU C++、用于AIX的 IBM XL C/C++、Embarcadero分发的Borland 5.5免费版本以及Digital Mars编译器)只能处理编译和链接阶段,要求在系统命令行输入命令。 在这种情况下,可以使用任何文本编辑器来创建和修改源代码。例如, 在UNIX系统上,可以使用vi、ed、ex或emacs;在以命令提示符模式运 行的Windows系统上,可以使用edlin、edit或任何程序编辑器。如果将 文件保存为标准ASCII文本文件(而不是特殊的字处理器格式),甚至 可以使用字处理器。另外,还可能有IDE选项,让您能够使用这些命令 行编译器。

编译和链接

Stroustrup实现C++时,使用了一个C++到C的编译器程序, 而不是开发直接的C++到目标代码的编译器。前者叫做cfront(表示C前 端,C front end),它将C++源代码翻译成C源代码,然后使用一个标准 C编译器对其进行编译。这种方法简化了向C的领域引入C++的过程。其 他实现也采用这种方法将C++引入到其他平台。随着C++的日渐普及, 越来越多的实现转向创建C++编译器,直接将C++源代码生成目标代 码。这种直接方法加速了编译过程,并强调C++是一种独立(虽然有些 相似)的语言。

Linux系统中最常用的编译器是g++,这是来自Free Software Foundation的GNU C++编译器。Linux的多数版本都包括该编译器,但并 不一定总会安装它。g++编译器的工作方式很像标准UNIX编译器。例 如,下面的命令将生成可执行文件a.out

目前有些不太理解的是int类型的长度居然是可变的。

C++的基本类型分为两组:一组由存储为整数的值组成,另一组由 存储为浮点格式的值组成。整型之间通过存储值时使用的内存量及有无 符号来区分。整型从最小到最大依次是:bool、char、signed char、 unsigned char、short、unsigned short、int、unsigned int、long、unsigned long以及C++11新增的long long和unsigned long long。

还有一种wchar_t 类型,它在这个序列中的位置取决于实现。C++11新增了类型char16_t 和char32_t,它们的宽度足以分别存储16和32位的字符编码。C++确保 了char足够大,能够存储系统基本字符集中的任何成员,而wchar_t则可 以存储系统扩展字符集中的任意成员,short至少为16位,而int至少与 short一样长,long至少为32位,且至少和int一样长。确切的长度取决于实现
字符通过其数值编码来表示。I/O系统决定了编码是被解释为字符 还是数字。

浮点类型可以表示小数值以及比整型能够表示的值大得多的值。3 种浮点类型分别是float、double和long double。C++确保float不比double 长,而double不比long double长。通常,float使用32位内存,double使用 64位,long double使用80到128位。

通过提供各种长度不同、有符号或无符号的类型,C++使程序员能 够根据特定的数据要求选择合适的类型。

quick start

目录:/work

gcc -m32 -S hello.c  # 只编译生成汇编代码片段,且通过 32 位的模式生成
gcc -S hello.c
gcc -S -fno-asynchronous-unwind-tables  # 去除生成的 针对debug 使用的信息

hello程序

#include <stdio.h>
 
int main()
{
    printf("Hello, World! \n");
    return 0;
}
	.file	"hello.c"  					 ;表明当前代码文件
	.section	.rodata   				 ;一个小节,rodata 只读数据段.除开数据段还有只读数据段
.LC0:
	.string	"Hello, World! "
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	$.LC0, %edi
	call	puts
	movl	$0, %eax
	popq	%rbp
	ret
.LFE0:
	.size	main, .-main
	.ident	"GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
	.section	.note.GNU-stack,"",@progbits

解释:

从上面的代码中我们可以看见,在进入 mian 函数后首先会处理 rbp rsp,并且在调用 ret 之前会先将 rbp 的值恢复。(注意:上面的代码是 64位机上编译的代码,所以 寄存器是 r 开头,表示 64位)
lea指令是啥意思

除此之外,我们还验证了一个东西:返回值是通过 ax 寄存器存储的

通过命令生成汇编代码如下(64bit):

        .file   "hello.c" 
        .text
        .section        .rodata 
.LC0:                          					 ;任何英文然后+: 就是一个地址  字符串首地址
        .string "Hello, World! "    			  ;string类型, 值为 hello,world
        .text                 					   ;代码段
        .globl  main					;全局符号名字 main  全局范围
        .type   main, @function			;类型为 方法、函数
main:                      					 ;任何英文然后+: 就是一个地址  main函数首地址
        endbr64
        pushq   %rbp          	  ;将此时的rbp压栈
        movq    %rsp, %rbp      	; rbp = rsp
        movl    $.LC0, %edi
        call    puts 
        movl    $1, %eax      		  ;将函数的返回值 放入到 eax 中【约定俗成】
        popq    %rbp			;rbp = pop
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
        .section        .note.GNU-stack,"",@progbits

32位的

    .file   "hello.c"  					   ;表明当前代码文件
    .text
    .section        .rodata    				 ;一个小节,rodata 只读数据段
.LC0:                          					 ;任何英文然后+: 就是一个地址
        .string "Hello, World! "    			  ;string类型, 值为 hello,world
        .text                 					   ;代码段
        .globl  main					;全局符号名字 main  全局范围
        .type   main, @function			;类型为 方法、函数
main:
        pushl   %ebp          	  ;将此时的rbp压栈
        movl    %esp, %ebp      	; rbp = rsp
        // 开辟main函数栈帧
        
        andl    $-16, %esp       	  ;与操作符号,将低位都变为0,清空的过程
        subl    $16, %esp       	  ;开辟空间
        
        movl    $.LC0, (%esp)    	  ;将字符串的地址保存在esp指向的内存单元中
        call    puts 
        movl    $1, %eax        ;将返回值 放入到 eax 中
        leave
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
        .section        .note.GNU-stack,"",@progbits

验证值传递代码

关键词是函数调用,可以参考一下视频进行学习。

https://www.bilibili.com/video/BV1RS4y1B75v

https://www.bilibili.com/video/BV1Nt4y1G728

值传递

#include <stdio.h>

int main()
{
    int i = 1;
    fun(i);
    return 0;
}

void fun(int a)
{
    a = a + 1;
}
	.file	"paramTrans.c"
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp                 
	subq	$16, %rsp              ;int i = 1 开辟栈空间并存放值
	movl	$1, -4(%rbp)
	movl	-4(%rbp), %eax         ;存放参数,通过 eax 中转
	movl	%eax, %edi
	movl	$0, %eax
	call	fun
	movl	$0, %eax
	leave
	ret
	.size	main, .-main
	.globl	fun
	.type	fun, @function
fun:
	pushq	%rbp                  ;开辟栈帧
	movq	%rsp, %rbp
	movl	%edi, -4(%rbp)        ;获取参数
	addl	$1, -4(%rbp)          ;执行代码
	popq	%rbp                  ;恢复RBP
	ret
	.size	fun, .-fun
	.ident	"GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
	.section	.note.GNU-stack,"",@progbits

通过上述代码和注释,可以看出,此时是使用的是 寄存器%edi 进行传参

栈传值

#include <stdio.h>

int main()
{
    int i = 1;
    fun(i,i,i,i,i,i,i,i,i,i,i,i,i);
    return 0;
}

void fun ( int a, int b, int c, int d, int e, int f, int h, int i, int j, int k, int l, int m, int n)
{
	a = a + b + c + d + e + f + h + i + j + k + l + m + n + 1;
}
        .file   "paramDemo2.c"
        .text
        .globl  main
        .type   main, @function
main:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $80, %rsp
        movl    $1, -4(%rbp)
            
            
        movl    -4(%rbp), %r9d
        movl    -4(%rbp), %r8d
        movl    -4(%rbp), %ecx
        movl    -4(%rbp), %edx
        movl    -4(%rbp), %esi
        movl    -4(%rbp), %eax
        
        
        movl    -4(%rbp), %edi
        movl    %edi, 48(%rsp)
            
        movl    -4(%rbp), %edi
        movl    %edi, 40(%rsp)
            
        movl    -4(%rbp), %edi
        movl    %edi, 32(%rsp)
            
        movl    -4(%rbp), %edi
        movl    %edi, 24(%rsp)
            
        movl    -4(%rbp), %edi
        movl    %edi, 16(%rsp)
            
        movl    -4(%rbp), %edi
        movl    %edi, 8(%rsp)
            
        movl    -4(%rbp), %edi
        movl    %edi, (%rsp)
            
        movl    %eax, %edi
        movl    $0, %eax
        call    fun
        movl    $0, %eax
        leave
        ret
        .size   main, .-main
        .globl  fun
        .type   fun, @function
fun:
        pushq   %rbp                    ;开辟栈帧
        movq    %rsp, %rbp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        movl    %edx, -12(%rbp)
        movl    %ecx, -16(%rbp)
        movl    %r8d, -20(%rbp)
        movl    %r9d, -24(%rbp)

        movl    -8(%rbp), %eax
        movl    -4(%rbp), %edx
        addl    %eax, %edx
        
        movl    -12(%rbp), %eax
        addl    %eax, %edx
        
        movl    -16(%rbp), %eax
        addl    %eax, %edx
        
        movl    -20(%rbp), %eax
        addl    %eax, %edx
        
        movl    -24(%rbp), %eax
        addl    %eax, %edx
        
        movl    16(%rbp), %eax
        addl    %eax, %edx
        
        movl    24(%rbp), %eax
        addl    %eax, %edx
        
        movl    32(%rbp), %eax
        addl    %eax, %edx
        
        movl    40(%rbp), %eax
        addl    %eax, %edx
        
        movl    48(%rbp), %eax
        addl    %eax, %edx
        
        movl    56(%rbp), %eax
        addl    %eax, %edx
        
        movl    64(%rbp), %eax
        addl    %edx, %eax
        
        addl    $1, %eax
        movl    %eax, -4(%rbp)
        popq    %rbp
        ret

通过观察我们可以发现,当寄存器不够使用时,就会使用 寄存器 + 栈内存 的方式 进行传递参数。别人定义的,自己去实现的。

结论:
global表示全局的符号,(变量或者函数)
.section描述节信息
.data表示数据段
.text表示代码段

.code32编译32位的东西可以这么做

数据的可见性

#include <stdio.h>

int data = 0;

int sum(){
    return data;
}

int main(){
    sum();
    return 1;
}

过将该代码编译,可以得到如下汇编代码

        .file   "main.c"
        
        .globl  data
        .bss
        .align 4
        .type   data, @object
        .size   data, 4
data:
        .zero   4
        
        
        .text
        .globl  sum
        .type   sum, @function
sum:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    data(%rip), %eax
        popq    %rbp
        ret
        .size   sum, .-sum
        .globl  main
        .type   main, @function
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $0, %eax
        call    sum
        movl    $1, %eax
        popq    %rbp
        ret

可见,在 sum 函数data 前,都有一个 .global ,我们就想是否是由于 .global 导致了数据和函数的全局可见性呢?为了验证这一点,我们可以将 int data 定义到其他文件,然后将两个文件合并编译,查看是否可以编译成功

// demo.c 文件
#include <stdio.h>

int sum(){
    return data;
}

int main(){
    sum();
    return 1;
}

// data.C 文件
int data = 0

编译的时候会编译不通过,但是这并不是什么问题,是因为一些语法问题,虽然说编译是不涉及到代码之间的整合的,但是我们在之后运行的时候是需要知道怎么去找这个数据的,找一个数据需要怎么找呢? 通过 变量名 + 类型,这两者匹配就可以确定位置了,所以需要给定这两个信息,这就需要使用 extern 关键字了,用它来表明:该数据是在外部文件中定义的,包括 变量名信息 和 变量类型信息

// demo.c 文件
#include <stdio.h>
extern int data;
int sum(){
    return data;
}

int main(){
    sum();
    return 1;
}

单独对该文件进行编译,得到汇编代码:

        .file   "main.c"
        .text
        .globl  sum
        .type   sum, @function
sum:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    data(%rip), %eax
        popq    %rbp
        ret
        .size   sum, .-sum
        .globl  main
        .type   main, @function
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $0, %eax
        call    sum
        movl    $1, %eax
        popq    %rbp
        ret

指针

一个需求:在函数 A 中的一个变量,想要在调用函数 B 后,由 函数 B 将该值修改后返回,并且函数 B 对该值的修改需要对 函数 A 可见。

如果我们需要多个方法中共享一个数据的操作的话,就需要他们同时有该真实数据的内存地址,所以就需要进行内存地址的传递。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXGY3Css-1670667358690)(C:/Doc/typora_pic/1653469335801-8f59b911-d6f5-4893-bec0-78da70749441-16662439264426.jpeg)]

首先我们需要传递数据的地址,就需要拿到数据的地址,在汇编语言层面,是通过lea指令获取到数据的地址,如:lea -10(%ESP)而为了可读性和方便,C语言 中将该取地址操作抽象为了&,类似:&a标识获取a变量的地址。

既然我们拿到了内存地址,那需要用什么去表示当前值是一个内存地址值呢?-----> 在汇编层面呢,是使用了()来表示括号内的值是内存地址,通过(内存地址)获取其中的内容,例如:mov -8(%EPB) %EAX,同样为了方便对()抽象,成为*,类似:void *p

而需要一种类型来接受这个值,也就是指针,但是我们需要知道所指的这块儿内存中存放的数据的宽度,所以就需要借助原有类型来数据宽度,将表示宽度的类型放到 * 之前,表示宽度,如:int *P表示指向一块内存地址,且该内存地址中的数据宽度为 int 的宽度,也就是4字节。

有了上面的推理,我们看一下接下来这段代码

#include <stdio.h>
void incr(int *p){
    (*p)++;
}

int main(){
    int shareData = 1;
    incr(&shareData);   
    return 1;
}

汇编得出以下汇编代码:

main:
        pushq   %rbp
        movq    %rsp, %rbp
        // 开辟栈空间存放 数据
        subq    $16, %rsp
        // 创建变量 shareData
        movl    $1, -4(%rbp)
        // 取地址  放入到 rax 中
        leaq    -4(%rbp), %rax
        // 将 shareData 的地址放入到 rdi 作为传参
        movq    %rax, %rdi
        // 将 eax 归零
        movl    $0, %eax
        call    incr
        movl    $1, %eax
        leave
        ret
        
        
incr:
        pushq   %rbp
        movq    %rsp, %rbp
        // 取参,此时拿到的是 地址 ==> p
        movq    %rdi, -8(%rbp)
        // 将地址信息放入 rax
        movq    -8(%rbp), %rax
        
        
        // 将 rax 中的地址 中 的数据 放入到 eax 中 --->  1
        movl    (%rax), %eax
        //  
        leal    1(%rax), %edx
        // 将地址信息放入 rax 中
        movq    -8(%rbp), %rax
        // 将运算的结果, 放入到 rax 所表示的地址中
        movl    %edx, (%rax)
        
        popq    %rbp
        ret

为何这里实现 (*p)++ 是通过 leal 1(%eax),%edx ?

通过该行代码的后续操作,可见此次往 edx 存放的内容是最终的运算结果 ----- ((*p)++后的结果 2),但是已知lea指令是获取地址的行为呀,

猜想1 : 难道 lea 操作数 等价于 mov 指令?

验证:

  1. 更改汇编代码
incr:
        pushq   %rbp
        movq    %rsp, %rbp
        movq    %rdi, -8(%rbp)
        movq    -8(%rbp), %rax
        movl    (%rax), %eax
        // 更改这个位置
        movl    1(%rax), %edx
        movq    -8(%rbp), %rax
        movl    %edx, (%rax)
        popq    %rbp
        ret
  1. 编译成功,但是执行报错 ----- 段错误

猜想2:既然我无法解决 leal 指令的问题,那么这个1(%rax)是怎么计算的呢?因为 ()是解引用,也即括号内的值是一个地址,但是此处的 rax 中装入的已经是一个数了,为何要解引用呢,难道 立即数(数)表示 这个数 + 立即数 的结果?

验证:

  1. 更改汇编代码如下
incr:
        pushq   %rbp
        movq    %rsp, %rbp
        movq    %rdi, -8(%rbp)
        movq    -8(%rbp), %rax
        movl    (%rax), %eax
        // 更改这个位置
        leal    2, %edx
        movq    -8(%rbp), %rax
        movl    %edx, (%rax)
        popq    %rbp
        ret
  1. 编译成功,且得到输出结果 2

所以目前可以认为 leal num,reg可以将该数存入寄存器中,且imm(num)可以表示 num + imm

论据:

intel 手册中只谈了 lea 指令将地址加载的作用,并不涉及到 lea 一个数

最终得知这是一个技巧

数组是什么

我们再看如下代码

#include <stdio.h>

int main(){
    int arr[] = {1,2,3};
    int *p = &arr[0];
    int a = *P;
    int b = *(p + 1);
    int c = *(p + 2);
}

编译得到汇编代码如下:

main:
        pushq   %rbp
        movq    %rsp, %rbp
        // 数组声明
        movl    $1, -32(%rbp)
        movl    $2, -28(%rbp)
        movl    $3, -24(%rbp)
        
        // int *p = &arr[0]
        // 将 arr[0] 地址放入到 p 中
        leaq    -32(%rbp), %rax
        movq    %rax, -8(%rbp)
        
        // 将地址放入 rax
        movq    -8(%rbp), %rax
        // 将 rax 中的地址 取出数据,放入 eax
        movl    (%rax), %eax
        // 将 eax 中的数据 放入 栈中开辟的变量内存中
        movl    %eax, -12(%rbp)
        // ……
        movq    -8(%rbp), %rax
        movl    4(%rax), %eax
        movl    %eax, -16(%rbp)
        movq    -8(%rbp), %rax
        movl    8(%rax), %eax
        movl    %eax, -20(%rbp)
        movl    $0, %eax
        popq    %rbp
        ret

综上,我们发现通过指针其实是可以实现数组的,或者说指针和数组是一种实现方式,只不过数组在使用上更加方便 ----> 所以我们可以理解为 数组是指针的语法糖

结构体又是什么?

简单的结构体

#include <stdio.h>

struct Student{
    int age;
}

int main(){
    struct Student stu = {666};
    printf("%s",stu.age);
}

如果有这样一个结构体,那么在内存中是怎样去存放数据的呢?

猜想一下:如果是在方法中的话,就会先开辟栈空间,开辟多少靠计算,类似上述代码就是:4字节,但是需要进行内存对齐(cpu规定),所以就应该是开辟 16字节空间

	.file	"demo.c"
	.section	.rodata
.LC0:
	.string	"%s"

	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$16, %rsp				开辟16字节栈空间
	movl	$666, -16(%rbp)			填充 age 属性,立即数$666放入到开辟的空间中
	
	     						*****printf 函数******
	     						
	movl	-16(%rbp), %eax		 	通过eax寄存器交给esi寄存器(字符串操作时,用于存放数据源的地址)
	movl	%eax, %esi	
	movl	$.LC0, %edi				字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作
	movl	$0, %eax
	call	printf
	leave
	ret

结构体 + 字符串

那在内存中是怎样去定位不同的内容的呢?很容易想到通过偏移量来定位,比如我想要得到 id 信息,就需要拿到该结构的初始地址,然后偏移到对应的位置即可。

如果是靠偏移量去做,那不就和数组一样了?只不过是内部数据的类型不统一而已,那我们是否可以拿到一个指针指向初始地址,然后 ++ 遍历获取值呢?其实这是不行的,虽然他类似于数组,但是本质上来说,这并不是数组。(当然如果真的这样操作了,由于C语言没有进行越界检查,还是可以执行成功的,只是结果不会是想见的那样)

struct Student{
    int age;
    char name[4];
}

int main() {
    struct Student stu = {
        666,"aaa"
    };
    printf("%s", stu.name);
}

执行:gcc -S -fno-asynchronous-unwind-tables demo.s

	.file	"hello2.c"
	.section	.rodata     只读数据段
.LC0:
	.string	"%s"
	
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp
	
	subq	$16, %rsp
	movl	$666, -16(%rbp)  	    存立即数
	movl	$6381921, -12(%rbp)		6381921是aaa的阿斯克码
	
	leaq	-16(%rbp), %rax			把地址给到rax寄存器
	addq	$4, %rax				通过rax寄存器将地址给到rsi寄存器
	movq	%rax, %rsi
	movl	$.LC0, %edi
	movl	$0, %eax
	call	printf
	leave
	ret

结论:

1、字符串采用ASCCII编码,放到自己的内存空间栈上
2、“aaa”取出,拼成32位,高八位和低八位依次组合

结构体 + 指针

#include<stdio.h>

struct Student{
    int age;
    char *name;
};

int main() {
    struct Student stu = {
        666,"aaa"
    };
    char name = stu.name[0];
    printf("%s", stu.name);
    return 1;
}
	.file	"demo.c"
		
	     						;***** 只读数据段 ******;
	.section	.rodata
.LC0:
	.string	"aaa"
.LC1:
	.string	"%s"
		
	     						;***** main函数 ******;
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$32, %rsp
	movl	$13, -32(%rbp)			;在开辟的32位中分了4位给666   [666,  ,  ,  ]
	movq	$.LC0, -24(%rbp)		;变成[13, ,.LC0首地址低 , .LC0首地址高 ,  ]
	movq	-24(%rbp), %rax
	movzbl	(%rax), %eax
	movb	%al, -1(%rbp)			;拿到低8位
			
	     						;***** print函数 ******;
	movq	-24(%rbp), %rax
    movq	%rax, %rsi
	movl	$.LC1, %edi
	movl	$0, %eax
	call	printf

	movl	$1, %eax
	leave
	ret

数组不等于指针,指针不等于数组。直接声明的数组会在栈空间中生成,而声名指针会在rodata中生成。

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

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

相关文章

精品基于SSM的民宿预订管理系统

《民宿预订管理系统》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 使用技术&#xff1a; 开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;my…

云原生事件驱动框架(KnativeEventing)

事件驱动是指事件在持续事务管理过程中&#xff0c;进行决策的一种策略。可以通过调动可用资源执行相关任务&#xff0c;从而解决不断出现的问题。通俗地说是当用户触发使用行为时对用户行为的响应。在 Serverless 场景下&#xff0c;事件驱动完美符合其设计初衷之一&#xff1…

基于xv6的Copy-On-Write

为什么需要写时拷贝呢&#xff1f; 当 shell执行指令的时候会 fork()&#xff0c;而这个 fork()出来的进程首先会调用的就是 exec来执行对应的命令&#xff0c;如果我们将 fork()创建的进程对地址空间进行了完整的拷贝,那将是一个巨大的消耗 因为在实际应用中&#xff0c;for…

SSM项目整合

文章目录1. 导入依赖2. 配置web.xml文件3. 编写springmvc.xml配置文件4. 编写spring.xml配置文件5. 编写项目整体业务逻辑6. 开发过程中常见状态码错误1. 导入依赖 在整合SSM项目时&#xff0c;需要导入以下依赖 Spring 相关依赖 &#xff08;springmvc相关、事务相关、spring…

凤姐从国外回来会再次爆红吗?她最近的一段话发人深省

随着互联网的发展&#xff0c;也诞生了很多的网红&#xff0c;其实现如今的众多网红&#xff0c;和凤姐比起来都是小巫见大巫。凤姐原名叫罗玉凤&#xff0c;当年因为参加各种选秀节目&#xff0c;再加上鬼使神差语无伦次的言谈&#xff0c;让她一夜之间爆红了网络。 其实当我们…

Pandas统计计数value_counts()的使用

value_counts()方法返回一个序列Series&#xff0c;该序列包含每个值的数量(对于数据框中的任何列&#xff0c;value_counts()方法会返回该列每个项的计数) value_counts()是Series拥有的方法&#xff0c;一般在DataFrame中使用时&#xff0c;需要指定对哪一列进行使用 语法 …

微服务框架 SpringCloud微服务架构 29 ES 集群 29.5 故障转移

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构29 ES 集群29.5 故障转移29.5.1 ES集群的故障转移29.5.2 总结29 ES 集群 …

【图像分割】粒子群优化T熵图像分割【含Matlab源码 286期】

⛄一、简介 本文所采用的基于熵的切割点和最小描述长度原则(MDLP)。 A.特征选择 特征选择是一个组合优化问题&#xff0c;因为在具有N个特征的数据集上有2N个可能的不同特征子集。FS方法通常有两个重要的部分组成&#xff0c;即搜索技术和特征评估方法。 在特征评估方面&am…

Python项目之文化和旅游数据可视化

文章目录关键词一、做什么二、怎么做1.爬取数据和处理2.数据库设计&#xff0c;并将数据写入数据库3.开发后端接口4.使用Echarts官方模板三、效果展示关键词 Python后端开发Python网络爬虫Echarts可视化面向对象(见源码) 一、做什么 国家5A级旅游景区数据可视化 国家级旅游休…

状态设计模式

一、状态模式 1、定义 状态模式&#xff08;State Pattern&#xff09;又称作状态机模式&#xff08;State Machine Pattern&#xff09;&#xff0c;允许对象在内部状态发生改变时改变它的行为&#xff0c;对象看起来好像修改了它的类。属于行为型设计模式。 状态模式的核心是…

Python:面向对象

目录 一、类的定义 基本语法 python与Java关于类的封装的区别 自定义构造方法 Java与Python的区别 源码 同一个类多种构造函数的构建 源码 二、魔法方法 基础部分 比较部分 与Java的区别 容器类型 三、属性管理 四、封装 基础部分 方法拓展 五、继承&多态 继…

基础IO——系统调用文件

文章目录1. 知识补充和回顾1.1 回顾C文件接口1.2 理论理解2. 系统调用文件接口2.1 open2.2 怎么使用2.3 close和write2.4 read1. 知识补充和回顾 1. 文件文件内容文件属性。即使创建一个空文件&#xff0c;也会占据磁盘数据。 2. 文件操作文件内容操作文件属性操作。在操作文件…

域名+七牛云+PicGo+pypora

域名七牛云PicGopypora 前提准备&#xff1a; 域名&#xff08;自己的域名&#xff09;七牛云 免费注册申请10G空间够用picGo 地址pypora &#xff08;自行下载&#xff09; GO&#xff01;&#xff01;&#xff01; 七牛云 注册--->登录--->控制台找到对象存储新建…

如何在AndroidStudio中使用GitHub

文章目录1.确认是否安装git2.添加GitHub账户3.创建库4.创建分支5. push内容在项目中肯定要集成版本管理工具&#xff0c;不过有时候更换电脑或者升级AndroidStudio时原来集成的内容就不在了&#xff0c;还在再次集成git。时间长了就容易忘记如何集成Git&#xff0c;因此整理总结…

工作10年我面试过上百个程序员,真想对他们说…

V-xin&#xff1a;ruyuanhadeng获得600页原创精品文章汇总PDF 一、写在前面 最近收到不少读者反馈&#xff0c;说自己在应聘一些中大型互联网公司的Java工程师岗位时遇到了不少困惑。 这些同学说自己也做了精心准备&#xff0c;网上搜集了不少Java面试题&#xff0c;然而实际…

详解c++---内存管理

这里写目录标题c语言在堆上申请空间malloccallocreallocfreec中向堆中申请空间的形式new的介绍delete的介绍new与自定义类型new与malloc的不同定位newoperator new与operator delete函数c语言在堆上申请空间 在之前的学习中我们知道c语言主要是通过malloc free calloc&#xf…

Java基础-常用API的使用方法(Math,System,Runtime,Object,BigInteger,BigDecimal)(1)

1 Math类 1.1 概述 tips&#xff1a;了解内容 查看API文档&#xff0c;我们可以看到API文档中关于Math类的定义如下&#xff1a; Math类所在包为java.lang包&#xff0c;因此在使用的时候不需要进行导包。并且Math类被final修饰了&#xff0c;因此该类是不能被继承的。 Math类…

J - 食物链 POJ - 1182

思路&#xff1a; 首先我们要理清楚三种动物之间的关系&#xff0c;那么可以用A到B的距离为1代表为A吃B&#xff0c; 那么就有下图的关系 那么我们用d1表示吃&#xff0c;d2表示被吃&#xff0c;d3表示是同类 对于另一张图也是符合的 然后我们去找每个点和他的根节点的关系 …

resnet(4)------全连接层与softmax

文章目录1. 全连接层2. SoftMax算法1. 全连接层 全连接层&#xff0c;指的是每一个结点都与上一层的所有结点相连&#xff0c;用来把前面几层提取到的特征综合起来。 举个例子&#xff0c;前面通过卷积和池化层提取出来的特征有眼睛鼻子和嘴巴&#xff0c;那我们能单独通过这…

基于tensorflow的深层神经网络(一)为什么神经网络需要解决非线性和异或问题

参考为什么神经网络需要解决多层和非线性问题 - 云社区 - 腾讯云 维基百科对深度学习的精确定义为“一类通过多层非线性变换对高复杂性数据建模算法的合集”。因为深度神经网络是实现“多层非线性变换”最常用的一种方法&#xff0c;所以在实际中基本上可以认为深度学习就是深度…