详解数据在内存中的存储

news2024/11/17 16:15:42

系列文章目录

第一章 C语言基础知识

第二章 C语言控制语句

第三章 C语言函数详解

第四章 C语言数组详解

第五章 C语言操作符详解

第六章 C语言指针详解

第七章 C语言结构体详解

文章目录

1. 数据类型

1.1 基本数据类型

1.2 派生数据类型

2. 整形在内存中的存储

2.1 原码、反码、补码

2.2 大端小端

2.3 代码示例:

3. 浮点型在内存中的存储

3.1 代码示例

3.2 浮点数存储规则


1. 数据类型

1.1 基本数据类型

整型(int):用于表示整数,包括正整数、负整数和零。   int x = 10;
short:短整型  
long:长整型  
long long:更长的整形
字符型(char):用于表示单个字符,可以是字母、数字或特殊字符。char ch = 'A';
浮点型(float、double):用于表示带有小数部分的数值。float num = 3.14;
布尔型(bool):用于表示逻辑值,只有两个取值:true(非零)和false(零)。bool flag = true;

占用存储空间:

char 

  • char类型通常占用1个字节(8位)的内存空间。
  • 存储的数据范围为-128到127(有符号char)或0到255(无符号char)。

short

  • short类型通常占用2个字节(16位)的内存空间。
  • 存储的数据范围为-32768到32767(有符号short)或0到65535(无符号short)。

int

  • int类型的大小通常为系统的字长,例如在32位系统中占用4个字节(32位),在64位系统中占用8个字节(64位)。
  • 存储的数据范围为-2147483648到2147483647(有符号int)或0到4294967295(无符号int)。

long

  • long类型通常占用4个字节(32位)或8个字节(64位)的内存空间,取决于系统的字长。
  • 存储的数据范围与int类型相似,但更大。

float

  • float类型通常占用4个字节(32位)的内存空间。
  • 存储的数据范围为IEEE754标准中的单精度浮点数范围。

double

  • double类型通常占用8个字节(64位)的内存空间。
  • 存储的数据范围为IEEE754标准中的双精度浮点数范围。

1.2 派生数据类型

数组(Array)
数组是一种存储相同类型数据元素的连续内存区域,通过下标来访问数组中的元素。在C语言中,数组的声明形式为type name[size],其中type表示数组中元素的类型,name表示数组的名称,size表示数组的大小。

int arr[5] = {1, 2, 3, 4, 5};

结构体(Struct)
结构体是一种用户自定义的数据类型,用于将多个不同类型的数据组合在一起形成一个新的数据类型。在C语言中,结构体的声明形式为struct关键字后跟结构体的名称,然后是一对大括号内部包含各个成员变量的声明。

struct Point {
    int x;
    int y;
};

指针(Pointer)
指针是一种特殊的数据类型,用于存储变量的地址。通过指针可以实现对变量的间接访问,以及动态内存分配和释放等功能。

int *ptr = &x;

枚举(Enum)
枚举是一种用于定义一组有限的命名常量集合的数据类型。枚举类型可以用于提高程序的可读性,使代码更加清晰易懂。

enum Color { RED, GREEN, BLUE };

联合(Union)
联合是一种特殊的数据类型,用于存储不同类型的数据,但在同一时间只能存储其中的一种类型。联合的大小等于其最大成员的大小。

union Data {
    int i;
    float f;
};

2. 整形在内存中的存储

2.1 原码、反码、补码

计算机中的整数有三种二进制表示方法,即原码、反码和补码。 三种表示方法均有符号位数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位:正数的原、反、补码都相同, 负整数的三种表示方法各不相同。

原码(Sign-Magnitude)

原码是最直观的表示方式,直接将数值按照正负数的形式翻译成二进制就可以得到原码。 其中最高位表示符号位(0表示正数,1表示负数),其余位表示数值的绝对值。

例如,+5的原码表示为00000101,-5的原码表示为10000101。

