C语言函数调用分析

发布者:龙腾少年最新更新时间:2015-05-06 来源: 51hei关键字:C语言  函数调用 手机看文章 扫描二维码
随时随地手机看文章
我的测试环境:Fedora14
Gcc版本:gcc-4.5.1
内核版本:2.6.38.1
 
C语言是一个强大的语言,特别是对于嵌入式开发过程中有时需要反汇编分析代码中存在的问题,函数是C语言中的难点,关于函数的调用也是很多人不能理解的,很多知道的也是一知半解。对C语言的调用有了一个比较清晰的认识就能够更清晰的分析代码中存在的问题。我也是看了很多的资料,然后自己写了一一段小代码作为分析的测试代码。首先记住在X86体系里很多的寄存器都有特殊的用途,其中ESP表示当前函数堆栈的栈顶指针,而EBP则表示当前函数堆栈的基地址。EBP是栈基址的指针,永远指向栈底(高地址),ESP是栈指针,永远指向栈顶(低地址)。  
我的代码如下:
  1. #include

  2.  
  3. int pluss_a_and_b(int a,int b)
     
  4. {
     
  5.         int c = -2;
     
  6.         return (a + b - c);
     
  7. }
     
  8. int call_plus(int *a,int *b) 
     
  9. {
     
  10.         int c = *a; 
     
  11.         int d = *b; 
     

  12.  
  13.         *a = d;
     
  14.         *b = c;
     
  15.         return pluss_a_and_b(c,d);
     
  16. }
     
  17. int main()
     
  18. {
     
  19.         int c = 10; 
     
  20.         int d = 20;
     
  21.         int g = call_plus(&c,&d);
     
  22.         return 0;
     
  23. }
对上面的代码进行编译和反汇编:
[gong@Gong-Computer deeplearn]$ gcc -g testcall.c -o testcall
[gong@Gong-Computer deeplearn]$ objdump -S -d testcall > testcall_s 
然后对反汇编的代码进行分析:
  1. ...
  2.  
  3. 8048393: c3 ret
  4.  
  5.  
  6.  
  7. 08048394 :
  8.  
  9. #include
  10.  
  11.  
  12.  
  13. int pluss_a_and_b(int a,int b)
  14.  
  15. {
  16.  
  17. 8048394: 55 push %ebp
  18.  
  19. 8048395: 89 e5 mov %esp,%ebp
  20.  
  21. 8048397: 83 ec 10 sub $0x10,%esp
  22.  
  23. int c = -2;
  24.  
  25. 804839a: c7 45 fc fe ff ff ff movl $0xfffffffe,-0x4(%ebp)
  26.  
  27. return (a + b - c);
  28.  
  29. 80483a1: 8b 45 0c mov 0xc(%ebp),%eax
  30.  
  31. 80483a4: 8b 55 08 mov 0x8(%ebp),%edx
  32.  
  33. 80483a7: 8d 04 02 lea (%edx,%eax,1),%eax
  34.  
  35. 80483aa: 2b 45 fc sub -0x4(%ebp),%eax
  36.  
  37. }
  38.  
  39. 80483ad: c9 leave
  40.  
  41. 80483ae: c3 ret
  42.  
  43.  
  44.  
  45. 080483af :
  46.  
  47.  
  48.  
  49. int call_plus(int *a,int *b)
  50.  
  51. {
  52.  
  53. 80483af: 55 push %ebp
  54.  
  55. 80483b0: 89 e5 mov %esp,%ebp
  56.  
  57. 80483b2: 83 ec 18 sub $0x18,%esp
  58.  
  59. int c = *a;
  60.  
  61. 80483b5: 8b 45 08 mov 0x8(%ebp),%eax
  62.  
  63. 80483b8: 8b 00 mov (%eax),%eax
  64.  
  65. 80483ba: 89 45 fc mov %eax,-0x4(%ebp)
  66.  
  67. int d = *b;
  68.  
  69. 80483bd: 8b 45 0c mov 0xc(%ebp),%eax
  70.  
  71. 80483c0: 8b 00 mov (%eax),%eax
  72.  
  73. 80483c2: 89 45 f8 mov %eax,-0x8(%ebp)
  74.  
  75.  
  76.  
  77. *a = d;
  78.  
  79. 80483c5: 8b 45 08 mov 0x8(%ebp),%eax
  80.  
  81. 80483c8: 8b 55 f8 mov -0x8(%ebp),%edx
  82.  
  83. 80483cb: 89 10 mov %edx,(%eax)
  84.  
  85. *b = c;
  86.  
  87. 80483cd: 8b 45 0c mov 0xc(%ebp),%eax
  88.  
  89. 80483d0: 8b 55 fc mov -0x4(%ebp),%edx
  90.  
  91. 80483d3: 89 10 mov %edx,(%eax)
  92.  
  93.  
  94.  
  95. return pluss_a_and_b(c,d);
  96.  
  97. 80483d5: 8b 45 f8 mov -0x8(%ebp),%eax
  98.  
  99. 80483d8: 89 44 24 04 mov %eax,0x4(%esp)
  100.  
  101. 80483dc: 8b 45 fc mov -0x4(%ebp),%eax
  102.  
  103. 80483df: 89 04 24 mov %eax,(%esp)
  104.  
  105. 80483e2: e8 ad ff ff ff call 8048394
  106.  
  107. }
  108.  
  109. 80483e7: c9 leave
  110.  
  111. 80483e8: c3 ret
  112.  
  113.  
  114.  
  115. 080483e9
    :
  116.  
  117.  
  118.  
  119. int main()
  120.  
  121. {
  122.  
  123. 80483e9: 55 push %ebp
  124.  
  125. 80483ea: 89 e5 mov %esp,%ebp
  126.  
  127. 80483ec: 83 ec 18 sub $0x18,%esp
  128.  
  129. int c = 10;
  130.  
  131. 80483ef: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%ebp)
  132.  
  133.  
  134.  
  135. int d = 20;
  136.  
  137. 80483f6: c7 45 f4 14 00 00 00 movl $0x14,-0xc(%ebp)
  138.  
  139.  
  140.  
  141. int g = call_plus(&c,&d);
  142.  
  143. 80483fd: 8d 45 f4 lea -0xc(%ebp),%eax
  144.  
  145. 8048400: 89 44 24 04 mov %eax,0x4(%esp)
  146.  
  147. 8048404: 8d 45 f8 lea -0x8(%ebp),%eax
  148.  
  149. 8048407: 89 04 24 mov %eax,(%esp)
  150.  
  151. 804840a: e8 a0 ff ff ff call 80483af
  152.  
  153. 804840f: 89 45 fc mov %eax,-0x4(%ebp)
  154.  
  155.  
  156.  
  157. return 0;
  158.  
  159. 8048412: b8 00 00 00 00 mov $0x0,%eax
  160.  
  161. }
  162.  
  163. 8048417: c9 leave
  164.  
  165. 8048418: c3 ret
  166.  
  167. 8048419: 90 nop
  168.  
  169. 804841a: 90 nop
  170.  
  171. ...
