故事從C專家編程的第三章分析C語言的聲明開始,書中給出了cdecl代碼,正好昨天把整章看完,所以今天就想著把程序抄一邊,然后搞懂一下怎么回事。代碼可以參考此鏈接。
這兒只將涉及到我的問題的部分代碼摘錄下來,原始代碼:
void deal_with_declarator()
{
/*處理標(biāo)識符之后可能存在的數(shù)組/函數(shù)*/
switch(this.type)
{
case '[':
deal_with_arrays();
break;
case '(':
deal_with_function_args();
break;
}
deal_with_pointers();
/*處理在讀入到標(biāo)識符之前壓入到堆棧的符號*/
while(top>=0)
{
if(stack[top].type=='('){
pop;
gettoken();/*讀取')'之后的符號*/
deal_with_declarator();
}
else
{
printf("%s ",pop.string);
}
}
}
int main()
{
/*將標(biāo)記壓入堆棧中,直到遇見標(biāo)識符*/
read_to_first_identifier();
deal_with_declarator();
printf("\n");
return 0;
}
而我在抄的時候?qū)?code>deal_with_declarator函數(shù)內(nèi)部的一個函數(shù)調(diào)用忘記了():
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 <deal_with_declarator+33>
0x08048a23 <+21>: cmp $0x5b,%eax
0x08048a26 <+24>: jne 0x8048a34 <deal_with_declarator+38>
112 {
113 case '[' :
114 deal_with_arrays(); break;
0x08048a28 <+26>: call 0x80488fb <deal_with_arrays>
0x08048a2d <+31>: jmp 0x8048a34 <deal_with_declarator+38>
115 case '(' :
116 deal_with_function_args(); break;
0x08048a2f <+33>: call 0x804898c <deal_with_function_args>
117 }
118 deal_with_pointers();
0x08048a34 <+38>: call 0x80489bc <deal_with_pointers>
119
120 while( top >= 0 ){
0x08048a39 <+43>: jmp 0x8048a9b <deal_with_declarator+141>
0x08048a9b <+141>: mov 0x804a038,%eax
0x08048aa0 <+146>: test %eax,%eax
0x08048aa2 <+148>: jns 0x8048a3b <deal_with_declarator+45>
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 <deal_with_declarator+95>
122 pop;
---Type <return> to continue, or q <return> to quit---
0x08048a54 <+70>: mov 0x804a038,%eax
0x08048a59 <+75>: sub $0x1,%eax
0x08048a5c <+78>: mov %eax,0x804a038
123 gettoken();
0x08048a61 <+83>: call 0x8048774 <gettoken> #函數(shù)調(diào)用
124 deal_with_declarator();
0x08048a66 <+88>: call 0x8048a0e <deal_with_declarator>
0x08048a6b <+93>: jmp 0x8048a9b <deal_with_declarator+141>
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 <printf@plt>
0x08048a98 <+138>: add $0x10,%esp
127 }
128 }
129 }
0x08048aa4 <+150>: nop
0x08048aa5 <+151>: leave
0x08048aa6 <+152>: ret
End of assembler dump.
其中第123行是調(diào)用函數(shù)gettoken。
我們再看看錯誤代碼的匯編結(jié)果:
(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 <deal_with_declarator+33>
0x08048a23 <+21>: cmp $0x5b,%eax
0x08048a26 <+24>: jne 0x8048a34 <deal_with_declarator+38>
112 {
113 case '[' :
114 deal_with_arrays(); break;
0x08048a28 <+26>: call 0x80488fb <deal_with_arrays>
0x08048a2d <+31>: jmp 0x8048a34 <deal_with_declarator+38>
115 case '(' :
116 deal_with_function_args(); break;
0x08048a2f <+33>: call 0x804898c <deal_with_function_args>
117 }
118 deal_with_pointers();
0x08048a34 <+38>: call 0x80489bc <deal_with_pointers>
119
120 while( top >= 0 ){
0x08048a39 <+43>: jmp 0x8048a96 <deal_with_declarator+136>
0x08048a96 <+136>: mov 0x804a038,%eax
0x08048a9b <+141>: test %eax,%eax
0x08048a9d <+143>: jns 0x8048a3b <deal_with_declarator+45>
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 <deal_with_declarator+90>
122 pop;
---Type <return> to continue, or q <return> to quit---
0x08048a54 <+70>: mov 0x804a038,%eax
0x08048a59 <+75>: sub $0x1,%eax
0x08048a5c <+78>: mov %eax,0x804a038
123 gettoken; #看到?jīng)],啥也沒做
124 deal_with_declarator();
0x08048a61 <+83>: call 0x8048a0e <deal_with_declarator>
0x08048a66 <+88>: jmp 0x8048a96 <deal_with_declarator+136>
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 <printf@plt>
0x08048a93 <+133>: add $0x10,%esp
127 }
128 }
129 }
0x08048a9f <+145>: nop
0x08048aa0 <+146>: leave
0x08048aa1 <+147>: ret
End of assembler dump.
看到?jīng)]有,這個地方啥也沒發(fā)生。
但是,假如我們這個地方不是一個存在的函數(shù)名而是一個隨便的字符串,會怎么樣?
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。
總結(jié)一下,如果想調(diào)用一個函數(shù),而忘記了它的參數(shù)列表,就只有一個函數(shù)名,編譯器是可以通過的。這是危險的。應(yīng)該對編譯器進(jìn)行配置以提醒這種情況。