博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C语言函数参数传递之痛
阅读量:6280 次
发布时间:2019-06-22

本文共 3220 字,大约阅读时间需要 10 分钟。

文章原地址:

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

转载于:https://www.cnblogs.com/JSD1207ZX/p/9386260.html

你可能感兴趣的文章
印度迎来可再生能源产业大发展
查看>>
光伏制造业“融资难、融资贵”问题亟待破解
查看>>
Java Mail最基本的发送邮件例子
查看>>
《HTML 5与CSS 3 权威指南(第3版·上册)》——2.3 新增的属性和废除的属性
查看>>
《Total Commander:万能文件管理器》——第3.5节.选择文件
查看>>
《日志管理与分析权威指南》一导读
查看>>
去 TMD 互联网思维,性价比而已
查看>>
如何手动删除Oracle 11g数据库
查看>>
懒人促进社会进步 - 5种索引的原理和优化Case (btree,hash,gin,gist,brin)
查看>>
《深入实践Spring Boot》一3.4 视图设计
查看>>
《设计模式解析(第2版•修订版)》目录—导读
查看>>
《Web前端开发精品课 HTML与CSS进阶教程》——2.2 标题语义化
查看>>
Java核心技术卷I基础知识3.5.3 强制类型转换
查看>>
可与Mirai比肩的恶意程序Hajime,竟是为了保护IoT设备?
查看>>
《Spring Data 官方文档》6. Cassandra 存储库
查看>>
聊聊并发(十)生产者消费者模式
查看>>
R语言数据挖掘2.2.4.2 FP-growth算法
查看>>
人工智能概念诞生60年,哪些大牛堪称“一代宗师”?
查看>>
《游戏大师Chris Crawford谈互动叙事》一9.5 真实案例
查看>>
Mybatis与Spring整合连接MySQL
查看>>