首先,C语言的入口都是从main函数开始的,但是从反汇编代码中可以发现并不是只有自己设计的代码,还存在很多关于初始化等操作。这主要是因为C语言的运行需要一些基本的环境和C-RunTime的一些基本函数。因此main 函数只是我们C语言的入口,但并不是一个程序的开始。因此main函数也需要堆栈的控制,也需要压栈出栈等操作。
需要注意的是:[page]
指令call用来调用一个函数或过程,这时下一条指令地址被压入堆栈中,以备返回时能恢复执行下条指令。sp=sp-1。通过下面的汇编代码就可知道函数的返回地址。
80483e2: e8 ad ff ff ff call 8048394
}
80483e7: c9 leave
可以知道指令call后的返回地址就是80483e7。而8048394则说明被调用函数的起始地址,这些数字可能在不同的系统中存在差别。
RET指令用来从一个函数或过程返回,之前CALL保存的下条指令地址会从栈内弹出到EIP寄存器中,程序转到CALL之前下条指令处执行。
 
下面简单的介绍几个代码:
80483e9: 55 push %ebp
80483ea: 89 e5 mov %esp,%ebp
80483ec: 83 ec 18 sub $0x18,%esp
首先push %ebp,是将调用函数的栈帧基地址压入栈中,也就是保存调用函数的栈帧EBP。将其指向的地址压入堆栈中。mov %esp,%ebp则是将ESP和EBP指向同一个地址,作为被调用函数的栈帧基地址。sub $0x18,%esp则是修改ESP的值,与EBP构成当前被调用函数的栈帧空间。
 
 
从图中可以每个函数的栈空间都是相互独立的,但是每一个栈空间的基本结构都是相同的。都是该函数的EBP指针,然后是局部变量空间,然后是往下一个函数的传递参数空间,返回的EBP地址。这样就能实现不同函数的调用,然后传递参数是采用基于EBP指针的相对位置实现的,并没有绝对地址。
 
由此可以知道栈空间的分布是根据调用情况分析的,当调用过多时就会导致溢出错误,因此并不是一味的迭代和递归。
 
关于函数调用的返回都是采用EAX寄存器实现的,但是当返回的是结构体以及联合体时返回就不能采用EAX实现了,基本的实现方法也是基于堆栈的。
  1. #include

  2.  
  3. typedef struct {
     
  4.         double d;
     
  5.         float f;
     
  6.         int i;
     
  7.         char c;
     
  8. }return_value;
     

  9.  

  10.  
  11. return_value my_test_of_return()
     
  12. {
     
  13.         return_value rv; 
     
  14.     
     
  15.         rv.d = 12.56;
     
  16.         rv.f = 3.1;
     
  17.         rv.i = 10; 
     
  18.         rv.c = 'a';
     

  19.  
  20.         return rv; 
     
  21. }
     

  22.  
  23. int main()
     
  24. {
     
  25.         return_value local = my_test_of_return();

  26.  
  27.         return 0;
     
  28. }
编译以及反汇编以后得到如下的结果:
[gong@Gong-Computer deeplearn]$ gcc -g structpass.c -o structpass
[gong@Gong-Computer deeplearn]$ objdump -S -d structpass > structpass_s
 
  1. ...
  2.  
  3. 08048394 :
  4.  
  5. char c;
  6.  
  7. }return_value;
  8.  
  9.  
  10.   return_value my_test_of_return()
  11.  
  12. {
  13.  
  14. 8048394: 55 push %ebp
  15.  
  16. 8048395: 89 e5 mov %esp,%ebp
  17.  
  18. 8048397: 83 ec 20 sub $0x20,%esp
  19.  
  20. 804839a: 8b 45 08 mov 0x8(%ebp),%eax
  21.  
  22. return_value rv;
  23.  
  24.  
  25.  
  26. rv.d = 12.56;
  27.  
  28. 804839d: dd 05 d8 84 04 08 fldl 0x80484d8
  29.  
  30. 80483a3: dd 5d e8 fstpl -0x18(%ebp)
  31.  
  32. rv.f = 3.1;
  33.  
  34. 80483a6: ba 66 66 46 40 mov $0x40466666,%edx
  35.  
  36. 80483ab: 89 55 f0 mov %edx,-0x10(%ebp)
  37.  
  38. rv.i = 10;
  39.  
  40. 80483ae: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%ebp)
  41.  
  42. rv.c = 'a';
  43.  
  44. 80483b5: c6 45 f8 61 movb $0x61,-0x8(%ebp)
  45.  
  46.  
  47.  
  48. return rv;
  49.  
  50. 80483b9: 8b 55 e8 mov -0x18(%ebp),%edx
  51.  
  52. 80483bc: 89 10 mov %edx,(%eax)
  53.  
  54. 80483be: 8b 55 ec mov -0x14(%ebp),%edx
  55.  
  56. 80483c1: 89 50 04 mov %edx,0x4(%eax)
  57.  
  58. 80483c4: 8b 55 f0 mov -0x10(%ebp),%edx
  59.  
  60. 80483c7: 89 50 08 mov %edx,0x8(%eax)
  61.  
  62. 80483ca: 8b 55 f4 mov -0xc(%ebp),%edx
  63.  
  64. 80483cd: 89 50 0c mov %edx,0xc(%eax)
  65.  
  66. 80483d0: 8b 55 f8 mov -0x8(%ebp),%edx
  67.  
  68. 80483d3: 89 50 10 mov %edx,0x10(%eax)
  69.  
  70. }
  71.  
  72. 80483d6: c9 leave
  73.  
  74. 80483d7: c2 04 00 ret $0x4
  75.  
  76.  
  77.  
  78. 080483da
    :
  79.  
  80.  
  81.  
  82. int main()
  83.  
  84. {
  85.  
  86. 80483da: 8d 4c 24 04 lea 0x4(%esp),%ecx
  87.  
  88. 80483de: 83 e4 f8 and $0xfffffff8,%esp
  89.  
  90. 80483e1: ff 71 fc pushl -0x4(%ecx)
  91.  
  92. 80483e4: 55 push %ebp
  93.  
  94. 80483e5: 89 e5 mov %esp,%ebp
  95.  
  96. 80483e7: 51 push %ecx
  97.  
  98. 80483e8: 83 ec 2c sub $0x2c,%esp
  99.  
  100. return_value local = my_test_of_return();
  101.  
  102. 80483eb: 8d 45 e0 lea -0x20(%ebp),%eax
  103.  
  104. 80483ee: 89 04 24 mov %eax,(%esp)
  105.  
  106. 80483f1: e8 9e ff ff ff call 8048394
  107.  
  108. 80483f6: 83 ec 04 sub $0x4,%esp
  109.  
  110.  
  111.  
  112. return 0;
  113.  
  114. 80483f9: b8 00 00 00 00 mov $0x0,%eax
  115.  
  116. }
  117.  
  118. 80483fe: 8b 4d fc mov -0x4(%ebp),%ecx
  119.  
  120. 8048401: c9 leave
  121.  
  122. 8048402: 8d 61 fc lea -0x4(%ecx),%esp
  123.  
  124. ...
