C语言表达式在内存中的运行方式

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; 
}

不同的编译器可能会给出不同的结果:

编译器
-128Tandy 6000 Xenix 3.2
30Dec Alpha OSF1 2.0
4Visual 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语言常用数据类型:

C语言常用数据类型
数据类型所占空间
char1字节(8bit)
short2字节(16bit)
int4字节(32bit)
long4字节(32bit)
long long8字节(64bit)
float4字节(32bit)
double8字节(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,再让其进行运算。

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值