C语言,数据在内存中的存储。——保姆级教学

简介: C语言,数据在内存中的存储。——保姆级教学

序言:

学了很久的C语言,直到C语言有很多的数据类型,但是你知道他们在内存中是怎么存储的吗?今天小编就带大家一起来,深入学习一下,数据在内存中是怎么存储的。今天主要介绍整型。关于浮点型,我们后面单独拿出来讲。


一.类型

一说到整形,大家肯定立马就想到了,int ,  short,  long,  long long,但是其实char也是整形大家族的一员。哎,有的小伙伴就有疑惑了,char 不是字符型吗,怎么成整形了?大家想一想,char在存储一个字符的时候,存储的还是字符的ASCII值,实际上还是存储的0~127之间的整数。存储的模式还是和int  short 一样,不一样的是,他们的大小不一样。



二.二进制的表示形式

我们知道 int 在内存中占有四个字节,32位,但是你知不知道,是怎么存储的吗?


有的小伙伴了解过可能知道,存储的是二进制。二进制直接存进去吗?不是的。


二进制也有自己形式,分别是,源码,反码,补码。计算机在内存中存的就是二进制的补码。


我们接下来就来讨论一下,一个整形数据二进制的,源码,反码,补码。


我们二进制都是 0 1组成的,那么源码,反码,补码也不例外了,三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”。


(1)正数的源码,反码,补码

        正数的原码、反码、补码都相同。源码就是,整形数据直接转换的二进制。

  例如:

    int a = 10;
  //二进制
  00000000000000000000000000001010;  "源码", "反码","补码";
  "首位 0 ,代表二进制数,是个正数";

(2)负数的源码,反码,补码

负数的源码:符号位是 1,其他未按照正数一样转换成二进制。

负数的反码:其源码的符号位不表,其他位按位取反,就是反码了。

负数的补码:在反码的基础上,加一。

例如:

    int b = -10;
  "二进制";"首位 1 ,代表二进制数是负数"
  10000000000000000000000000001010;  "源码"
  11111111111111111111111111110101;  "反码"
  11111111111111111111111111110110;  "补码"

如果需要用补码求源码,也很简单,补码减一,得到反码,反码再按位取反,就得到了源码。


(3)无符号数

我们知道还有一些数是无符号数。对于无符号数就是,首位符号位也看做是数值有效位。


那为什么计算机的存储整形数据的时候,为什么要用补码来存储吗?


原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。


三.大小端介绍

我们知道整形数据再内存中是以补码的形式存储的,我们来看一看到底是不是这么回事。


ce2df8774e514791a1927af565a8e1d8.png


好像是补码,又不完全是,ff ff ff f6,但是显示的是 f6 ff ff ff,好像是倒过来的,但是又不完全是。 其实是倒过来的,只不过是两个十六进制数,一个十六进制数,占4个比特位,两个就占8个比特位,也就是一个字节,而字节又是内存的最小单元,所以两个十六进制数在一起的是没法分开的。所以ff ff ff ff 反过来就是,f6 ff ff ff 。


那么问题来了,为什么会反过来存储呢?


这就是因为存在大小端的问题。为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。


简单来说就是大小端就是俩种,不同的字节序存储方式:


大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址

中;

小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地

址中。


例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为

高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高

地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则

为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式

还是小端模式。


(1)小端存储图例:


2dd347b7d5b942e8b114da9bbae88afe.png


(2)大端存储图例:


d00de6302f3b48b09100aba219fa024b.png


四.大小端字节序的判断

那我们怎么设计程序来判断当前的机器是大端存储还是小端存储呢?


我们知道计算机在往外拿数据的时候,是从低地址往高地址拿取的。那我们只要拿出数据的第一个字节的数据,看看它是高位数据,还是低位数据,就可以判断是大端存储还是小端存储了。

代码:

#include<stdio.h>
int main()
{
  int a = 0x00000001;
  //将数据地址强转为(char*),意味着当解引用访问时,
  //因为char只有一个字节,所以char*解引用访问时,只会
  //访问a的空间的一个字节。
  char* ch = (char*) & a;
  if (*ch == 1)
  {
    printf("小端存储\n");
  }
  else
  {
    printf("大端存储\n");
  }
  return 0;
}


五.整形提升,截断,整形数据的范围

(1)什么是整形提升?

C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。


(2)整形提升又有什么意义呢?

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度

一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。


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


(3)如何进行整体提升呢?

 整形提升是按照变量的数据类型的符号位来提升的

//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001

(4) 截断:将较大数据类型赋值给小的数据类型,就会发生。

截断规则:只需从大的数据类型最低位开始取够小的数据类型的位数,给小的数据类型就可以了

int main()
{
  char a = 4;
//  00000100  补码
//  00000000000000000000000000000100   整形提升后
  char b = -1;
//  11111111  补码
//  11111111111111111111111111111111   整形提升后
  char c = a + b;
//  00000000000000000000000000000100 -  4    补码
//  11111111111111111111111111111111 -  -1   补码
// 100000000000000000000000000000011 -  3    最高位符号位为0,正数源,反,补码相同
//  00000011  ---c    因为 c 是 char类型,所以只需要发生截断,取后8位。
//  %d 是打印有符号整形 仍需要整形提升后打印。
  printf("%d ", c);
  return 0;
}

(5)整形提升的实例

实例1:

int main()
{
  char a = 0xb6;
  //10110110  - a  补码
  //111111111111111111111111 10110110   整形提升过后的补码
  //111111111111111111111111 10110101   反码
  //100000000000000000000000 01001010   源码  = -74
  short b = 0xb600;
  int c = 0xb6000000;
  if (a == 0xb6)
    printf("a");
  if (b == 0xb600)
    printf("b");
  if (c == 0xb6000000)
    printf("c");
  return 0;
}



a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真.

实例2:

int main()
{
    char c = 1;
    printf("%u\n", sizeof(c));
    printf("%u\n", sizeof(+c));
    printf("%u\n", sizeof(-c));
    return 0;
}



六.数据的范围(char 为例)

我们知道char 是有 8 个比特位,如果是有符号的数(signed char),那么最高位就是符号位,有效数据为只有7位。如图:



这里还有一个圆形图:



如果是无符号数(unsigned char),最高位也看作是有效数据位。一共就有8位有效数据位。



最后:

虽然永远无法预料明天是晴还是雨,也无法预知你在乎的人是否还在身旁,以及你一直以来的坚持究竟能否换来什么。但你能决定的是,今天有没有备好雨伞,有没有好好爱自己,以及是否为自己追求的理想而拼尽全力。


相关文章
|
18天前
|
存储 程序员 编译器
C 语言中的数据类型转换:连接不同数据世界的桥梁
C语言中的数据类型转换是程序设计中不可或缺的一部分,它如同连接不同数据世界的桥梁,使得不同类型的变量之间能够互相传递和转换,确保了程序的灵活性与兼容性。通过强制类型转换或自动类型转换,C语言允许开发者在保证数据完整性的前提下,实现复杂的数据处理逻辑。
|
18天前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
30 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
19天前
|
存储 数据管理 C语言
C 语言中的文件操作:数据持久化的关键桥梁
C语言中的文件操作是实现数据持久化的重要手段,通过 fopen、fclose、fread、fwrite 等函数,可以实现对文件的创建、读写和关闭,构建程序与外部数据存储之间的桥梁。
|
22天前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
40 6
|
22天前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
28天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
100 13
|
28天前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
57 11
|
22天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
28天前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
52 11
|
20天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
49 1