从上面的结果可以知道可以知道,返回的过程并不是一次通过EAX返回的,而是通过堆栈一个一个的传递出来,实现结果的返回。因此这也是我们需要注意的地方。
 
同样对于结构体的传递方式也是采用堆栈的方式进行传递,基本的参看下面的分析。参数也是依据堆栈中的位置进行控制的。[page]
代码:
  1. #include

  2.  
  3. typedef struct {
     
  4.         double d;
     
  5.         float f;
     
  6.         int i;
     
  7.         char c;
     
  8. }return_value;
     

  9.  
  10. return_value my_test_pass(return_value pass)
     
  11. {
     
  12.         return_value rv; 
     
  13.         rv.d = pass.d;
     
  14.         rv.f = pass.f;
     
  15.         rv.i = pass.i;
     
  16.         rv.c = pass.c;
     

  17.  
  18.         return rv; 
     
  19. }
     
  20. return_value my_test_of_return()
     
  21. {
     
  22.         return_value rv; 
     
  23.     
     
  24.         rv.d = 12.56;
     
  25.         rv.f = 3.1;
     
  26.         rv.i = 10; 
     
  27.         rv.c = 'a';
     

  28.  
  29.         return rv; 
     
  30. }
     

  31.  
  32. int main()
     
  33. {
     
  34.         return_value local = my_test_of_return();
     
  35.         return_value local1 = my_test_pass(local);

  36.  
  37.         return 0;
     
  38. }
编译和反汇编过程:
[gong@Gong-Computer deeplearn]$ gcc -g structpass.c -o structpass
[gong@Gong-Computer deeplearn]$ objdump -S -d structpass > structpass_s
 
  1. ...
  2. int main()
  3.  
  4. {
  5.  
  6. 804841d: 8d 4c 24 04 lea 0x4(%esp),%ecx
  7.  
  8. 8048421: 83 e4 f8 and $0xfffffff8,%esp
  9.  
  10. 8048424: ff 71 fc pushl -0x4(%ecx)
  11.  
  12. 8048427: 55 push %ebp
  13.  
  14. 8048428: 89 e5 mov %esp,%ebp
  15.  
  16. 804842a: 51 push %ecx
  17.  
  18. 804842b: 83 ec 4c sub $0x4c,%esp
  19.  
  20. return_value local = my_test_of_return();
  21.  
  22. 804842e: 8d 45 e0 lea -0x20(%ebp),%eax
  23.  
  24. 8048431: 89 04 24 mov %eax,(%esp)
  25.  
  26. 8048434: e8 9e ff ff ff call 80483d7
  27.  
  28. 8048439: 83 ec 04 sub $0x4,%esp
  29.  
  30.  
  31.  
  32. return_value local1 = my_test_pass(local);
  33.  
  34. 804843c: 8d 45 c8 lea -0x38(%ebp),%eax
  35.  
  36. 804843f: 8b 55 e0 mov -0x20(%ebp),%edx
  37.  
  38. 8048442: 89 54 24 04 mov %edx,0x4(%esp)
  39.  
  40. 8048446: 8b 55 e4 mov -0x1c(%ebp),%edx
  41.  
  42. 8048449: 89 54 24 08 mov %edx,0x8(%esp)
  43.  
  44. 804844d: 8b 55 e8 mov -0x18(%ebp),%edx
  45.  
  46. 8048450: 89 54 24 0c mov %edx,0xc(%esp)
  47.  
  48. 8048454: 8b 55 ec mov -0x14(%ebp),%edx
  49.  
  50. 8048457: 89 54 24 10 mov %edx,0x10(%esp)
  51.  
  52. 804845b: 8b 55 f0 mov -0x10(%ebp),%edx
  53.  
  54. 804845e: 89 54 24 14 mov %edx,0x14(%esp)
  55.  
  56. 8048462: 89 04 24 mov %eax,(%esp)
  57.  
  58. 8048465: e8 2a ff ff ff call 8048394
  59.  
  60. 804846a: 83 ec 04 sub $0x4,%esp
  61.  
  62.  
  63.  
  64. return 0;
  65.  
  66. 804846d: b8 00 00 00 00 mov $0x0,%eax
  67.  
  68. }
