• C语言函数参数传递之痛


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

    首先先讲一下表达式中“类型提升”,来自《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,)


  • 相关阅读:
    Navicat Premium 15 永久激活版安装教程
    win10安装redis
    Linux下安装Go环境
    IoT platforms that deserves to be noticed
    TortoiseGit配置
    impot和require区别
    websocket搭建简单的H264实时视频流播放
    应用索引技术优化SQL 语句(Part 3)
    应用索引技术优化SQL 语句(Part 2)
    应用索引技术优化SQL 语句(Part 1)
  • 原文地址:https://www.cnblogs.com/JSD1207ZX/p/9386260.html
Copyright © 2020-2023  润新知