反码(Ones' Complement)

反码是将原码中的正数不变,负数按位取反(0变为1,1变为0)得到的编码方式。

例如,+5的反码和原码相同(00000101),-5的反码为11111010。

补码(Two's Complement)

补码是将原码中的正数不变,负数取反后再加1得到的编码方式。也就是反码+1就得到补码。 补码可以解决反码的问题,即负零和两个零的存在。补码中只有一个零,即00000000。

例如,+5的补码和原码相同(00000101),-5的补码为11111011。

在计算机中,整数类型的数据存储在内存中时,通常采用补码形式。这主要是为了简化算术运算和减少硬件设计的复杂性。补码具有以下几个优点:

  • 唯一表示零:补码能够唯一地表示零,而原码和反码都存在正零和负零的问题。在补码中,只有一个零的表示方式,即所有位均为0。

  • 简化加法和减法运算:在补码中,加法和减法的运算规则是一致的,无需额外的逻辑操作。例如,将两个补码相加,然后将结果直接截取为指定位数即可,而无需考虑正负数的特殊情况。

  • 统一处理溢出:在补码中,溢出时会自然地从最高位溢出到符号位,从而实现了对于正数和负数溢出的统一处理方式。

  • 硬件实现简单:补码的加法和减法可以使用同一套逻辑电路来实现,简化了硬件设计的复杂性。

2.2 大端小端

大小端(Endian)是指在多字节数据存储时,低字节的存放位置和高字节的存放位置的不同排列方式。主要分为大端序(Big Endian)和小端序(Little Endian)两种。

  1. 大端序(Big Endian)

  • 在大端序中,高位字节(Most Significant Byte,MSB)存放在低地址处,低位字节(Least Significant Byte,LSB)存放在高地址处。
  • 例如,十六进制数0x12345678,在内存中的存储顺序是:12 34 56 78。
  1. 小端序(Little Endian)

  • 在小端序中,低位字节(LSB)存放在低地址处,高位字节(MSB)存放在高地址处。
  • 例如,十六进制数0x12345678,在内存中的存储顺序是:78 56 34 12。
       这是因为在计算机系统中是以字节为单位的,每个地址单元都对应着一个字节,一个字节为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处理器还可以由硬件来选择是大端模式还是小端模式。

2.3 代码示例:

1. 简述大端字节序和小端字节序的概念,设计一个程序来判断当前机器的字节序。
// 代码1:通过判断低地址处的字节内容来确定系统的字节序(小端或大端)

#include <stdio.h>

// 检查系统字节序的函数
int check_sys() {
    int i = 1; // 创建一个整数 i,赋值为 1
    return (*(char *)&i); // 返回 i 的低地址处的字节内容
}

int main() {
    int ret = check_sys(); // 调用 check_sys 函数,返回字节内容并赋给 ret
    if (ret == 1) { // 如果返回值为 1,则表示小端字节序
        printf("小端\n"); // 打印小端字节序
    } else {
        printf("大端\n"); // 否则打印大端字节序
    }
    return 0;
}

这段代码中,check_sys() 函数首先创建一个整数 i,然后通过将 i 的地址强制转换为字符型指针 char *,再取其指向的内容,即低地址处的字节。如果当前系统是小端字节序,那么该字节的值应该为 1,因为整数 1 的低字节就是 1,所以函数返回 1,表示小端字节序;反之,如果当前系统是大端字节序,那么该字节的值应该为 0,因为整数 1 的低字节是 0,所以函数返回 0,表示大端字节序。

// 代码2:使用联合体检查系统字节序

// 检查系统字节序的函数
int check_sys() {
    union { // 定义一个联合体,用于共享同一段内存空间
        int i; // 整数
        char c; // 字符
    } un; // 联合体变量 un

    un.i = 1; // 将整数 i 赋值为 1
    return un.c; // 返回联合体中字符 c 的值,即 i 的低地址处的字节内容
}

这段代码使用了一个联合体 union,联合体中包含一个整数 i 和一个字符 c。由于联合体的所有成员共享同一段内存空间,所以当给 i 赋值为 1 后,c 的值就是 i 中低地址处的字节,这个值就是用来判断字节序的。因此,和代码1的原理类似,返回的结果也是 10,表示小端或大端字节序。

补码示例:

#include <stdio.h>
int main()
{
    char a= -1;          // 将 -1 赋值给 char 类型的变量 a,这里的 -1 在转换成补码后就是 11111111
    signed char b=-1;    // signed char 类型也是有符号的,所以 -1 在转换成补码后仍然是 11111111
    unsigned char c=-1;  // unsigned char 类型是无符号的,但 -1 在转换成补码后也是 11111111,因为 char 是 8 位的,无符号范围是 0~255,-1 被当作 255 处理
    printf("a=%d,b=%d,c=%d",a,b,c);  // 输出变量 a、b、c 的值,分别是 -1、-1、255
    return 0;
}
#include <stdio.h>
int main()
{
    char a = -128;   // char 类型是有符号的,范围是 -128~127,所以 -128 被当作 -128 处理
    printf("%u\n",a);  // 格式化输出 a 的值,由于使用了 %u,-128 在按无符号打印时被当作 4294967168 处理
    return 0;
}
#include <stdio.h>
int main()
{
    char a = 128;    // char 类型是有符号的,范围是 -128~127,所以 128 被当作 -128 处理
    printf("%u\n",a);  // 格式化输出 a 的值,由于使用了 %u,128 在按无符号打印时被当作 4294967168 处理
    return 0;
}
#include <stdio.h>
int main()
{
    int i= -20;         // 定义有符号整型变量 i,赋值为 -20
    unsigned int j = 10;  // 定义无符号整型变量 j,赋值为 10
    printf("%d\n", i+j);  // 输出 i+j 的值,-20+10= -10,按照补码的形式进行运算,最后格式化成为有符号整数,结果为 -10
    return 0;
}

3. 浮点型在内存中的存储

浮点数在内存中的存储通常采用 IEEE 754 标准来进行表示,这个标准规定了浮点数的存储格式,包括单精度浮点数(float)和双精度浮点数(double)。

3.1 代码示例

#include <stdio.h>

int main() {
    int n = 9;                    // 定义一个整型变量 n,初始值为 9
    float *pFloat = (float *)&n;  // 将 n 的地址强制转换为 float 类型的指针 pFloat

    printf("n的值为:%d\n", n);  // 打印 n 的值
    printf("*pFloat的值为:%f\n", *pFloat);  // 通过指针 *pFloat 打印 n 所指向的浮点数值

    *pFloat = 9.0;  // 修改 *pFloat 指向的值为 9.0,实际上也就修改了 n 的值

    printf("num的值为:%d\n", n);  // 再次打印 n 的值,此时已经被修改为 1092616192
    printf("*pFloat的值为:%f\n", *pFloat);  // 通过指针 *pFloat 打印 n 所指向的浮点数值,此时为 9.0

    return 0;
}

3.2 浮点数存储规则

对于上一个示例:num 和 *pFloat 在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?

这是因为浮点数和整数在内存中的存储格式是不同的。虽然 num*pFloat 在内存中表示的是相同的二进制数据,但它们的解读方式不同:

  • num 是一个整数类型,它会按照整数的解读方式来解释内存中的二进制数据。因此,当我们将整型变量 num 的值打印出来时,会按照整数的格式来解读,得到的结果是整数值 9
  • *pFloat 是一个浮点数指针,它会按照浮点数的解读方式来解释内存中的二进制数据。即使这个内存中的二进制数据实际上是一个整数,但在浮点数的解读方式下,它会被解释为一个浮点数值。这种解读方式会导致我们得到一个较大的浮点数值,而不是我们期望的整数值。

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:

(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
比如:
十进制的 5.0 ,写成二进制是 101.0 ,相当于 1.01×2^2
那么,按照上面 V 的格式,可以得出 S=0 M=1.01 E=2
十进制的 -5.0 ,写成二进制是 - 101.0 ,相当于 - 1.01×2^2 。那么, S=1 M=1.01 E=2
IEEE 754
对于 32 位的浮点数,最高的 1 位是符号位 S ,接着的 8 位是指数 E ,剩下的 23 位为有效数字 M

对于 64 位的浮点数,最高的 1 位是符号位 S,接着的 11 位是指数 E ,剩下的 52 位为有效数字 M
对有效数字M和指数E,IEEE 754还有一些特别规定。
前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
对于指数E,情况会复杂一些。
首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
指数E从内存中取出还可以再分成三种情况:
E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
比如:
0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:
0 01111110 00000000000000000000000
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
好了,关于浮点数的表示规则,就说到这里。
解释前面的代码:
为什么 0x00000009 还原成浮点数,就成了 0.000000 ?
首先,将 0x00000009 拆分,得到第一位符号位s=0,后面8位的指数 E=00000000 ,最后23位的有效数字M=000 0000 0000 0000 0000 1001。
9 -> 0000 0000 0000 0000 0000 0000 0000 1001
由于指数E全为0,所以符合上一节的第二种情况。因此,浮点数V就写成:
V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)
V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000。
代码的第二部分。
浮点数9.0,如何用二进制表示?还原成十进制又是多少?
首先,浮点数9.0等于二进制的1001.0,即1.001×2^3。
9.0 -> 1001.0 ->(-1)^01.0012^3 -> s=0, M=1.001,E=3+127=130
所以第一位的符号位s=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010。所以,写成二进制形式,应该是s+E+M,即
0 10000010 001 0000 0000 0000 0000 0000
这个32位的二进制数,还原成十进制,正是 1091567616 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1610802.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣练习题(2024/4/18)

1不相交的线 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j]且绘制的直线不与任何其他连线&#xff08;非水平线…

kaggle 房价预测 得分0.53492

流程 导入需要的包引入文件,查看内容数据处理调用模型准备训练输出结果 导入需要的包 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.model_selection import train_test_split from sklearn.linear_model i…

Pandas介绍与Series创建

1.Pandas介绍 Pandas 是基于 NumPy 的一种工具&#xff0c;该工具是为解决数据分析任务而创建的&#xff0c;Pandas 提供了大量能使我们快速便捷地处理数据的功能 Pandas 与出色的 Jupyter 工具包和其他库相结合&#xff0c;Python 中用于进行数据分析的环境在性能、生产率和协…

【介绍下WebStorm开发插件】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

稀碎从零算法笔记Day53-LeetCode:不同路径 II

稀碎系列有点更不动(更多是自己懈怠了) 题型&#xff1a;矩阵、模拟 链接&#xff1a;63. 不同路径 II - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &…

Emerald AI 2024

使用易于使用的编辑器和大量内置功能,快速创建高质量的人工智能。 Emerald AI 2024是一个完全重写和重新设计的通用人工智能框架,适用于各种人工智能和游戏类型。它的多组件设计使开发人员能够灵活地只使用他们需要的功能,并允许有组织和可管理的工作流程。Emerald AI经过了…

【介绍下LeetCode的使用方法】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

OpenHarmony其他工具类—libharu [GN编译]

简介 libharu主要用于生成 PDF格式文件。 下载安装 直接在OpenHarmony-SIG仓中搜索libharu并下载。 使用说明 以OpenHarmony 3.1 Beta的rk3568版本为例 库代码存放路径&#xff1a;./third_party/libharu 修改添加依赖的编译脚本&#xff0c;路径&#xff1a;/developtools…

OpenHarmony南向开发案例:【智能中控屏】

样例简介 本Demo是基于Hi3516开发板&#xff0c;使用开源OpenHarmony开发的应用。通过控制面板可以控制同一局域网内的空调&#xff0c;窗帘&#xff0c;灯等智能家居设备。 当前支持的配套L0设备只有[智能灯]&#xff0c;如需添加新的设备。 应用运行效果图&#xff1a; 样…

nginx--Nginx转发真实的IP

Nginx转发真实的IP 前言给nginx.conf 设置proxy_set_headerjava 程序里获取 前言 在使用nginx的时候可能会遇到判断是不是本机在做操作&#xff0c;这样的话web端我们是可以通过ip和端口进行远程连接的这样的话我们就需要从后端获取到真实ip来判断是不是指定的机器了&#xff…

PS-ZB转座子分析流程2-重新分析并总结

数据处理 数据质控 随机挑出九个序列进行比对&#xff0c;结果如下&#xff1a; 所有序列前面的部分序列均完全相同&#xff0c;怀疑是插入的转座子序列&#xff0c;再随机挑选9个序列进行比对&#xff0c;结果如下&#xff1a; 结果相同&#xff0c;使用cutadapt将该段序列修…

【C语言】贪吃蛇项目(2)- 实现代码详解

文章目录 前言一、游戏开始界面设计首先 - 打印环境界面其次 - 游戏地图、蛇身及食物的设计1、地图2、蛇身设置及打印3、食物 二、游戏运行环节蛇的上下左右移动等功能蛇的移动 三、结束游戏代码 前言 在笔者的前一篇博客中详细记载了贪吃蛇项目所需的一些必备知识以及我们进行…

【飞桨AI实战】人体姿态估计:零基础入门,从模型训练到应用开发

前言 本次分享将带领大家从 0 到 1 完成一个人体姿态估计任务&#xff0c;覆盖数据准备、模型训练、推理部署和应用开发的全流程&#xff0c;项目将采用以PaddlePaddle为核心的飞桨深度学习框架进行开发&#xff0c;并总结开发过程中踩过的一些坑&#xff0c;希望能为有类似项…

模电期末复习(二)放大电路的基本原理和分析方法

放大电路的基本原理和分析方法 2.1 放大的概念2.2 放大电路的主要技术指标2.3 单管共发射极放大电路2.3.1 单管共发射极放大电路的组成2.3.2 单管共射放大电路的工作原理 2.4 放大电路的基本分析方法2.4.1 直流通路与交流通路2.4.2 静态工作点的近似估算2.4.3 图解法&#xff…

第23天:安全开发-PHP应用后台模块SessionCookieToken身份验证唯一性

第二十三天 一、PHP后台身份验证模块实现 二、Cookie&Session技术&差异 1.生成cookie的原理图过程&#xff1a;见上图 客户端向服务器发送HTTP请求。服务器检查请求头中是否包含cookie信息。如果请求头中包含cookie信息&#xff0c;则服务器使用该cookie来识别客户端…

C++奇迹之旅:构造函数和析构函数

文章目录 &#x1f4dd;类的6个默认成员函数&#x1f320; 构造函数&#x1f309; 概念&#x1f309;特性&#x1f309;三种默认构造函数 &#x1f320; 析构函数&#x1f320; 特性&#x1f6a9;总结 &#x1f4dd;类的6个默认成员函数 如果一个类中什么成员都没有&#xff0…

OpenHarmony其他工具类—lua

简介 Lua是一种功能强大、高效、轻量级、可嵌入的脚本语言。 支持过程编程、面向对象编程、函数编程、数据驱动编程和数据描述。 下载安装 直接在OpenHarmony-SIG仓中搜索lua并下载。 使用说明 以OpenHarmony 3.1 Beta的rk3568版本为例 将下载的lua库代码存在以下路径&#…

javase__进阶 day13stream流和方法引用

1.不可变集合 1.1 什么是不可变集合 ​ 是一个长度不可变&#xff0c;内容也无法修改的集合 1.2 使用场景 ​ 如果某个数据不能被修改&#xff0c;把它防御性地拷贝到不可变集合中是个很好的实践。 ​ 当集合对象被不可信的库调用时&#xff0c;不可变形式是安全的。 简单…

von Mises-Fisher Distribution (代码解析)

torch.distribution 中包含了很多概率分布的实现&#xff0c;本文首先通过均匀分布来说明 Distribution 的具体用法, 然后再解释 von Mises-Fisher 分布的实现, 其公式推导见 von Mises-Fisher Distribution. 1. torch.distribution.Distribution 以下是 Uniform 的源码: cl…

黑灰产行业简介

参考&#xff1a;2021年黑灰产行业研究及趋势洞察报告 1. 有哪些场景面临大量黑灰产攻击&#xff1f; 1.营销活动场景 -- 该场景最为猖獗 1. 抹机及接码注册&#xff1a;黑灰产会使用抹机工具修改设备参数伪装成一台新设备&#xff0c;再配合联系卡商进行手机号接码&#xf…