C语言数据在内存中的存储

慈云数据 5个月前 (05-29) 技术支持 28 0

C语言数据在内存中的存储

个人主页:大白的编程日记

个人专栏:C语言学习之路


文章目录

  • C语言数据在内存中的存储
    • 前言
    • 一.整数在内存中的存储
      • 1.1整数的表示形式
      • 1.2整数在内存中的存放
      • 二.大小端字节序和字节序判断
        • 2.1大小端的概念
        • 2.2为什么有大小端
        • 三.练习
          • 3.1整型提升
          • 3.2算术转换
          • 3.3练习一(百度笔试题)
          • 3.4练习二
          • 3.5练习三
          • 3.6练习四
          • 3.7练习五
          • 3.8练习六
          • 四.浮点数在内存中的存储
            • 4.1练习
            • 4.2浮点数的存储
            • 4.3浮点数存的过程
            • 4.4浮点数取的过程
            • 后言

              前言

              哈喽,各位小伙伴大家好!我们都知道计算机的数据都是存储在内存中的。那它是如何存储,以什么形式存储,存储方法又是什么呢?今天小编就带着大家一起去学习数据在内存中的存储。向着大厂冲锋!


              一.整数在内存中的存储

              1.1整数的表示形式

              整数的2进制表示方法有三种,即原码、反码和补码。

              三种表示方法均有符号位和数值位两部分,

              符号位都是用0表示“正”,用1表示“负”,

              而数值位最高位的⼀位是被当做符号位,剩余的都是数值位。

              • 正数:

                正整数的原、反、补码都相同。

              • 负数:

                负整数的三种表示方法各不相同。

                原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。

                反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

                补码:反码+1就得到补码。

                注意,补码转原码有两种方式。

                一:先-1后取反。

                二:先取反后+1。


                1.2整数在内存中的存放

                对于整形来说:数据存放内存中其实存放的是补码。

                在计算机系统中,数值⼀律⽤补码来表示和存储。

                为什么呢?

                • 使用补码,可以将符号位和数值域统⼀处理。
                • 加法和减法也可以统⼀处理(CPU只有加法器),可以将减法转化为加法运算。这是用原码计算是错误的,使用补码才能正确运算。

                • 补码与原码相互转换,其运算过程是相同的,都可以按取反+1转化,不需要额外的硬件电路。

                  二.大小端字节序和字节序判断

                  当我们了解了整数在内存中存储后,我们调试看⼀个细节:

                  #include 
                  int main()
                  {
                   int a = 0x11223344;
                   return 0;
                  }
                  

                  调试的时候,我们可以看到在a中的 0x11223344 这个数字是按照字节为单位,倒着存储的。

                  这是为什么呢?

                  这就涉及到大小段字节序的问题了。


                  2.1大小端的概念

                  其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题。

                  我们来想一个问题

                  #include 
                  int main()
                  {
                   int a = 0x11223344;
                   return 0;
                  }
                  

                  如果我们要把a存在内存里我们该怎么存。

                  比如这四种存放顺序。

                  其实只要保证存放时的数据和我们拿出来的数据是一样的,

                  任何顺序都可以。

                  但是为了为了方便理解,我们就选用第一和第二种存放方式。

                  这两种就会大端字节序存放和小端字节序存放。、

                  • 大端字节序存储

                    将一个数据低位字节的内容存放到高地址,把高位字节的内容存放到低地址处。

                  • 小端字节序存储

                    将一个数据高位字节的内容存放到高地址,把低位字节的内容存放到低地址处。

                    注意无论是大端还是小段存储,我存放的是什么,从内存拿出来时就是什么。不会因为倒着存放,拿出来的数据就是倒着的。


                    2.2为什么有大小端

                    这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8bit位,

                    但是在C语言中除了8bit的 char 之外,还有16bit的 short 型,32bit的 long 型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,

                    由于寄存器宽度大于⼀个字节,那么必然存在着⼀个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

                    • 举例

                      ⼀个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么0x11 为高字节, 0x22 为低字节。

                      对于大端模式,就将 0x11 放在低地址中,即 0x0010 中,

                      0x22 放在高地址中,即 0x0011 中。

                      小端模式,刚好相反。我们常用的 X86 结构是小端模式,而KEIL C51 则为大端模式。很多的ARM,DSP都为小段模式。

                      有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。


                      三.练习

                      3.1整型提升

                      C语言中整型算术运算总是至少以默认整型类型的精度来进行的。

                      为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

                      • 整型提升的意义

                        表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度⼀般就是int的字节长度,同时也是CPU的通用寄存器的长度。

                        因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

                        通用CPU(general-purposeCPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

                        //实例1 
                        char a,b,c;
                        ...
                        a = b + c;
                        

                        b和c的值被提升为普通整型,然后再执行加法运算。

                        加法运算完成之后,结果将被截断,然后再存储于a中。

                        那如何整型提升呢?

                        • 有符号整数提升是按照变量的数据类型的符号位来提升的。
                        • 无符号整数提升,高位补0。
                          //负数的整形提升 
                          char c1 = -1;
                          变量c1的⼆进制位(补码)中只有8个⽐特位:
                          1111111
                          因为 char 为有符号的 char
                          所以整形提升的时候,⾼位补充符号位,即为1
                          提升之后的结果是:
                          11111111111111111111111111111111
                          //正数的整形提升 
                          char c2 = 1;
                          变量c2的⼆进制位(补码)中只有8个⽐特位:
                          00000001
                          因为 char 为有符号的 char
                          所以整形提升的时候,⾼位补充符号位,即为0
                          提升之后的结果是:
                          00000000000000000000000000000001
                          //⽆符号整形提升,⾼位补0 
                          

                          3.2算术转换

                          如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就无法进行。下⾯的层次体系称为寻常算术转换。

                          long double
                          double
                          float
                          unsigned long int
                          long int
                          unsigned int
                          int
                          

                          如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外⼀个操作数的类型后执行运算。


                          3.3练习一(百度笔试题)

                          请简述大端字节序和小端字节序的概念,设计⼀个小程序来判断当前机器的字节序。(10分)-百度笔试题

                          以1为例。

                          我们只需要拿到低地址处的字节内容即可。

                          那怎样才能拿到呢?

                          #include 
                          int check_sys()
                          {
                          	int i = 1;
                          	return (*(char*)&i);//拿到低地址处的字节内容
                          }
                          int main()
                          {
                          	int ret = check_sys();
                          	if (ret == 1)
                          	{
                          		printf("⼩端\n");
                          	}
                          	else
                          	{
                          		printf("⼤端\n");
                          	}
                          	return 0;
                          }
                          

                          我们只需要取出变量i的地址,因为&取出的总是低地址的那个字节地址。

                          但是因为是int类型,而我们只需要一个字节,所以我们强制类型转化为char*。

                          再解引用即可访问低地址的那个字节,再判断是1还是0即可。

                          • 验证:

                            在这里插入图片描述


                            3.4练习二

                            int main()
                            {
                            	char a = -1;
                            	signed char b = -1;
                            	unsigned char c = -1;
                            	printf("a=%d,b=%d,c=%d", a, b, c);
                            	return 0;
                            }
                            

                            上面的代码会输出啥?

                            首先我们需要知道signed char和unsigned char的区别

                            • signed char

                            • unsigned char

                              我们现在再回到题目。

                            • 补码存储

                              因为内存中存储的是二进制的补码,所以我们先把-1的补码写出来。

                            • 截断

                              因为-1是整数,放在char类型变量需要发生截断,只保留后八位比特位。

                            • 整型提升

                              注意char是有符号的char还是无符号的char是取决于编译器的!

                              在vs中char等价于signed char。

                                  char a = -1;
                              	signed char b = -1;
                              	unsigned char c = -1;
                              	printf("a=%d,b=%d,c=%d", a, b, c);
                              

                              因为是%d打印,%d是打印有符号整数。所以这里需要发生整型提升。

                              整型提升有符号数按照符号位填充,无符号数用0填充。

                              所以结果就是-1 -1 255。

                              • 验证

                                所以数据在存储时不会关心是否是有符号数还是无符号数。

                                输出时才会考虑是否是有符号数还是无符号数。


                                3.5练习三

                                #include 
                                int main()
                                {
                                	char a = -128;
                                	printf("%u\n", a);
                                	return 0;
                                }
                                
                                • 补码存储

                                  这里我们写出-128的补码。

                                • 截断

                                • 整型提升

                                  %u认为内存存放的是无符号数。

                                  a是char类型,需要发生整形提升。

                                • 验证:


                                  #include 
                                  int main()
                                  {
                                   char a = 128;
                                   printf("%u\n",a);
                                   return 0;
                                  }
                                  

                                  所以这两个代码的输出一样。

                                  • 验证:


                                    3.6练习四

                                    #include 
                                    int main()
                                    {
                                     char a[1000];
                                     int i;
                                     for(i=0; i
                                     a[i] = -1-i;
                                     }
                                     printf("%d",strlen(a));
                                     return 0;
                                    }
                                    
                                       a[i] = -1-i;
                                     }
                                    
                                     for(i = 0;i
                                     printf("Hello World\n");
                                     }
                                     return 0;
                                    }
                                    
                                     unsigned int i;
                                     for(i = 9; i = 0; i--)
                                     {
                                     printf("%u\n",i);
                                     }
                                     return 0;
                                    }
                                    
                                     int a[4] = { 1, 2, 3, 4 };
                                     int *ptr1 = (int *)(&a + 1);
                                     int *ptr2 = (int *)((int)a + 1);
                                     printf("%x,%x", ptr1[-1], *ptr2);
                                     return 0;
                                    }
                                    
                                     int n = 9;
                                     float *pFloat = (float *)&n;
                                     printf("n的值为:%d\n",n);
                                     printf("*pFloat的值为:%f\n",*pFloat);
                                     *pFloat = 9.0;
                                     printf("num的值为:%d\n",n);
                                     printf("*pFloat的值为:%f\n",*pFloat);
                                     return 0;
                                    }
                                    
                                    	float f = 5.5f;
                                    	return 0;
                                    }
                                    
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon