故事从C专家编程的第三章分析C语言的声明
开始,书中给出了cdecl
代码,正好昨天把整章看完,所以今天就想着把程序抄一边,然后搞懂一下怎么回事。代码可以参考此链接。
这儿只将涉及到我的问题的部分代码摘录下来,原始代码:
void deal_with_declarator()
{
/*处理标识符之后可能存在的数组/函数*/
switch(this.type)
{
case '[':
deal_with_arrays();
break;
case '(':
deal_with_function_args();
break;
}
deal_with_pointers();
/*处理在读入到标识符之前压入到堆栈的符号*/
while(top>=0)
{
if(stack[top].type=='('){
pop;
gettoken();/*读取')'之后的符号*/
deal_with_declarator();
}
else
{
printf("%s ",pop.string);
}
}
}
int main()
{
/*将标记压入堆栈中,直到遇见标识符*/
read_to_first_identifier();
deal_with_declarator();
printf("\n");
return 0;
}
而我在抄的时候将deal_with_declarator
函数内部的一个函数调用忘记了()
:
if(stack[top].type=='('){
pop;
gettoken; //问题正出在这儿,虽然没有括号,但依然能编译通过
deal_with_declarator();
}
我们先看一下,原始代码编译后是什么样的,记住加上-g
选项:
(gdb) disassemble /m deal_with_declarator
Dump of assembler code for function deal_with_declarator:
109 void deal_with_declarator(void) {
0x08048a0e <+0>: push %ebp
0x08048a0f <+1>: mov %esp,%ebp
0x08048a11 <+3>: sub $0x8,%esp
110 //printf("\nthis.type %c\n",this.type);
111 switch(this.type)
0x08048a14 <+6>: movzbl 0x804a060,%eax
0x08048a1b <+13>: movsbl %al,%eax
0x08048a1e <+16>: cmp $0x28,%eax
0x08048a21 <+19>: je 0x8048a2f
0x08048a23 <+21>: cmp $0x5b,%eax
0x08048a26 <+24>: jne 0x8048a34
112 {
113 case '[' :
114 deal_with_arrays(); break;
0x08048a28 <+26>: call 0x80488fb
0x08048a2d <+31>: jmp 0x8048a34
115 case '(' :
116 deal_with_function_args(); break;
0x08048a2f <+33>: call 0x804898c
117 }
118 deal_with_pointers();
0x08048a34 <+38>: call 0x80489bc
119
120 while( top >= 0 ){
0x08048a39 <+43>: jmp 0x8048a9b
0x08048a9b <+141>: mov 0x804a038,%eax
0x08048aa0 <+146>: test %eax,%eax
0x08048aa2 <+148>: jns 0x8048a3b
121 if( stack[top].type == '(') {
0x08048a3b <+45>: mov 0x804a038,%edx
0x08048a41 <+51>: mov %edx,%eax
0x08048a43 <+53>: shl $0x6,%eax
0x08048a46 <+56>: add %edx,%eax
0x08048a48 <+58>: add $0x804a0c0,%eax
0x08048a4d <+63>: movzbl (%eax),%eax
0x08048a50 <+66>: cmp $0x28,%al
0x08048a52 <+68>: jne 0x8048a6d
122 pop;
---Type to continue, or q to quit---
0x08048a54 <+70>: mov 0x804a038,%eax
0x08048a59 <+75>: sub $0x1,%eax
0x08048a5c <+78>: mov %eax,0x804a038
123 gettoken();
0x08048a61 <+83>: call 0x8048774 #函数调用
124 deal_with_declarator();
0x08048a66 <+88>: call 0x8048a0e
0x08048a6b <+93>: jmp 0x8048a9b
125 }else {
126 printf("%s ", pop.string);
0x08048a6d <+95>: mov 0x804a038,%edx
0x08048a73 <+101>: lea -0x1(%edx),%eax
0x08048a76 <+104>: mov %eax,0x804a038
0x08048a7b <+109>: mov %edx,%eax
0x08048a7d <+111>: shl $0x6,%eax
0x08048a80 <+114>: add %edx,%eax
0x08048a82 <+116>: add $0x804a0c0,%eax
0x08048a87 <+121>: add $0x1,%eax
0x08048a8a <+124>: sub $0x8,%esp
0x08048a8d <+127>: push %eax
0x08048a8e <+128>: push $0x8048be4
0x08048a93 <+133>: call 0x8048410
0x08048a98 <+138>: add $0x10,%esp
127 }
128 }
129 }
0x08048aa4 <+150>: nop
0x08048aa5 <+151>: leave
0x08048aa6 <+152>: ret
End of assembler dump.
其中第123行是调用函数gettoken
。
我们再看看错误代码的汇编结果:
(gdb) disassemble /m deal_with_declarator
Dump of assembler code for function deal_with_declarator:
109 void deal_with_declarator(void) {
0x08048a0e <+0>: push %ebp
0x08048a0f <+1>: mov %esp,%ebp
0x08048a11 <+3>: sub $0x8,%esp
110 //printf("\nthis.type %c\n",this.type);
111 switch(this.type)
0x08048a14 <+6>: movzbl 0x804a060,%eax
0x08048a1b <+13>: movsbl %al,%eax
0x08048a1e <+16>: cmp $0x28,%eax
0x08048a21 <+19>: je 0x8048a2f
0x08048a23 <+21>: cmp $0x5b,%eax
0x08048a26 <+24>: jne 0x8048a34
112 {
113 case '[' :
114 deal_with_arrays(); break;
0x08048a28 <+26>: call 0x80488fb
0x08048a2d <+31>: jmp 0x8048a34
115 case '(' :
116 deal_with_function_args(); break;
0x08048a2f <+33>: call 0x804898c
117 }
118 deal_with_pointers();
0x08048a34 <+38>: call 0x80489bc
119
120 while( top >= 0 ){
0x08048a39 <+43>: jmp 0x8048a96
0x08048a96 <+136>: mov 0x804a038,%eax
0x08048a9b <+141>: test %eax,%eax
0x08048a9d <+143>: jns 0x8048a3b
121 if( stack[top].type == '(') {
0x08048a3b <+45>: mov 0x804a038,%edx
0x08048a41 <+51>: mov %edx,%eax
0x08048a43 <+53>: shl $0x6,%eax
0x08048a46 <+56>: add %edx,%eax
0x08048a48 <+58>: add $0x804a0c0,%eax
0x08048a4d <+63>: movzbl (%eax),%eax
0x08048a50 <+66>: cmp $0x28,%al
0x08048a52 <+68>: jne 0x8048a68
122 pop;
---Type to continue, or q to quit---
0x08048a54 <+70>: mov 0x804a038,%eax
0x08048a59 <+75>: sub $0x1,%eax
0x08048a5c <+78>: mov %eax,0x804a038
123 gettoken; #看到没,啥也没做
124 deal_with_declarator();
0x08048a61 <+83>: call 0x8048a0e
0x08048a66 <+88>: jmp 0x8048a96
125 }else {
126 printf("%s ", pop.string);
0x08048a68 <+90>: mov 0x804a038,%edx
0x08048a6e <+96>: lea -0x1(%edx),%eax
0x08048a71 <+99>: mov %eax,0x804a038
0x08048a76 <+104>: mov %edx,%eax
0x08048a78 <+106>: shl $0x6,%eax
0x08048a7b <+109>: add %edx,%eax
0x08048a7d <+111>: add $0x804a0c0,%eax
0x08048a82 <+116>: add $0x1,%eax
0x08048a85 <+119>: sub $0x8,%esp
0x08048a88 <+122>: push %eax
0x08048a89 <+123>: push $0x8048be4
0x08048a8e <+128>: call 0x8048410
0x08048a93 <+133>: add $0x10,%esp
127 }
128 }
129 }
0x08048a9f <+145>: nop
0x08048aa0 <+146>: leave
0x08048aa1 <+147>: ret
End of assembler dump.
看到没有,这个地方啥也没发生。
但是,假如我们这个地方不是一个存在的函数名而是一个随便的字符串,会怎么样?
gcc -o cdecl ch3_cdecl.c -g
ch3_cdecl.c: In function ‘deal_with_declarator’:
ch3_cdecl.c:123:4: error: ‘dasgettoken’ undeclared (first use in this function)
dasgettoken;
^
ch3_cdecl.c:123:4: note: each undeclared identifier is reported only once for each function it appears in
此时,编译器会识别出这个名字,它是没定义的identifier
。
总结一下,如果想调用一个函数,而忘记了它的参数列表,就只有一个函数名,编译器是可以通过的。这是危险的。应该对编译器进行配置以提醒这种情况。