[关闭]
@yiltoncent 2015-11-18T17:08:56.000000Z 字数 5436 阅读 904

C函数调用而忘记参数列表的后果 [C专家编程]

C语言基础


故事从C专家编程的第三章分析C语言的声明开始,书中给出了cdecl代码,正好昨天把整章看完,所以今天就想着把程序抄一边,然后搞懂一下怎么回事。代码可以参考此链接

这儿只将涉及到我的问题的部分代码摘录下来,原始代码:

  1. void deal_with_declarator()
  2. {
  3. /*处理标识符之后可能存在的数组/函数*/
  4. switch(this.type)
  5. {
  6. case '[':
  7. deal_with_arrays();
  8. break;
  9. case '(':
  10. deal_with_function_args();
  11. break;
  12. }
  13. deal_with_pointers();
  14. /*处理在读入到标识符之前压入到堆栈的符号*/
  15. while(top>=0)
  16. {
  17. if(stack[top].type=='('){
  18. pop;
  19. gettoken();/*读取')'之后的符号*/
  20. deal_with_declarator();
  21. }
  22. else
  23. {
  24. printf("%s ",pop.string);
  25. }
  26. }
  27. }
  28. int main()
  29. {
  30. /*将标记呀入堆栈中,直到遇见标识符*/
  31. read_to_first_identifier();
  32. deal_with_declarator();
  33. printf("\n");
  34. return 0;
  35. }

而我在抄的时候将deal_with_declarator函数内部的一个函数调用忘记了()

  1. if(stack[top].type=='('){
  2. pop;
  3. gettoken; //问题正出在这儿,虽然没有括号,但依然能编译通过
  4. deal_with_declarator();
  5. }

我们先看一下,原始代码编译后是什么样的,记住加上-g选项:

  1. (gdb) disassemble /m deal_with_declarator
  2. Dump of assembler code for function deal_with_declarator:
  3. 109 void deal_with_declarator(void) {
  4. 0x08048a0e <+0>: push %ebp
  5. 0x08048a0f <+1>: mov %esp,%ebp
  6. 0x08048a11 <+3>: sub $0x8,%esp
  7. 110 //printf("\nthis.type %c\n",this.type);
  8. 111 switch(this.type)
  9. 0x08048a14 <+6>: movzbl 0x804a060,%eax
  10. 0x08048a1b <+13>: movsbl %al,%eax
  11. 0x08048a1e <+16>: cmp $0x28,%eax
  12. 0x08048a21 <+19>: je 0x8048a2f <deal_with_declarator+33>
  13. 0x08048a23 <+21>: cmp $0x5b,%eax
  14. 0x08048a26 <+24>: jne 0x8048a34 <deal_with_declarator+38>
  15. 112 {
  16. 113 case '[' :
  17. 114 deal_with_arrays(); break;
  18. 0x08048a28 <+26>: call 0x80488fb <deal_with_arrays>
  19. 0x08048a2d <+31>: jmp 0x8048a34 <deal_with_declarator+38>
  20. 115 case '(' :
  21. 116 deal_with_function_args(); break;
  22. 0x08048a2f <+33>: call 0x804898c <deal_with_function_args>
  23. 117 }
  24. 118 deal_with_pointers();
  25. 0x08048a34 <+38>: call 0x80489bc <deal_with_pointers>
  26. 119
  27. 120 while( top >= 0 ){
  28. 0x08048a39 <+43>: jmp 0x8048a9b <deal_with_declarator+141>
  29. 0x08048a9b <+141>: mov 0x804a038,%eax
  30. 0x08048aa0 <+146>: test %eax,%eax
  31. 0x08048aa2 <+148>: jns 0x8048a3b <deal_with_declarator+45>
  32. 121 if( stack[top].type == '(') {
  33. 0x08048a3b <+45>: mov 0x804a038,%edx
  34. 0x08048a41 <+51>: mov %edx,%eax
  35. 0x08048a43 <+53>: shl $0x6,%eax
  36. 0x08048a46 <+56>: add %edx,%eax
  37. 0x08048a48 <+58>: add $0x804a0c0,%eax
  38. 0x08048a4d <+63>: movzbl (%eax),%eax
  39. 0x08048a50 <+66>: cmp $0x28,%al
  40. 0x08048a52 <+68>: jne 0x8048a6d <deal_with_declarator+95>
  41. 122 pop;
  42. ---Type <return> to continue, or q <return> to quit---
  43. 0x08048a54 <+70>: mov 0x804a038,%eax
  44. 0x08048a59 <+75>: sub $0x1,%eax
  45. 0x08048a5c <+78>: mov %eax,0x804a038
  46. 123 gettoken();
  47. 0x08048a61 <+83>: call 0x8048774 <gettoken> #函数调用
  48. 124 deal_with_declarator();
  49. 0x08048a66 <+88>: call 0x8048a0e <deal_with_declarator>
  50. 0x08048a6b <+93>: jmp 0x8048a9b <deal_with_declarator+141>
  51. 125 }else {
  52. 126 printf("%s ", pop.string);
  53. 0x08048a6d <+95>: mov 0x804a038,%edx
  54. 0x08048a73 <+101>: lea -0x1(%edx),%eax
  55. 0x08048a76 <+104>: mov %eax,0x804a038
  56. 0x08048a7b <+109>: mov %edx,%eax
  57. 0x08048a7d <+111>: shl $0x6,%eax
  58. 0x08048a80 <+114>: add %edx,%eax
  59. 0x08048a82 <+116>: add $0x804a0c0,%eax
  60. 0x08048a87 <+121>: add $0x1,%eax
  61. 0x08048a8a <+124>: sub $0x8,%esp
  62. 0x08048a8d <+127>: push %eax
  63. 0x08048a8e <+128>: push $0x8048be4
  64. 0x08048a93 <+133>: call 0x8048410 <printf@plt>
  65. 0x08048a98 <+138>: add $0x10,%esp
  66. 127 }
  67. 128 }
  68. 129 }
  69. 0x08048aa4 <+150>: nop
  70. 0x08048aa5 <+151>: leave
  71. 0x08048aa6 <+152>: ret
  72. End of assembler dump.

其中第123行是调用函数gettoken

我们再看看错误代码的汇编结果:

  1. (gdb) disassemble /m deal_with_declarator
  2. Dump of assembler code for function deal_with_declarator:
  3. 109 void deal_with_declarator(void) {
  4. 0x08048a0e <+0>: push %ebp
  5. 0x08048a0f <+1>: mov %esp,%ebp
  6. 0x08048a11 <+3>: sub $0x8,%esp
  7. 110 //printf("\nthis.type %c\n",this.type);
  8. 111 switch(this.type)
  9. 0x08048a14 <+6>: movzbl 0x804a060,%eax
  10. 0x08048a1b <+13>: movsbl %al,%eax
  11. 0x08048a1e <+16>: cmp $0x28,%eax
  12. 0x08048a21 <+19>: je 0x8048a2f <deal_with_declarator+33>
  13. 0x08048a23 <+21>: cmp $0x5b,%eax
  14. 0x08048a26 <+24>: jne 0x8048a34 <deal_with_declarator+38>
  15. 112 {
  16. 113 case '[' :
  17. 114 deal_with_arrays(); break;
  18. 0x08048a28 <+26>: call 0x80488fb <deal_with_arrays>
  19. 0x08048a2d <+31>: jmp 0x8048a34 <deal_with_declarator+38>
  20. 115 case '(' :
  21. 116 deal_with_function_args(); break;
  22. 0x08048a2f <+33>: call 0x804898c <deal_with_function_args>
  23. 117 }
  24. 118 deal_with_pointers();
  25. 0x08048a34 <+38>: call 0x80489bc <deal_with_pointers>
  26. 119
  27. 120 while( top >= 0 ){
  28. 0x08048a39 <+43>: jmp 0x8048a96 <deal_with_declarator+136>
  29. 0x08048a96 <+136>: mov 0x804a038,%eax
  30. 0x08048a9b <+141>: test %eax,%eax
  31. 0x08048a9d <+143>: jns 0x8048a3b <deal_with_declarator+45>
  32. 121 if( stack[top].type == '(') {
  33. 0x08048a3b <+45>: mov 0x804a038,%edx
  34. 0x08048a41 <+51>: mov %edx,%eax
  35. 0x08048a43 <+53>: shl $0x6,%eax
  36. 0x08048a46 <+56>: add %edx,%eax
  37. 0x08048a48 <+58>: add $0x804a0c0,%eax
  38. 0x08048a4d <+63>: movzbl (%eax),%eax
  39. 0x08048a50 <+66>: cmp $0x28,%al
  40. 0x08048a52 <+68>: jne 0x8048a68 <deal_with_declarator+90>
  41. 122 pop;
  42. ---Type <return> to continue, or q <return> to quit---
  43. 0x08048a54 <+70>: mov 0x804a038,%eax
  44. 0x08048a59 <+75>: sub $0x1,%eax
  45. 0x08048a5c <+78>: mov %eax,0x804a038
  46. 123 gettoken; #看到没,啥也没做
  47. 124 deal_with_declarator();
  48. 0x08048a61 <+83>: call 0x8048a0e <deal_with_declarator>
  49. 0x08048a66 <+88>: jmp 0x8048a96 <deal_with_declarator+136>
  50. 125 }else {
  51. 126 printf("%s ", pop.string);
  52. 0x08048a68 <+90>: mov 0x804a038,%edx
  53. 0x08048a6e <+96>: lea -0x1(%edx),%eax
  54. 0x08048a71 <+99>: mov %eax,0x804a038
  55. 0x08048a76 <+104>: mov %edx,%eax
  56. 0x08048a78 <+106>: shl $0x6,%eax
  57. 0x08048a7b <+109>: add %edx,%eax
  58. 0x08048a7d <+111>: add $0x804a0c0,%eax
  59. 0x08048a82 <+116>: add $0x1,%eax
  60. 0x08048a85 <+119>: sub $0x8,%esp
  61. 0x08048a88 <+122>: push %eax
  62. 0x08048a89 <+123>: push $0x8048be4
  63. 0x08048a8e <+128>: call 0x8048410 <printf@plt>
  64. 0x08048a93 <+133>: add $0x10,%esp
  65. 127 }
  66. 128 }
  67. 129 }
  68. 0x08048a9f <+145>: nop
  69. 0x08048aa0 <+146>: leave
  70. 0x08048aa1 <+147>: ret
  71. End of assembler dump.

看到没有,这个地方啥也没发生。

但是,假如我们这个地方不是一个存在的函数名而是一个随便的字符串,会怎么样?

  1. gcc -o cdecl ch3_cdecl.c -g
  2. ch3_cdecl.c: In function deal_with_declarator’:
  3. ch3_cdecl.c:123:4: error: dasgettoken undeclared (first use in this function)
  4. dasgettoken;
  5. ^
  6. ch3_cdecl.c:123:4: note: each undeclared identifier is reported only once for each function it appears in

此时,编译器会识别出这个名字,它是没定义的identifier

总结一下,如果想调用一个函数,而忘记了它的参数列表,就只有一个函数名,编译器是可以通过的。这是危险的。应该对编译器进行配置以提醒这种情况。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注