C语言----内存中的数据运算
1、数据在内存中的存储方式----补码
C语言程序中,所有变量的值都以补码的形式存储在内存之中。计算机中数据的表示方法有三种形式:原码、反码和补码。
其中:正数的补码=原码,负数的补码按照下面的规则进行计算:
原码:直接将数据转化为二进制序列。(如果是有符号数:最高位为符号位,符号位为0代表正数,符号位为1代表负数。 如果是无符号数,则没有符号位)。
补码:原码的符号位不变,其余二进制位 按位取反即可得到反码。
补码:反码+1
其中:补码转化为原码时,不需要按照上诉步骤进行反操作。可以直接将操作顺序中的补码和原码互换(即:原码=补码按位取反(符号位不变),再+1。 补码=原码按位取反(符号位不变),再+1)。
原码和补码都是二进制,为什么非要将原码换成补码再放到内存中呢? 答案是:如果内存中直接放原码可能会导致一些问题。
假设内存中存放的是原码,无符号数或正数的操作完全没问题。
比如:char a=1; char b=2; char c=a+b;
转换为二进制原码:a= 0000 0001; b=0000 0010; c=a+b=0000 0011=3.
但对于有符号负数来说,就会存在一个问题:符号位怎么操作?
比如: char a= -1; char b=-2; char c=a+b;
转换为二进制原码:a= 1000 0001; b=1000 0010; c=a+b=1 0000 0011=0000 0011=3;(二个符号位都是1,相加进位,但char只能存储8位数据,所以,最高位被丢失,本来不是符号位的0变成了符号位,致结果错误)。
如果将其转化为补码:a=1111 1111; b=1111 1110; c=a+b=1 1111 1101; 去掉最高位后c=1111 1101,此时c表示的是补码,再将其转化为原码:c=1000 0011=-3
所以,将数据转化为补码后再放入内存中,就不需要将符号位单独拿出来进行处理。这也是为什么内存中存放的数据都是补码的原因。
2、表达式求值
1:表达式书写的规范性
表达式和我们写的数学公式是类似的,必须明确优先级才能得出我们需要的值。不同的是,对于数学问题来说,编译器是人脑,对于C语言程序来说,编译器是电脑软件。这将导致一些我们人脑认为没有问题的表达式,电脑却看不懂。
比如:a=b*c + c*d +d*e; 对于人脑来说,因为 + 的优先级比 * 低,所以我们默认会先计算a*b c*d d*e,再将三个值相加。但是对于编译器来说却不一定是这样,第一个 * 比第一个 + 的优先级高,但第三个 * 的优先级一定比第一个 + 的优先级高吗? 答案:这取决于编译器,不同的编译器会给出不同的解释,最后也会导致不同的结果。
所以:表达式求值的顺序只有一部分是由操作符的优先级和结合性决定的。
对于下面这个代码:
int main() { int i = 10; i = i-- - --i * ( i = -3 ) * i++ + ++i; printf("i = %d\n", i); return 0; }
不同的编译器可能会给出不同的结果:
值 编译器 -128 Tandy 6000 Xenix 3.2 30 Dec Alpha OSF1 2.0 4 Visual Studio 2019 上诉代码就是逻辑混乱的代码,又叫非法代码。(所以我们写代码时,只有在保证逻辑正确的基础上,才能简化代码。所以代码不是越长越好,但也不是越短越好)。
2:整形提升(隐式转换)
对于char short 和int型的变量,如果其是整数,在进行整数运算时,运算环境都是在CPU中的内整形运算器(ALU)内完成的,而ALU的操作数的字节长度是Int型,这也是目前CPU的通用寄存器的长度。 这意味着:哪怕只是2个char型的整数相加运算,运算前CPU也会将变量转化为Int型变量,再计算。 这里不讨论最后输出的数据类型,因为C语言支持 强制类型转换,你可以将结果输出为你需要的数据类型。
举例: char a=8; char b= -1; char c=a+b; 首先:a的二进制补码为:a=0000 1000 ; b的二进制补码为: b=1111 1111;
a b都是整数,运算时,将a b进行整型提升(相当于将a b强制类型转换为 int型),转换时:如果是有符号数,补符号位。如果是无符号数,补0; (注意:a b是不是有符号是只取决于定义a b时,a b的数据类型是不是有符号的)
这里a b都是有符号数,所以高位补 符号位。(假设系统为32位)即a= 00000000 00000000 00000000 0000 1000; b=11111111 11111111 11111111 11111111; c=a+b=1 00000000 00000000 00000000 00000111。
因为定义c时,将其数据类型定义为char型,所以此时内存中c=00000111;(只保留后8位)。 此时c对应的10进制数为:c=00000111 =7;(注意:正数的补码=原码,只有负数才需要通过取反+1计算补码)
这里需要注意的是:(我们定义:占用空间小的数据类型为 小数据类型,占用空间大的数据类型为 大数据类型。 这里的小 大 只是相对而言,比如int 相对于char 就是大数据类型,char就是小数据类型)
(1)二个不同数据类型的整数进行运算,一般都是先将小数据类型的数提升到大数据类型。比如说 int a 和 float b, a b进行运算时,先将a的数据类型提升到float,再运算。
(2)二个不同数据类型的整数进行运算,如果大数据类型还没int大,则将二个整数都提升到int。比如 int a 和char b,a b进行运算时,先将b的数据类型提升到int型,再运算。 char a short b, a b运算时,将a和b都提升到int型,再运算。
(3)运算结果的数据类型与大数据类型保持一致(>=int)。比如:int a 和 float b的运算结果是float型。 char a short b运算的结果是int型。 最后用来接收结果的变量c只会根据自己的数据类型进行数据的截取,比如 int a=00000000 00000000 00000001 00000000, char b=a; 那么b=00000000(a的后8位)。
(4)最重要的一点:只有参与运算才会发生整型提升。(什么是参与运算: char a; a+1; 这里a参与了运算。 char a; !a; 这里只是将a进行取反操作,a本身并没有参与表达式运算)
有了上诉结论,如何运算时不注意数据类型,那么就会导致出现一些我们不想看到的结果。这些问题的本质就是程序运行会按照既定的逻辑进行,这与我们的主观想象并不全是一样的,这也叫 隐式转换。
这里先给出C语言常用数据类型:
数据类型 | 所占空间 |
char | 1字节(8bit) |
short | 2字节(16bit) |
int | 4字节(32bit) |
long | 4字节(32bit) |
long long | 8字节(64bit) |
float | 4字节(32bit) |
double | 8字节(64bit) |
整型提升实例:
实例1:
#include <stdio.h> int main() { char c = 1; printf("%u\n", sizeof(c)); printf("%u\n", sizeof(+c)); printf("%u\n", sizeof(!c)); return 0; }
输出结果为: 1 4 1 (程序中u%是指无符号整型(unsigned int)。sizeof(a) 求的是a空间占几个字节)
这里需要注意的是:sizeof(a)的作用是求a空间占几个字节,但sizeof是C语言的关键字,即中sizeof(a)过程中,a并没有参与运算。
printf("%u\n", sizeof(c)); //求c的空间大小,c为char型,并且sizeof(a)不是运算操作,使用c并没有发生整型提升,即结果为1字节
printf("%u\n", sizeof(+c));// +c是运算,c被整型提升,所以输出结果为4字节(这也是整型提升最直接的证据)
printf("%u\n", sizeof(!c)); // !c并没有参与表达式运算,所以输出结果为1字节实例2:
#include <stdio.h> int main() { char a = 0xb6; short b = 0xb600; int c = 0xb6000000; if (a == 0xb6) printf("a"); if (b == 0xb600) printf("b"); if (c == 0xb6000000) printf("c"); return 0; }
输出结果为: c
if语句中都进行了赋值操作,即a b c都参与了表达式运算,而a b的数据类型比Int小,所以要进行整型提升,而c不需要进行整型提升,所以只有c=0xb600000000成立。
如果我们想要输出 a和b,我们通过整形提升来反推a b的真实值。
char a=0xb6; 写出二进制:a=1011 0110 。 a为有符号数,整型提升时,高位补符号位,则 a= 11111111 11111111 11111111 10110110=0xFFFFFFB6
char b=0xb600; 其二进制为b= 10110110 00000000。 b为有符号数,整型提升时,高位补符号位,则b= 11111111 11111111 10110110 00000000=0xFFFFB600
将代码改为:
#include <stdio.h> int main() { char a = 0xb6; short b = 0xb600; int c = 0xb6000000; if (a == 0xFFFFFFB6) printf("a"); if (b == 0xFFFFB600) printf("b"); if (c == 0xb6000000) printf("c"); return 0; }
输出结果: a b c
总结:
(1)C语言中,支持数据类型强制转换。强制转换时,如果将小数据类型转为大数据类型,相当于将某块内存中的数据放到一个更大的内存空间中(这样做一般不会造成程序问题)。但如果反之,相当于把某块内存中的数据放到一个更小的内存空间中,这就可能导致数据截取时发生数据丢失。
(2)C语言中,如果变量的数据类型比int小,则其参与表达式运算时,CPU会先将其数据类型强制转换为Int,再让其进行运算。