C语言函数参数传递之痛

来源:互联网 发布:ubuntu 强制删除用户 编辑:程序博客网 时间:2024/06/05 00:26

文章原地址:点击打开链接

首先先讲一下表达式中“类型提升”,来自《C专家编程》

 

    整型提升就是char,short(无论unsigned,signed),位段类型,枚举类型都将提升为int类型。前提是int类型能完整容纳原先的数据,否则提升为unsigned int类型。

    char c1,c2;

    c1=c1+c2;

 

    在运算时,c1和c2都先被提升为int类型进行运算,运算结果再裁减为char类型。

   

    类似的

    float f1,f2;

 

    f1=f1*f2;

    在运算时,f1和f2都先提升为double类型,然后运算结果再裁减为float类型。

 

 

函数参数时的“类型提升”。

 

      其实就是函数传递时的一种策略:方便编译器的设计。

 

例子:

 

file1.c中的函数:

     

      void newpf(char a,char b)
      {
            printf("%f,%d/n",a,b);
      }

 

file2.c中调用:

     

      extern void newpf(char ,char);

      

      int main()
      {   
           char a=42;
           char b=41;
           newpf(a,b);
           return 0;
       }

 

对这两个文件分别反汇编:

gcc -S file1.c

gcc -S file2.c


 

file2.s如下(只摘取调用函数的语句):

 

 movb $42, -1(%ebp)  ==》char a=42;
 movb $41, -2(%ebp)  ==》char b=41;
 subl $8, %esp


 把b压入栈,压入的是4byte              (虽然形参是char类型)==》可以认为是“类型提升为int”

 movsbl -2(%ebp),%eax
 pushl %eax

 

把a压入栈,压入的是4byte               (虽然形参是char类型)==》可以认为是“类型提升为int”
 movsbl -1(%ebp),%eax
 pushl %eax

 

调用函数
 call newpf

 

file1.s如下(只摘取获取实参的语句):

 

 movl 8(%ebp), %eax    ==》提取实参a  (4byte)
 movl 12(%ebp), %edx  ==》提取实参b  (4byte)


 movb %al, -1(%ebp)    ==》重新把实参a进行裁减为char类型
 movb %dl, -2(%ebp)    ==》重新把实参b进行裁减为char类型


 subl $4, %esp

在调用printf函数时,参数类型也会被提升为4byte
 movsbl -2(%ebp),%eax
 pushl %eax
 movsbl -1(%ebp),%eax
 pushl %eax


 pushl $.LC0


 call printf

 

 

所以在32bit系统上,函数参数不管是char,short,int都是以4byte压入栈中。函数在根据定义,把实参裁减为定义的类型。

 

所以file2.c修改成如下,也可以运行。

 

      注掉原型防止编译不通过

      //extern void newpf(char ,char);

      

      int main()
      {   

           a,b都修改成int类型


           int a=42;
           int b=41;


           newpf(a,b);
           return 0;
       }

 

gcc -o file file1.c file2.c

./file

 

结果如下:42,41

 

如果把file2.c修改成如下:

 

 

      注掉原型防止编译不通过

      //extern void newpf(char ,char);

      

      int main()
      {   

           a,b都修改成int类型


           int a=65535;
           int b=65535;


           newpf(a,b);
           return 0;
       }

 

 

运行结果如下:-1,-1

 

原因:

在newpf函数中,实参会被重新裁减为char类型。

所以65535被转换成signed char 就变成了-1

 

 

 

很显然,如果遵守编程规范不了解“类型提升”似乎没什么问题?

但是当下面的情况出现时,如果不了解“类型提升”,就很难找到BUG了。

 

file1.c

 

void newpf(float a,char b)
{

     printf("%f,%c/n",a,b);
}

 

 

file2.c

 

int main()
{   
    float a=40.0;
    char  b=41;


    newpf(a,b);
    return 0;
}

 

 

看起来没什么问题。但是编译运行结果如下:0.000000,

 

通过反汇编我们找下答案:

gcc -S file1.c  file2.c

 

 

file2.s

 

 


 movl $0x42200000, -4(%ebp)==》a=40.0
 movb $41, -5(%ebp)              ==》b=41
 subl $4, %esp

把b转换成4byte压入栈中 

 movsbl -5(%ebp),%eax
 pushl %eax
 

flds 指令意为把单精度value的值放入FPU的st7寄存器(64bit)中

 flds -4(%ebp)  ==》float a==》double a
 leal -8(%esp), %esp

 

接着fstpl 指令把FPU的寄存器中的值以双精度的形式出栈,并存储在(%esp)处
 fstpl (%esp)

 

 调用函数

 call newpf

 

所以栈中此时景象如下:

 

地址高

                b           4byte

                a           8byte

                addr      4byte的函数返回地址

                ebp       4byte
地址低

我们在看看file1.c中的newpf函数如何取实参的:

 

file1.s


 movl 12(%ebp), %eax ==》本意是想取实参b,但是实际上取的是实参a的高4byte
 movb %al, -1(%ebp)
 movsbl -1(%ebp),%eax
 pushl %eax


 flds 8(%ebp)               ==》该指令只能取实参a的低4byte,flds指令只能取单精度值--》即float类型
 leal -8(%esp), %esp
 fstpl (%esp)
 pushl $.LC0

所以调用printf的时候,悲剧发生了。
 call printf

 

 

为何会这样?

C标准中的参数“类型提升”。

在没有“原型”声明时,

 

    float a=40.0;
    char  b=41;

调用newpf时,实参a从float类型自动提升为double类型
    newpf(a,b);

 

所以a变成了8Byte。所以堆栈为:

 

地址高

                b           4byte

                a           8byte

                addr      4byte的函数返回地址

                ebp       4byte
地址低

 

而在函数newpf中,取实参时,根据newpf的“定义”,a为float类型,

 

函数newpf认为堆栈如下:

 

地址高

                b           4byte

                a           4byte

                addr      4byte的函数返回地址

                ebp       4byte
地址低

 

所以造成了不一致的情况。

 

如何解决?

 

在file2.c中加入 newpf函数的“原型”,如下:

 

加入原型

void newpf(float a,char b);

 

int main()
{   
    float a=40.0;
    char  b=41;


    newpf(a,b);
    return 0;
}

 

我们此时在看一下file2.c的反汇编:

 

file2.s

 

 movl $0x42200000, -4(%ebp)==》float a=40.0
 movb $41, -5(%ebp)              ==》char b=41
 subl $8, %esp

 

压入b参数
 movsbl -5(%ebp),%eax
 pushl %eax


 pushl -4(%ebp) ==》直接压入4byte的a,不在对float a进行扩展。
 call newpf

 

 

再次运行下:

gcc -o file file2.c file1.c

./file

运行结果如下:40.000000,)


0 0
原创粉丝点击