...
由上面的反汇编代码可以知道结构体的传递参数是依据堆栈实现的。这也说明了多参数的传递过程并不是按着固定的模式实现的,这也是我们需要注意的问题。参数的传递需要根据实际情况分析。
 
总结:
函数的调用是有一定的方式的,各个函数都有一定的堆栈空间,而且每一个堆栈空间的分布情况也是类似的,但是大小要根据实际的情况分析。一般一个函数的堆栈空间中包含下面几个部分:1、栈帧(用来表示该堆栈空间的栈底,也就是指开始的地址EBP),局部变量的空间,下一个被调用函数的参数传递,最后是返回地址(实质上也是一个EBP)。就是依据EBP和相对位置就能知道每一个函数的基本分布,而ESP就能知道堆栈空间的大小。
 
被调用参数的获取主要是依据EBP指针的相对位置获得,因为被调用函数的堆栈空间上一个堆栈空间就是调用函数的堆栈空间。根据函数的栈帧指针(EBP)和相对位置(-4,-8等)找到对应的参数,但是相对位置也是不固定的,这需要考虑结构体的对齐等方式,具体的要在实际中计算。
 
返回值一般都是采用EAX返回的,但是对于结构体等则是采用堆栈的方式一个元算一个元素的返回的,但是还是运用了EAX的特性。
 
函数调用的分布打开如下:

从上面的分析我们可以发现汇编代码是非常有用的,建议多参看汇编代码分析具体的问题。
关键字:C语言  函数调用 引用地址:C语言函数调用分析

上一篇:C/C++中宏定义的经典运用
下一篇:UCOS操作系统堆栈浅谈

推荐阅读最新更新时间:2024-03-16 14:00

