程序调试与实践:数据的存储与运算---数据类型的转换
整数之间的数据类型转换
C语言中,数据的赋值不是发生在真值上的复制,而是在机器数上的赋值。
赋值语句 b=a;
情况一:相同宽度的两个整型数据之间的赋值
将一个n位的整数a赋值给另一个n位的整数b,赋值发生在机器数上,所以此情况下a和b的机器数一样。
a和b的机器数相同,真值不一定相同,取决于a和b的数据类型。
情况二:将一个短的数据类型赋值给一个长的数据类型
将一个n位的整数a赋值给另一个m位的整数b(n<m).
把a的n位01序列复制在b的低n位,b的高m-n位由a的数据类型决定。
1.如果a的数据类型是无符号整数,不管b是什么数据类型都需要采用零扩展策略,即将b的高m-n位置为0。
2.如果a的数据类型是带符号整数,不管b是什么数据类型都采用符号扩展策略,即将b的高m-n位置为a的符号位。
情况三:将一个长的数据类型赋值给一个短的数据类型
将一个n位的整数a赋值给另一个m位的整数b(n>m)--------截断策略
将a的低m位的01序列赋值给b,丢弃a的高位部分。
1.在home目录下的test文件夹中新建intconversion.c文本文件
intconversion.c代码如下
#include "stdio.h"
void main()
{
short si=-100;
unsigned short usi=si;
int i=usi;
unsigned ui=usi;
int i1=si;
unsigned ui1=si;
int i2=0x12348765;
short si2=i2;
unsigned short usi2=i2;
int i3=si2;
int i4=4294967296;
printf("si=%d,usi=%u,i=%d,ui=%u,i1=%d,ui1=%u\n",si,usi,i,ui,i1,ui1);
printf("i2=%d,si2=%d,usi2=%u,i3=%d,i4=%d \n", i2,si2,usi2,i3,i4);
}
2.编译运行
gcc -o0 -m32 -g intconversion.c -o intconversion
3.反汇编该文件
objdump -S intconversion>intconversion.txt
4.打开gdb调试运行到printf语句
gdb intconversion
说明之前的赋值语句都已经执行过
显示当前的esp ebp寄存器的内容
i r esp ebp
显示当前的栈帧内容
x/60xb $esp
由si的机器数,可知小端方式存储
5.分析一:unsigned short usi=si;
si、usi:16位整数,属于相同宽度的两个整数数据之间的赋值。
则为第一种情况,即usi和si的机器数相同,但是真值与数据类型相关。
./ intconversion
6.分析二:int i=usi; unsigned ui=usi;
usi是16位无符号整数,i是32位带符号整数,ui是32位无符号整数。
即零扩展:将一个短的无符号整数赋值给一个长的整数类型。
编译是根据源操作数的类型来确定使用的指令。
i、ui:机器数相同,低16位是usi的01序列,高16位都是0。
7.分析三:int i1=si; unsigned ui1=si;
把si赋值给带符号整数i1,把si赋值给无符号整数ui1,si是16位带符号整数,i1和 ui1都是32位整数。即为第二种情况中的符号扩展,两个语句编译转换后的指令是一样的,都是符号扩展传送指令,
i1和ui1的机器数相同,低16位都是si的01序列,高16位都是si的符号位。
8.分析四:int i2=0x12348765; short si2=i2; unsigned short usi2=i2;
i2赋值给无符号整数usi2,i2是32位带符号整数,si2是16位带符号整数,usi2是16位无符号整数。即为第三种截断操作,两个语句编译转换后的指令是一样的
si2、usi2:机器数相同,都是i2的低16位,i2的高12位都被丢弃
由于数据类型的宽度不同,所以机器数上的赋值过程中,可能出现扩展或截断的情况,所以计算机中的赋值操作不同于数学上的等于操作。
i2赋值给带符号整数si2,i2是32位带符号整数,si2是16位带符号整数。即为第三种截断操作,si2是i2的低16位,i2的高16位被舍弃。si2赋值给i3,为第二种情况中的符号扩展,扩展出来的高16位是si2的符号位0,所以i2和i3的高16位不一样,则i2和i3的机器数必不相同,所以,i2和i3的值不相等。
整数和浮点数之间的转换
整数与浮点数之间的转换,是在编码格式上的转换
带符号整数:补码的编码方法。 例:int是32位带符号整数,1位符号位,31位数值位。
浮点数:采用IEEE 754标准,有float、double两种基本格式。 例:float格式32位编码,1位符号位,8位阶码,23位尾数。
1.在home目录下的test文件夹中新建intconversion.c文本文件
intconversion.c代码如下
#include "stdio.h"
int main()
{
int i1=0x7fffffff, i2, itemp;
float f1=0x987654321, f2,ftemp;
ftemp=i1;
i2=ftemp; //i2=(int)(float)i1;
itemp=f1;
f2=itemp; //f2=(float)(int)f1;
printf("i1=%d,i2=%d,f1=%f,f2=%f\n", i1,i2,f1,f2);
}
2.编译运行,反汇编该文件,输出相应的结果
gcc -o0 -m32 -g intfloat.c -o intfloat
objdump -S intfloat>intfloat.txt
./intfloat
3.打开gdb调试运行到printf语句
gdb intfloat
说明之前的赋值语句都已经执行过
显示当前的esp ebp寄存器的内容
i r esp ebp
显示当前的栈帧内容
x/15xw $esp
4.分析一:i1和i2
i1和i2的机器数不一样。
在整数与浮点数之间的转换时,要进行数据编码格式上的转换,而不是机器数上的直接复制。补码->float编码->补码。
i1: 0x7fff ffff 补码
=0111 1111 1111 1111 1111 1111 1111 1111B
=1.11 1111 1111 1111 1111 1111 1111 1111*2^30 真值
要将i1写成尾数和阶码的格式,尾数的有效数字有31位,float格式浮点数的有效数字只有24位,则需要对尾数进行舍入操作,根据IEEE 754的舍入原则,执行入的操作,则在有效数字的最低位+1。因为10.0B不符合规格化的尾数,所以把尾数调整为1.0B
符号位位0,阶码为31+127,尾数为23个0
i2的机器数编码比i1的机器数编码大1,i2的真值为负数,与i1的真值差异较大。
5.分析二:f1和f2
用二进制值编码有36个二进制位,超过float浮点的24位精度,所以进行舍操作。
f1的浮点数编码是符号位为0,阶码为35+127,尾数为23位二进制,尾数部分如下:
0x51187654是f1的机器数。
f1->itemp,当f1转换为int时需要做编码格式的转换,f1的机器数对初始数据做过社操作,对于初始数据f1的二进制值依旧有36位,但是其低12位全为0,int型只有32位二进制,f1有36位二进制,所以超过了int型的表示范围。
itemp赋值给f2时,又要进行编码格式上的转换,itemp的补码转换为真值,则f2的编码的符号位为1,阶码为31+127,尾数为23个0,则f2的机器数为十六进制的cf000000H,f1和f2的机器数不一样。
根本原因在于f1超出了int型数据的表示范围,转换为int型时,系统赋值为最高位为1,其余位为0的机器数。
总结:
1. 整数与浮点数转换时,是在编码格式上的转换。
2. 在int float转换中,可能会有精度损失、溢出、小数丢弃等问题导 致的数据不一致。
3. 不同编译系统采用的编译优化有差异,同一程序在不同系统上运行,得 到的结果可能不一样。
C语言中的自动类型转换
1.在home目录下的test文件夹中新建fn.c文本文件
fn.c代码如下
#include "stdio.h"
int f1( unsigned int n )
{
int sum = 1, power = 1;
int i;
for ( i = 0; i <= n - 1; i ++ )
{
power *= 2;
sum += power;
}
return sum;
}
int main()
{
int sum;
sum=f1(0);
printf("sum=%d\n",sum);
return 0;
}
2.编译运行,反汇编该文件
gcc -o0 -m32 -g fn.c -o fn
objdump –S fn >fn.txt
i是带符号整数,n是无符号整数,带符号整数和无符号整数比较时,系统采用的是无符号整数的比较指令,i会被自动转换为无符号整数,然后与n-1进行比较
3.打开gdb调试运行到55a这条指令处
gdb fn
显示当前的edx eax寄存器的内容
i r edx eax
4.分析一:
edx中存储着n-1的内容,是16进制的8个f,是32位无符号整数的最大值
eax中存储着i的内容,i的内容在程序的执行过程中,从0开始会逐渐加1,但是在无符号数比较时,无论i的值为多少n-1大于等于i永远成立,i<0xffffff永远成立,因此这条转移指令永远转移到544的指令处执行,不会退出循环体,程序陷入死循环。
不能让系统采用无符号整数的比较转移指令,而要采用带符号整数比较的转移指令,所以n必须设置为带符号整数。
5.分析二:
修改n的定义,定义n为带符号整数,再编译该程序,反汇编可执行目标文件,查看反汇编后的内容
打开gdb调试运行到557这条指令处
gdb fn
显示当前eax寄存器的内容,当n为0时n-1的内容依旧是16进制的8个f,对于带符号整数来说这是负数-1,i=0时,因为0<=-1不成立,所以退出循环体,得到sum的值为1
程序中少数无符号整数在带符号整数和无符号整数一起运算时,系统会自动类型转换为无符号整数处理,而且无符号整数0-1得到了能表示的无符号整数的最大值,则会带来程序的一些错误问题。