C语言与汇编

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

本书余下的篇幅讨论源代码文件中的内容;本节讨论创建源代码文 件的技巧。有些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 %rbpmovq    %rsp, %rbpmovl  $.LC0, %edicall putsmovl    $0, %eaxpopq    %rbpret
.LFE0:.size main, .-main.ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)".section    .note.GNU-stack,"",@progbits

解释:

从上面的代码中我们可以看见,在进入 mian 函数后首先会处理 rbprsp,并且在调用 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函数首地址endbr64pushq   %rbp              ;将此时的rbp压栈movq    %rsp, %rbp          ; rbp = rspmovl    $.LC0, %edicall    puts movl    $1, %eax              ;将函数的返回值 放入到 eax 中【约定俗成】popq    %rbp          ;rbp = popret.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 中leaveret.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  %rbpmovq    %rsp, %rbp                 subq $16, %rsp              ;int i = 1 开辟栈空间并存放值movl    $1, -4(%rbp)movl    -4(%rbp), %eax         ;存放参数,通过 eax 中转movl   %eax, %edimovl  $0, %eaxcall    funmovl $0, %eaxleaveret.size   main, .-main.globl  fun.type    fun, @function
fun:pushq   %rbp                  ;开辟栈帧movq %rsp, %rbpmovl  %edi, -4(%rbp)        ;获取参数addl $1, -4(%rbp)          ;执行代码popq %rbp                  ;恢复RBPret.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   %rbpmovq    %rsp, %rbpsubq    $80, %rspmovl    $1, -4(%rbp)movl    -4(%rbp), %r9dmovl    -4(%rbp), %r8dmovl    -4(%rbp), %ecxmovl    -4(%rbp), %edxmovl    -4(%rbp), %esimovl    -4(%rbp), %eaxmovl    -4(%rbp), %edimovl    %edi, 48(%rsp)movl    -4(%rbp), %edimovl    %edi, 40(%rsp)movl    -4(%rbp), %edimovl    %edi, 32(%rsp)movl    -4(%rbp), %edimovl    %edi, 24(%rsp)movl    -4(%rbp), %edimovl    %edi, 16(%rsp)movl    -4(%rbp), %edimovl    %edi, 8(%rsp)movl    -4(%rbp), %edimovl    %edi, (%rsp)movl    %eax, %edimovl    $0, %eaxcall    funmovl    $0, %eaxleaveret.size   main, .-main.globl  fun.type   fun, @function
fun:pushq   %rbp                    ;开辟栈帧movq    %rsp, %rbpmovl    %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), %eaxmovl    -4(%rbp), %edxaddl    %eax, %edxmovl    -12(%rbp), %eaxaddl    %eax, %edxmovl    -16(%rbp), %eaxaddl    %eax, %edxmovl    -20(%rbp), %eaxaddl    %eax, %edxmovl    -24(%rbp), %eaxaddl    %eax, %edxmovl    16(%rbp), %eaxaddl    %eax, %edxmovl    24(%rbp), %eaxaddl    %eax, %edxmovl    32(%rbp), %eaxaddl    %eax, %edxmovl    40(%rbp), %eaxaddl    %eax, %edxmovl    48(%rbp), %eaxaddl    %eax, %edxmovl    56(%rbp), %eaxaddl    %eax, %edxmovl    64(%rbp), %eaxaddl    %edx, %eaxaddl    $1, %eaxmovl    %eax, -4(%rbp)popq    %rbpret

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

结论:
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   %rbpmovq    %rsp, %rbpmovl    data(%rip), %eaxpopq    %rbpret.size   sum, .-sum.globl  main.type   main, @function
main:pushq   %rbpmovq    %rsp, %rbpmovl    $0, %eaxcall    summovl    $1, %eaxpopq    %rbpret

可见,在 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   %rbpmovq    %rsp, %rbpmovl    data(%rip), %eaxpopq    %rbpret.size   sum, .-sum.globl  main.type   main, @function
main:pushq   %rbpmovq    %rsp, %rbpmovl    $0, %eaxcall    summovl    $1, %eaxpopq    %rbpret

指针

一个需求:在函数 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   %rbpmovq    %rsp, %rbp// 开辟栈空间存放 数据subq    $16, %rsp// 创建变量 shareDatamovl    $1, -4(%rbp)// 取地址  放入到 rax 中leaq    -4(%rbp), %rax// 将 shareData 的地址放入到 rdi 作为传参movq    %rax, %rdi// 将 eax 归零movl    $0, %eaxcall    incrmovl    $1, %eaxleaveretincr:pushq   %rbpmovq    %rsp, %rbp// 取参,此时拿到的是 地址 ==> pmovq    %rdi, -8(%rbp)// 将地址信息放入 raxmovq    -8(%rbp), %rax// 将 rax 中的地址 中 的数据 放入到 eax 中 --->  1movl    (%rax), %eax//  leal    1(%rax), %edx// 将地址信息放入 rax 中movq    -8(%rbp), %rax// 将运算的结果, 放入到 rax 所表示的地址中movl    %edx, (%rax)popq    %rbpret

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

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

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

验证:

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

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

验证:

  1. 更改汇编代码如下
incr:pushq   %rbpmovq    %rsp, %rbpmovq    %rdi, -8(%rbp)movq    -8(%rbp), %raxmovl    (%rax), %eax// 更改这个位置leal    2, %edxmovq    -8(%rbp), %raxmovl    %edx, (%rax)popq    %rbpret
  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   %rbpmovq    %rsp, %rbp// 数组声明movl    $1, -32(%rbp)movl    $2, -28(%rbp)movl    $3, -24(%rbp)// int *p = &arr[0]// 将 arr[0] 地址放入到 p 中leaq    -32(%rbp), %raxmovq    %rax, -8(%rbp)// 将地址放入 raxmovq    -8(%rbp), %rax// 将 rax 中的地址 取出数据,放入 eaxmovl    (%rax), %eax// 将 eax 中的数据 放入 栈中开辟的变量内存中movl    %eax, -12(%rbp)// ……movq    -8(%rbp), %raxmovl    4(%rax), %eaxmovl    %eax, -16(%rbp)movq    -8(%rbp), %raxmovl    8(%rax), %eaxmovl    %eax, -20(%rbp)movl    $0, %eaxpopq    %rbpret

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

结构体又是什么?

简单的结构体

#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  %rbpmovq    %rsp, %rbpsubq  $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, %eaxcall    printfleaveret

结构体 + 字符串

那在内存中是怎样去定位不同的内容的呢?很容易想到通过偏移量来定位,比如我想要得到 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  %rbpmovq    %rsp, %rbpsubq  $16, %rspmovl   $666, -16(%rbp)         存立即数movl    $6381921, -12(%rbp)     6381921是aaa的阿斯克码leaq    -16(%rbp), %rax         把地址给到rax寄存器addq $4, %rax                通过rax寄存器将地址给到rsi寄存器movq %rax, %rsimovl  $.LC0, %edimovl $0, %eaxcall    printfleaveret

结论:

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  %rbpmovq    %rsp, %rbpsubq  $32, %rspmovl   $13, -32(%rbp)          ;在开辟的32位中分了4位给666   [666,  ,  ,  ]movq  $.LC0, -24(%rbp)        ;变成[13, ,.LC0首地址低 , .LC0首地址高 ,  ]movq   -24(%rbp), %raxmovzbl   (%rax), %eaxmovb    %al, -1(%rbp)           ;拿到低8位;***** print函数 ******;movq    -24(%rbp), %raxmovq %rax, %rsimovl  $.LC1, %edimovl $0, %eaxcall    printfmovl  $1, %eaxleaveret

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

【C语言与汇编】简单学学C到汇编代码相关推荐

  1. 【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 )

    相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...

  2. c语言编译流程简单整理以及简单makefile编写

    c语言编译流程简单整理以及简单makefile编写 前言: 最近接触了makefile.为了学习makefile,就去了解了部分需要的知识,目前先简单的整理在这里. c语言编译过程 C语言在编译过程中 ...

  3. c语言管理系统实践体会,C语言实践实习工作个人心得体会汇编

    <C语言实践实习工作个人心得体会汇编>由会员分享,可在线阅读,更多相关<C语言实践实习工作个人心得体会汇编(4页珍藏版)>请在人人文库网上搜索. 1.欢迎阅读优秀材料c语言实践 ...

  4. 语言axff所占字节数_【每日一答】(74)数组名v.s.指针变量,C语言其实很简单形象比喻为“是一个朋友圈的”...

    下图选自<C语言其实很简单>第8章: 问:请问张老师!书上第227页一句话:指针变量a本身的地址(a所在内存字节编号)是数组的地址,数值上与元素a[0]的地址相等! 这句话是什么意思,我看 ...

  5. 语言都是相通的,学好一门语言,再学第二门语言就很简单,记录一下我复习c语言的过程。...

    语言都是相通的,学好一门语言,再学第二门语言就很简单,记录一下我复习c语言的过程. 为了将本人的python培训提高一个层次,本人最近买了很多算法的书. 这个书上的代码基本都是c语言实现的,c语言很久 ...

  6. R语言grafify包简单、快速绘制19个漂亮的统计图实战

    R语言grafify包简单.快速绘制19个漂亮的统计图实战 目录 R语言grafify包简单.快速绘制19个漂亮的统计图实战 #grafify是什么?

  7. c语言游戏总出bug,C语言 编写的简单移动游戏出现bug?_编程_游戏设计_C语言_天涯问答_天涯社区...

    C语言 编写的简单移动游戏出现bug? 第一次按上下左右没什么问题,之后就会卡住..不知道出了什么错 源码如下:(用vc++的win32 console程序编写) #include #include ...

  8. 简单算法的举例c语言,计算机科学与技术系C语言程序设计22简单算法举例.PPT

    计算机科学与技术系C语言程序设计22简单算法举例 第2章 程序的灵魂--算法 本章主要介绍算法的思想及算法的表示方法. 2.0 绪论 2.1 算法的概念 2.2 简单算法举例 2.3 算法的特性 2. ...

  9. c语言表现一些简单的图片,C语言的一些简单例题.doc

    C语言的一些简单例题 基础知识 例1.1 分析下面程序的输出结果. void main() {int a,b,c; a=2;b=3; c=a+b; printf("\nThe sum of ...

最新文章

  1. 如何从用户体验的角度去做一个网站的页面设计
  2. 应用程序启动器选项卡以及页面内容的设置
  3. CEO 赠书 |《跨越鸿沟》如何将梦想变为现实?
  4. 浏览器直接访问Linux云服务器下的文件
  5. cppcheck值得注意的一些筛选项
  6. 消息称小米之家全面取消员工销售提成 回应:内容严重偏颇失实
  7. break;continue语句
  8. 【转】无线路由器密码破解
  9. js 导出 excel
  10. osip和mysql_osip2/eXosip2调试笔记
  11. Mysql8.x主从基础同步
  12. java程序设计--孙鑫java无难事Lesson3《包、类和方法说明符、垃圾回收、接口》
  13. Python实现大文本文件分割成多个小文件
  14. python和pyqt5入门之简易汇率转换器
  15. 我是如何一步一步爬上 「64K限制」 的坑
  16. 多端开发之uniapp开发app
  17. oracle12c密码登录失败,【译】解决Oracle12c Cloud Control登录验证出错问题
  18. Keil用ST-LINK下载STM32程序后不自动运行
  19. 树的前序,中序,后序遍历。
  20. 区块链技术正向积极乐观的智能前景发展

热门文章

  1. Python学习之requests接口测试
  2. 模拟微信联系人右侧字母滑动
  3. 常用的javascript js正则
  4. SpringBoot + Ajax 实现个人账目管理系统 Ajax如此简单~
  5. Three.js建模基础
  6. 为IE内核的WebBrowser控件内存泄漏所烦恼的可以考虑用Cefsharp代替它!
  7. 智能家居主战场:小米生态链VS华为联盟
  8. android 著名播放器,【精华】十二大最著名的Android播放器开源项目
  9. SCSI协议与iSCSI协议
  10. 使用 EA 交易可视向导创建 EA 交易