热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

x86的c语言和arm64的c语言,X8664和ARM64用户栈的结构(5)mian()函数和子函数之间的栈...

main函数及其子函数之间的栈1工具及实验程序本文的实验在一个虚拟机中进行,虚拟机模拟的cpu是x86-64(Intel(R)Core(TM)i7-6820HQCPU

main函数及其子函数之间的栈

1 工具及实验程序

本文的实验在一个虚拟机中进行,虚拟机模拟的cpu是x86-64(Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz),运行的是64bit ubuntu,安装了ARM64的工具链:

$sudo apt-get install gcc-aarch64-linux-gnu

$sudo apt-get install gcc-arm-linux-gnueabi

实验使用的程序为:

#include

#include

int func_C(int x1, int x2, int x3, int x4, int x5, int x6){

int sum = 0;

sum = x1 + x2;

sum = sum + x3 + x4;

sum = sum + x5 + x6;

return sum;

}

int func_B(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, char x9){

int sum = 0;

sum = func_C(x1, x2, x3, x4, x5,x6);

sum = sum + x7;

sum = sum + x8;

sum += x9;

return sum;

}

void func_A(void){

int sum = 0;

int x1 = 1;

int x2 = 2;

int x3 = 3;

int x4 = 4;

int x5 = 5;

int x6 = 6;

int x7 = 7;

int x8 = 8;

char x9 = 'c';

sum = func_B(x1, x2, x3, x4, x5, x6, x7, x8, x9);

printf("sum = %d\n", sum);

}

int main(int argc, char *argv[], char *envp[])

{

int count = argc;

char **p_argv = argv;

char **p_env = envp;

func_A();

return 0;

}

2 x86-64

2.1 main函数的栈

int main(int argc, char *argv[], char *envp[])

{

int count = argc;

char **p_argv = argv;

char **p_env = envp;

func_A();

return 0;

}

首先,编译源程序 $gcc test.c -o test -fno-stack-protector,然后反汇编出main函数 $gdb test:

(gdb) disas main

Dump of assembler code for function main:

0x0000000000000790 : push %rbp

0x0000000000000791 : mov %rsp,%rbp

0x0000000000000794 : sub $0x40,%rsp

0x0000000000000798 : mov %edi,-0x24(%rbp)

0x000000000000079b : mov %rsi,-0x30(%rbp)

0x000000000000079f : mov %rdx,-0x38(%rbp)

0x00000000000007a3 : mov -0x24(%rbp),%eax

0x00000000000007a6 : mov %eax,-0x4(%rbp)

0x00000000000007a9 : mov -0x30(%rbp),%rax

0x00000000000007ad : mov %rax,-0x10(%rbp)

0x00000000000007b1 : mov -0x38(%rbp),%rax

0x00000000000007b5 : mov %rax,-0x18(%rbp)

0x00000000000007b9 : callq 0x6fd

0x00000000000007be : mov $0x0,%eax

0x00000000000007c3 : leaveq

0x00000000000007c4 : retq

End of assembler dump.

从上面对main函数栈的分析可以知道,一个函数栈大致会做以下几件事:

保存上一个栈帧的信息。

0x0000000000000790 : push %rbp

0x0000000000000791 : mov %rsp,%rbp

在x86-64上rbp和rsp完全可以描绘出一个栈帧,rbp被称为帧指针(frame pointer),rsp被称为栈指针(stack pointer);rbp+0x08指向当前栈帧的底部,rsp指向栈帧的顶部。下面的第一句是将上一个栈帧的帧指针rbp压栈保存起来;rbp保存起来后,紧接着下一句汇编就为rbp赋予一个新值,将栈指针rsp的值赋给帧指针rbp,让rbp指向当前栈帧的底部,其实rbp+0x08才是当前栈帧的底部,只不过rbp+0x08处是上一个函数运行call指令时硬件自动存放的rip,对软件不可见。

4f2f84d3667063fde0831ca78758450f.png

aa4b450dd7d59131d4cb8772722757f2.png

开栈。

0x0000000000000794 : sub $0x40,%rsp

也就是开辟当前栈帧的空间,开辟的栈帧空间主要用于接收参数、存放局部变量以及运算的场所,下面的汇编开辟0x40个字节的空间。

43902622bfe703cfebeb34d4ce7126c4.png

接收参数。

0x0000000000000798 : mov %edi,-0x24(%rbp)

0x000000000000079b : mov %rsi,-0x30(%rbp)

0x000000000000079f : mov %rdx,-0x38(%rbp)

前面也说过,x86-64参数传递的规则是rdi传递第一个参数、rsi传递第二个参数、rdx传递第三个参数....。mian函数的一个形参是argc,第二个形参是argv,第三个形参是envp。从下面的汇编也可以看出,实参在rdi、rsi和rdx中,然后分别放到rbp - 0x24 、rbp - 0x30和rbp -0x38形参的位置处。

47142bc6cf39b75c16fac64bd37acb04.png

栈上运算。

0x00000000000007a3 : mov -0x24(%rbp),%eax

0x00000000000007a6 : mov %eax,-0x4(%rbp)

0x00000000000007a9 : mov -0x30(%rbp),%rax

0x00000000000007ad : mov %rax,-0x10(%rbp)

0x00000000000007b1 : mov -0x38(%rbp),%rax

0x00000000000007b5 : mov %rax,-0x18(%rbp)

0x00000000000007b9 : callq 0x6fd

栈的最大作用就是作为运算场所,main的栈上并没有安排太多的运算,仅仅做了三次赋值,然后主要工作就就转给子函数了。下面的一二两句的作用是将argc的值赋值给count;三四句的作用是将argv的值赋给p_argv;五六两句的作用是将envp赋值给p_envl;最后一句就是调用子函数,我们也把他看做是运算的一部分。

9e078fa368fd066bdba1ae9a1970c145.png

设置返回值。x86-64的函数返回值使用rax传递。从return 0可知函数的返回值是0,因此将0赋给eax。

0x00000000000007be : mov $0x0,%eax

恢复上一个栈帧。先看一下相反的操作保存栈帧的两个步骤:一是将帧指针(rbp)压栈;二是将栈指针(rsp)的值赋值给帧指针(rbp),可知,上一个栈帧结构可以由当前的帧指针rbp推导出,也即下面汇编语句leaveq的作用,该语句的作用有两个:一是将帧指针(rbp)的值赋值给栈指针(rsp),即mov %rbp, %rsp;二是将帧指针(rbp)出栈,即pop %rbp。正好和保存上一个栈帧结构的操作相反。leaveq指令执行完后,帧指针(rbp)已经切换回caller的栈了,也即数据存储区已经切换完成,只差函数控制切换。

0x00000000000007c3 : leaveq

为了更加深入的了解如何恢复栈帧,画了一个如下所示的图,rsp和rbp指向了current frame,也就是说寄存器rbp和rsp并没有指向任何"previous frame",恢复上一个栈帧的核心问题在于如何让rbp和rsp指向上一个栈帧,答案的钥匙就是rbp寄存器。rbp指向的就是上一个rbp,rbp-16就是上一个rsp, 直觉上恢复上一个栈帧就是将rbp-16赋值给rsp,并将rbp指向的值赋值给rbp,显示上述方法可以恢复rsp和rbp,但是事实上并没有使用上述方法,而是利用栈自然而然的恢复上一个栈帧,所谓的自然而然就是怎么来怎么回去。首先让rbp赋值给rsp,然后pop一次栈即可恢复rbp,再pop栈一次即可恢复rsp。“首先让rbp赋值给rsp,然后pop一次栈即可恢复rbp”是人类发明的指令leaveq干的,"再pop栈一次即可恢复rsp"是人类发明的指令ret干的。

04727d68a032da70ddd794f5b7f431ee.png

函数控制转移。完成函数控制切换,说白了也就是让CPU接着执行caller函数中callee函数后面的指令。下面的指令使用栈指针指向的值恢复rip,功能可以按照mov (%sp), %rip; addq $8,%rsp或pop %rip来理解。该指令执行完后,函数的控制以及栈指针(rsp)切换完成。 retq指令改变了rsp 和 rip的值。

0x00000000000007c4 : retq

ccbccbacc36e9abe128a6702255b670a.png

2. 2 子函数的栈

2.2.1 func_A的栈

void func_A(void){

int sum = 0;

int x1 = 1;

int x2 = 2;

int x3 = 3;

int x4 = 4;

int x5 = 5;

int x6 = 6;

int x7 = 7;

int x8 = 8;

char x9 = 'c';

sum = func_B(x1, x2, x3, x4, x5, x6, x7, x8, x9);

printf("sum = %d\n", sum);

}

首先,编译源程序 $gcc test.c -o test -fno-stack-protector,然后反汇编出func_A函数 $gdb test:

(gdb) disas func_A

Dump of assembler code for function func_A:

0x00000000000006fd : push %rbp

0x00000000000006fe : mov %rsp,%rbp

0x0000000000000701 : sub $0x30,%rsp

0x0000000000000705 : movl $0x0,-0x4(%rbp)

0x000000000000070c : movl $0x1,-0x8(%rbp)

0x0000000000000713 : movl $0x2,-0xc(%rbp)

0x000000000000071a : movl $0x3,-0x10(%rbp)

0x0000000000000721 : movl $0x4,-0x14(%rbp)

0x0000000000000728 : movl $0x5,-0x18(%rbp)

0x000000000000072f : movl $0x6,-0x1c(%rbp)

0x0000000000000736 : movl $0x7,-0x20(%rbp)

0x000000000000073d : movl $0x8,-0x24(%rbp)

0x0000000000000744 : movb $0x63,-0x25(%rbp)

0x0000000000000748 : movsbl -0x25(%rbp),%edi

0x000000000000074c : mov -0x1c(%rbp),%r9d

0x0000000000000750 : mov -0x18(%rbp),%r8d

0x0000000000000754 : mov -0x14(%rbp),%ecx

0x0000000000000757 : mov -0x10(%rbp),%edx

0x000000000000075a : mov -0xc(%rbp),%esi

0x000000000000075d : mov -0x8(%rbp),%eax

0x0000000000000760 : push %rdi

0x0000000000000761 : mov -0x24(%rbp),%edi

0x0000000000000764 : push %rdi

0x0000000000000765 : mov -0x20(%rbp),%edi

0x0000000000000768 : push %rdi

0x0000000000000769 : mov %eax,%edi

0x000000000000076b : callq 0x699

0x0000000000000770 : add $0x18,%rsp

0x0000000000000774 : mov %eax,-0x4(%rbp)

0x0000000000000777 : mov -0x4(%rbp),%eax

0x000000000000077a : mov %eax,%esi

0x000000000000077c : lea 0xd1(%rip),%rdi # 0x854

0x0000000000000783 : mov $0x0,%eax

0x0000000000000788 : callq 0x520

0x000000000000078d : nop

0x000000000000078e : leaveq

0x000000000000078f : retq

End of assembler dump.

上述汇编做的事情也是那几个,保存上一个栈帧的信息、开栈、接受参数、栈上运算....,下面将进行分析。

保存上一个栈帧的信息

0x00000000000006fd : push %rbp

0x00000000000006fe : mov %rsp,%rbp

保存上一个栈帧的作用就是为了该函数被调用完成后还能再回到caller函数的栈帧继续执行,需要把caller的栈帧保存起来,保存地点就是callee的栈帧上。上述汇编的第一句就是把帧指针rbp入栈保存在栈上,第二句把栈指针rsp保存在帧指针rbp中。上述两句执行完后,func_C的栈布局如下图所示。

68c43464d9c866cd9a935875fbec639b.png

开栈

0x0000000000000701 : sub $0x30,%rsp

开栈的操作比较简单,就是把rsp的值减小,开辟出一片连续的内存区域用作接收参数,存放局部变量以及栈上运算。不过func_C没有参数和栈上运算。执行完上述汇编后,栈的布局如下图所示。

476046e530c5ddefc4f0afa8fa9761e3.png

接受参数。func_A不涉及。

栈上运算

0x0000000000000705 : movl $0x0,-0x4(%rbp)

0x000000000000070c : movl $0x1,-0x8(%rbp)

0x0000000000000713 : movl $0x2,-0xc(%rbp)

0x000000000000071a : movl $0x3,-0x10(%rbp)

0x0000000000000721 : movl $0x4,-0x14(%rbp)

0x0000000000000728 : movl $0x5,-0x18(%rbp)

0x000000000000072f : movl $0x6,-0x1c(%rbp)

0x0000000000000736 : movl $0x7,-0x20(%rbp)

0x000000000000073d : movl $0x8,-0x24(%rbp)

0x0000000000000744 : movb $0x63,-0x25(%rbp)

这里的栈上运算比较简单,只是对局部变量进行赋值。局部变量赋值过后的栈空间如下图所示:

53488ebf2e269b9900cee39a31e6057d.png

参数传递

0x0000000000000748 : movsbl -0x25(%rbp),%edi

0x000000000000074c : mov -0x1c(%rbp),%r9d

0x0000000000000750 : mov -0x18(%rbp),%r8d

0x0000000000000754 : mov -0x14(%rbp),%ecx

0x0000000000000757 : mov -0x10(%rbp),%edx

0x000000000000075a : mov -0xc(%rbp),%esi

0x000000000000075d : mov -0x8(%rbp),%eax

0x0000000000000760 : push %rdi

0x0000000000000761 : mov -0x24(%rbp),%edi

0x0000000000000764 : push %rdi

0x0000000000000765 : mov -0x20(%rbp),%edi

0x0000000000000768 : push %rdi

0x0000000000000769 : mov %eax,%edi

前面也说过,在X86-64平台,当参数小于7个时使用寄存器传参 。当参数个数大于等于7时,参数arg1~arg6分别使用寄存器rdi,rsi, rdx, rcx, r8 and r9传参,其余参数使用栈传递。如下图所示,实参x1~x6使用寄存器rdi,rsi, rdx, rcx, r8 and r9传参,实参x7~x9使用栈传递。

680c1e86e3c1d58605f4b1011259285d.png

2.2.2 func_B的栈

int func_B(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, char x9){

int sum = 0;

sum = func_C(x1, x2, x3, x4, x5,x6);

sum = sum + x7;

sum = sum + x8;

sum += x9;

return sum;

}

首先,编译源程序 $gcc test.c -o test -fno-stack-protector,然后反汇编出func_B函数 $gdb test:

(gdb) disas func_B

Dump of assembler code for function func_B:

0x0000000000000699 : push %rbp

0x000000000000069a : mov %rsp,%rbp

0x000000000000069d : sub $0x30,%rsp

0x00000000000006a1 : mov %edi,-0x14(%rbp)

0x00000000000006a4 : mov %esi,-0x18(%rbp)

0x00000000000006a7 : mov %edx,-0x1c(%rbp)

0x00000000000006aa : mov %ecx,-0x20(%rbp)

0x00000000000006ad : mov %r8d,-0x24(%rbp)

0x00000000000006b1 : mov %r9d,-0x28(%rbp)

0x00000000000006b5 : mov 0x20(%rbp),%eax

0x00000000000006b8 : mov %al,-0x2c(%rbp)

0x00000000000006bb : movl $0x0,-0x4(%rbp)

0x00000000000006c2 : mov -0x28(%rbp),%r8d

0x00000000000006c6 : mov -0x24(%rbp),%edi

0x00000000000006c9 : mov -0x20(%rbp),%ecx

0x00000000000006cc : mov -0x1c(%rbp),%edx

0x00000000000006cf : mov -0x18(%rbp),%esi

0x00000000000006d2 : mov -0x14(%rbp),%eax

0x00000000000006d5 : mov %r8d,%r9d

0x00000000000006d8 : mov %edi,%r8d

0x00000000000006db : mov %eax,%edi

0x00000000000006dd : callq 0x64a

0x00000000000006e2 : mov %eax,-0x4(%rbp)

0x00000000000006e5 : mov 0x10(%rbp),%eax

0x00000000000006e8 : add %eax,-0x4(%rbp)

0x00000000000006eb : mov 0x18(%rbp),%eax

0x00000000000006ee : add %eax,-0x4(%rbp)

0x00000000000006f1 : movsbl -0x2c(%rbp),%eax

0x00000000000006f5 : add %eax,-0x4(%rbp)

0x00000000000006f8 : mov -0x4(%rbp),%eax

0x00000000000006fb : leaveq

0x00000000000006fc : retq

End of assembler dump.

一个函数的汇编做的还是那几件事,下面分析:

保存上一个栈帧信息

c1762c2315cb8f7741d674363ca38be3.png

0x0000000000000699 : push %rbp

0x000000000000069a : mov %rsp,%rbp

上一个栈帧的帧指针rbp保存在当前栈帧上, 上一个栈帧的栈指针rsp保存在当前栈帧的帧指针rbp寄存器中。

开栈

0x000000000000069d : sub $0x30,%rsp

在栈上开辟一块连续的地址用于接收参数,局部变量,栈上运算。

6b5975ad3fa79005163b221f5800c344.png

接收参数

0x00000000000006a4 : mov %esi,-0x18(%rbp)

0x00000000000006a7 : mov %edx,-0x1c(%rbp)

0x00000000000006aa : mov %ecx,-0x20(%rbp)

0x00000000000006ad : mov %r8d,-0x24(%rbp)

0x00000000000006b1 : mov %r9d,-0x28(%rbp)

0x00000000000006b5 : mov 0x20(%rbp),%eax

0x00000000000006b8 : mov %al,-0x2c(%rbp)

函数总共有9个参数,寄存器传递6个参数,栈传递三个参数。

0d9bd47ebf76a816cc3854cb1843e2e4.png

传递参数

0x00000000000006c2 : mov -0x28(%rbp),%r8d

0x00000000000006c6 : mov -0x24(%rbp),%edi

0x00000000000006c9 : mov -0x20(%rbp),%ecx

0x00000000000006cc : mov -0x1c(%rbp),%edx

0x00000000000006cf : mov -0x18(%rbp),%esi

0x00000000000006d2 : mov -0x14(%rbp),%eax

0x00000000000006d5 : mov %r8d,%r9d

0x00000000000006d8 : mov %edi,%r8d

0x00000000000006db : mov %eax,%edi

调用函数有6个参数,这6个参数都使用寄存器传递。

b4498ccbba44aba2c952cccd83289f83.png

栈上运算

0x00000000000006dd : callq 0x64a

0x00000000000006e2 : mov %eax,-0x4(%rbp)

0x00000000000006e5 : mov 0x10(%rbp),%eax

0x00000000000006e8 : add %eax,-0x4(%rbp)

0x00000000000006eb : mov 0x18(%rbp),%eax

0x00000000000006ee : add %eax,-0x4(%rbp)

0x00000000000006f1 : movsbl -0x2c(%rbp),%eax

0x00000000000006f5 : add %eax,-0x4(%rbp)

运算第一步就是将func_A的返回值%eax赋值给sum;第二步是将x7的值和sum相加放在sum中;第三步是将x8的和sum相加结果放在sum中;第三步是将x9的值扩展成32bit,和sum相加结果放在sum中。

2ae4d64f9989f04584145151f69be81c.png

2.2.3 func_C的栈

int func_C(int x1, int x2, int x3, int x4, int x5, int x6){

int sum = 0;

sum = x1 + x2;

sum = sum + x3 + x4;

sum = sum + x5 + x6;

return sum;

}

首先,编译源程序 $gcc test.c -o test -fno-stack-protector,然后反汇编出func_A函数 $gdb test:

(gdb) disas func_C

Dump of assembler code for function func_C:

0x000000000000064a : push %rbp

0x000000000000064b : mov %rsp,%rbp

0x000000000000064e : mov %edi,-0x14(%rbp)

0x0000000000000651 : mov %esi,-0x18(%rbp)

0x0000000000000654 : mov %edx,-0x1c(%rbp)

0x0000000000000657 : mov %ecx,-0x20(%rbp)

0x000000000000065a : mov %r8d,-0x24(%rbp)

0x000000000000065e : mov %r9d,-0x28(%rbp)

0x0000000000000662 : movl $0x0,-0x4(%rbp)

0x0000000000000669 : mov -0x14(%rbp),%edx

0x000000000000066c : mov -0x18(%rbp),%eax

0x000000000000066f : add %edx,%eax

0x0000000000000671 : mov %eax,-0x4(%rbp)

0x0000000000000674 : mov -0x4(%rbp),%edx

0x0000000000000677 : mov -0x1c(%rbp),%eax

0x000000000000067a : add %eax,%edx

0x000000000000067c : mov -0x20(%rbp),%eax

0x000000000000067f : add %edx,%eax

0x0000000000000681 : mov %eax,-0x4(%rbp)

0x0000000000000684 : mov -0x4(%rbp),%edx

0x0000000000000687 : mov -0x24(%rbp),%eax

0x000000000000068a : add %eax,%edx

0x000000000000068c : mov -0x28(%rbp),%eax

0x000000000000068f : add %edx,%eax

0x0000000000000691 : mov %eax,-0x4(%rbp)

0x0000000000000694 : mov -0x4(%rbp),%eax

0x0000000000000697 : pop %rbp

0x0000000000000698 : retq

End of assembler dump.

func_A的栈结构和前几个函数类型,但是编译器识别到该函数时叶子函数(leaf function),其栈指针rsp不再被使用,就会少一修改rsp的指令和一个恢复rsp的指令。

保存上一个栈帧信息

d7f7ab36a287f061f1415f8f2b759a0f.png

0x0000000000000699 : push %rbp

0x000000000000069a : mov %rsp,%rbp

不会有开栈的操作,即不会修改rsp指针。

接收参数

0x000000000000064e : mov %edi,-0x14(%rbp)

0x0000000000000651 : mov %esi,-0x18(%rbp)

0x0000000000000654 : mov %edx,-0x1c(%rbp)

0x0000000000000657 : mov %ecx,-0x20(%rbp)

0x000000000000065a : mov %r8d,-0x24(%rbp)

0x000000000000065e : mov %r9d,-0x28(%rbp)

8dcaf2538e686073b6454f6a813c89c7.png

栈上运算

0x0000000000000662 : movl $0x0,-0x4(%rbp)

0x0000000000000669 : mov -0x14(%rbp),%edx

0x000000000000066c : mov -0x18(%rbp),%eax

0x000000000000066f : add %edx,%eax

0x0000000000000671 : mov %eax,-0x4(%rbp)

0x0000000000000674 : mov -0x4(%rbp),%edx

0x0000000000000677 : mov -0x1c(%rbp),%eax

0x000000000000067a : add %eax,%edx

0x000000000000067c : mov -0x20(%rbp),%eax

0x000000000000067f : add %edx,%eax

0x0000000000000681 : mov %eax,-0x4(%rbp)

0x0000000000000684 : mov -0x4(%rbp),%edx

0x0000000000000687 : mov -0x24(%rbp),%eax

0x000000000000068a : add %eax,%edx

0x000000000000068c : mov -0x28(%rbp),%eax

0x000000000000068f : add %edx,%eax

0x0000000000000691 : mov %eax,-0x4(%rbp)

汇编语言描述的和C语言一致。

c084a2577fdab1d5f7610eadc8947a94.png

2.3.4 调用关系以及红区

c6caadaaa296aa193656fe8ae7b693b5.png



推荐阅读
  • 原文地址http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/最开始时 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • 嵌入式处理器的架构与内核发展历程
    本文主要介绍了嵌入式处理器的架构与内核发展历程,包括不同架构的指令集的变化,以及内核的流水线和结构。通过对ARM架构的分析,可以更好地理解嵌入式处理器的架构与内核的关系。 ... [详细]
  • Mono为何能跨平台
    概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa ... [详细]
  • 三、查看Linux版本查看系统版本信息的命令:lsb_release-a[root@localhost~]#lsb_release-aLSBVersion::co ... [详细]
  • 【技术分享】一个 ELF 蠕虫分析
    【技术分享】一个 ELF 蠕虫分析 ... [详细]
  • 本文介绍了基于c语言的mcs51单片机定时器计数器的应用教程,包括定时器的设置和计数方法,以及中断函数的使用。同时介绍了定时器应用的举例,包括定时器中断函数的编写和频率值的计算方法。主函数中设置了T0模式和T1计数的初值,并开启了T0和T1的中断,最后启动了CPU中断。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • C#设计模式之八装饰模式(Decorator Pattern)【结构型】
    一、引言今天我们要讲【结构型】设计模式的第三个模式,该模式是【装饰模式】,英文名称:DecoratorPattern。我第一次看到这个名称想到的是另外一个词语“装修”,我就说说我对“装修”的理 ... [详细]
  • ihaveusedthedelphidatabindingwizardwithmyxmlfile,andeverythingcompilesandrunsfine. ... [详细]
  • uboot与linux驱动1.uboot本身是裸机程序(1)在裸机中本来是没有驱动概念的(狭义的驱动概念是指在操作系统中用来具体操控硬 ... [详细]
  • docker安装到基本使用
    记录docker概念,安装及入门日常使用Docker安装查看官方文档,在"Debian上安装Docker",其他平台在"这里查 ... [详细]
author-avatar
余温
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有