51单片机c语言定义寄存器r,c51特殊功能寄存器定义及作用
单片机C51语言是由C语言继承而来的。和C语言不同的是,C51语言运行于单片机平台,而C语言则运行于普通的桌面平台。C51语言具有C语言结构清晰的优点,便于学习,同时具有汇编语言的硬件操作能力。对于具有C语言编程基础的读者,能够轻松地掌握单片机C51语言的程序设计。 c51主要特点 单片机C51语言兼备高级语言与低级语言的优点。 语法结构和标准C语言基本一致,语言简洁,便于学习。 单片机C51实验板 运行于单片机平台,支持的微处理器种类繁多,可移植性好。对于兼容的8051系列单片机,只要将一个硬件型号下的程序稍加修改,甚至不加改变,就可移植到另一个不同型号的单片机中运行。 具有高级语言的特点,尽量减少底层硬件寄存器的操
[单片机]
51单片机<font color='red'>c语言</font>定义寄存器r,c51特殊功能寄存器定义及作用
C语言编写的交通灯程序
用四盏双色灯模拟,P1口控制;南北红灯亮30秒,同时东西绿灯亮25秒,绿灯闪烁3次(间隔为1秒),然后黄灯亮2秒;然后东西与南北互换一下;重复; #include reg51.h // #define uint unsigned int #define uchar unsigned char #define ON 0 //0的时候灯亮 #define OFF 1 //1的时候灯亮 //管脚定义 //南北道 黄灯亮3秒 红灯亮 30秒 黄灯3秒 绿灯38秒 //东西道 黄灯亮3秒 绿灯亮 28秒 黄灯3秒 红灯40秒 sbit PortDX_Yellow = P1^0; //东西方向黄灯 sbit PortDX_Red = P1^1
[单片机]
单片机C语言易错知识点经验笔记
今天写这一篇文章并不是因为已经想好了一篇文章才写下来,而是我要将这一篇文章作为一篇笔记来写,一直更新下去。在进行单片机开发时,经常都会出现一些很不起眼的问题,这些问题其实都是很基础的c语言知识点,是一些小细节。但是正是因为很基础,又都是小细节,所以我们往往容易忽视它们。结果有时候我们会花很长的时间纠结一个问题,迟迟找不到问题的所在。当发现原因竟然是这么的简单和不起眼时,我想不单是我,大家都会感到痛不欲生。笔者今天又碰到了这样的问题,实在忍不住了。因为这些问题都是小的知识点,考虑到之前也遇到好多这种问题,于是决定每次遇到问题就记录下来,时刻提醒自己!! 1. !和 ~ 不一样 ! 是逻辑非符号,~ 是位取反符号。 对IO口某个管脚赋
[单片机]
单片机<font color='red'>C语言</font>易错知识点经验笔记
单片机C语言延时需注意的问题
标准的C语言中没有空语句。但在单片机的C语言编程中,经常需要用几个空指令产生短延时的效果。这在汇编语言中很容易实现,写几个nop就行了。 在keil C51中,直接调用库函数: #include // 声明了void _nop_(void); _nop_(); // 产生一条NOP指令 作用:对于延时很短的,要求在us级的,采用 _nop_ 函数,这个函数相当汇编NOP指令,延时几微秒。NOP指令为单周期指令,可由晶振频率算出延时时间,对于12M晶振,延时1uS。对于延时比较长的,要求在大于10us,采用C51中的循环语句来实现。 在选择C51中循环语句时,要注意以下几个问题 第一、定义的C51中循环变量,尽量采用无符号字
[单片机]
C语言延时子程序准确设置
在给单片机写程序的时候往往需要一个微秒或者毫秒的延时子程序,但是C语言不同于汇编,很难根据语句确定准确的延时时间,经过各种查资料得到一种简单实用的方法:通过keil下断点调试确定时间。 就是编写一个延时子程序,然后在延时程序处和while(1)设置断点,运行程序,在调试模式下就可以获得该延时子程序的准确延时时间。 1,编写如下源程序: #include void delay_ms(unsigned int ms) { unsigned int i; unsigned char j; for(i=0;i { for(j=0;j 200;j++); for(j=0;j 102
[单片机]
<font color='red'>C语言</font>延时子程序准确设置
嵌入式C语言开发ADSP21XX系列DSP
  引言   长期以来,在DSP系统开发中,一直把汇编语言作为主要的开发工具;但汇编语言与自然语言差距很大,不易常,而且汇编语言是依赖于处理器的,不利于软件的可重复利用和系统的稳定性,程序不易移植,给开发工作带来了很大的困难。随着嵌入式系统复杂程度的不断提高,用汇编语言编写一个巨大的程度将是困难,甚至是不可能的。为此,AD公司推出了针对ADSP21XX系列DSP的嵌入式C和C++语言集成开发工具,分别是VisualDSP和VisualDSP++系列,这些开发工具提供了C语言和C++语音的开发功能。以下就以笔者在实际开发中的一些经验,结合VisualDSP6.1版本,介绍用C语言开发VisualDSP6.1版本,介绍用C语言开发A
[嵌入式]
C语言指针的简单示例
前边我们提到了,指针的意义往往在小程序里是体现不出来的,对于简单程序来说,有时候用了指针,反而可能比没用指针还麻烦,但是为了让大家巩固一下指针的用法,我还是写了个使用指针的流水灯程序,目的是让大家从简单程序开始了解指针,当程序复杂的时候不至于手足无措。 #include reg52.h sbit ADDR0 = P1^0; sbit ADDR1 = P1^1; sbit ADDR2 = P1^2; sbit ADDR3 = P1^3; sbit ENLED = P1^4; void ShiftLeft(unsigned char *p); void main(){ unsigned int i; unsigned cha
[单片机]
单片机C语言程序设计:定时器控制交通指示灯
/* 名称:定时器控制交通指示灯 说明:东西向绿灯亮 5s 后,黄灯闪烁,闪烁 5 次亮红灯, 红灯亮后,南北向由红灯变成绿灯,5s 后南北向黄灯闪烁, 闪烁 5 次后亮红灯,东西向绿灯亮,如此往复。 */ #include reg51.h #define uchar unsigned char #define uint unsigned int sbit RED_A=P0^0; //东西向指示灯 sbit YELLOW_A=P0^1; sbit GREEN_A=P0^2; sbit RED_B=P0^3; //南北向指示灯 sbit YELLOW_B=P0^4; sbit
[单片机]
单片机<font color='red'>C语言</font>程序设计:定时器控制交通指示灯
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved