D16—C语言内功之数据在内存中的存储

目录

引言

[1. 整数在内存中的存储](#1. 整数在内存中的存储)

[1.1 原码、反码、补码](#1.1 原码、反码、补码)

[2. 大小端字节序和字节序判断](#2. 大小端字节序和字节序判断)

[2.1 什么是大小端?](#2.1 什么是大小端?)

[2.2 为什么有大小端?](#2.2 为什么有大小端?)

[2.3 判断当前机器字节序的方法](#2.3 判断当前机器字节序的方法)

[3. 浮点数在内存中的存储](#3. 浮点数在内存中的存储)

[3.2 示例分析](#3.2 示例分析)

[4. 练习巩固与解析](#4. 练习巩固与解析)

练习一:

练习二:

练习三:

练习四:

练习五:

总结


引言

在C语言编程中,理解数据在内存中的存储方式至关重要。它不仅关系到程序的正确性,还涉及性能优化、跨平台兼容性等高级话题。本文将深入探讨整数、浮点数在内存中的存储方式,以及大小端字节序的概念和判断方法,并通过多个练习巩固理解。

1. 整数在内存中的存储

整数在计算机内存中是以补码形式存储的。为什么使用补码?原因如下:

  • 统一符号位和数值位的处理

  • 加法和减法可以统一用加法器实现

  • 补码与原码转换过程相同,无需额外硬件电路

1.1 原码、反码、补码

  • 原码:直接将数值转换为二进制,最高位为符号位(0正1负)

  • 反码:正数反码与原码相同;负数反码为原码符号位不变,其余位取反

  • 补码:正数补码与原码相同;负数补码为反码加1

示例

cpp 复制代码
int a = -10;

原码:10000000 00000000 00000000 00001010

反码:11111111 11111111 11111111 11110101

补码:11111111 11111111 11111111 11110110

实际内存中存储的是补码。

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

2.1 什么是大小端?

对于多字节数据(如int、short),在内存中的存储顺序有两种:

  • 大端模式:数据的高位字节存放在低地址处,低位字节存放在高地址处

  • 小端模式:数据的低位字节存放在低地址处,高位字节存放在高地址处

示例

cpp 复制代码
int a = 0x11223344;

我们在VS上调试发现,这里满足数据的低位字节存放在低地址处,高位字节存放在高地址处,也就是小端排序。

2.2 为什么有大小端?

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为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(0000001)的地址进行强制类型转换为(char*)并进行解引用,如果得到的是1,那么就是小端字节序,是0则是大端字节序。

cpp 复制代码
#include<stdio.h>
int is_sys(int num)
{
	int* p = &num;
	return *(char*)p;
}
int main()
{ 
	int ret=is_sys(1);
	if (ret)
		printf("小端排序\n");
	else printf("大端排序\n");
	return 0;
}

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

常见的浮点数:3.14159、1E10等,浮点数家族包括: float 、 double 、 long double 类型。
浮点数存储遵循IEEE 754标准,将浮点数表示为:

举例来说:

十进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2 。

那么,按照上面的格式,可以得出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存储时省略首位的1(因为总是1),只存储小数部分

  • E存储时为实际值加偏移量(8位E加127,11位E加1023)

3.2 示例分析

cpp 复制代码
#include <stdio.h>

int main() {
    int n = 9;
    float *pFloat = (float*)&n;
    printf("n的值为:%d\n", n);          // 输出9
    printf("*pFloat的值为:%f\n", *pFloat); // 输出0.000000

    *pFloat = 9.0;
    printf("num的值为:%d\n", n);          // 输出1091567616
    printf("*pFloat的值为:%f\n", *pFloat); // 输出9.000000
    return 0;
}

解释

  • 整数9的补码:00000000 00000000 00000000 00001001按浮点数解释:S=0, E=00000000, M=000...01001由于E全0,结果为接近0的小数,输出0.000000

  • 浮点数9.0二进制:1001.0 = 1.001×2³,存储时:S=0, E=3+127=130 (10000010), M=001...

    组合后的二进制被当作整数解释,即为1091567616

4. 练习巩固与解析

练习一:

cpp 复制代码
#include <stdio.h>
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;
}
// 输出:a=-1,b=-1,c=255

解析:a和b为有符号char,-1的补码为11111111,按%d输出时整型提升为-1。c为无符号,整型提升后为255。

练习二:

cpp 复制代码
#include <stdio.h>
int main() {
    char a = -128;
    printf("%u\n", a);
    return 0;
}

解析:-128的补码为10000000,按%u输出时整型提升为无符号整数,即4294967168。

练习三:

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main() {
    char a[1000];
    int i;
    for(i = 0; i < 1000; i++) {
        a[i] = -1 - i;
    }
    printf("%d", strlen(a));
    return 0;
}

解析:char的取值范围为-128~127。当i从0到999,a[i]的值从-1递减,经过-128后变为127,再到0。strlen遇到'\0'(即0)停止,因此统计从-1到-128(128个值)和127到1(127个值),共255个字符。

练习四:

cpp 复制代码
#include <stdio.h>
unsigned char i = 0;
int main() {
    for(i = 0; i <= 255; i++) {
        printf("hello world\n");
    }
    return 0;
}

解析:unsigned char 的范围是0~255,不会跳出循环,所以会死循环地打印hello world。

练习五:

cpp 复制代码
//小端环境下
#include <stdio.h>
int main() {
    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;
}

解析

  • &a + 1跳过了整个数组,ptr1[-1]*(ptr1-1)为a[3]=4

  • (int)a + 1将地址按字节偏移1,在小端模式下,a[0]的存储为01 00 00 00,偏移1字节后指向00,接着读取4字节得到00 00 00 02,即0x2000000

总结

  • 整数存储采用补码,统一了加减法运算

  • 大小端字节序影响多字节数据的存储顺序,可通过简单程序判断

  • 浮点数存储遵循IEEE 754标准,通过S、M、E三部分表示,存储和读取有特殊规则

理解数据在内存中的存储方式,有助于编写更健壮、高效的C语言程序,尤其是在涉及底层操作、跨平台开发和数据序列化时。


欢迎在评论区交流讨论,如果觉得有帮助,请点赞收藏支持!

更多C语言技术文章,请访问我的博客主页:https://blog.csdn.net/2402_87657156

相关推荐
leo__5202 小时前
C#与三菱PLC串口通信源码实现(基于MC协议)
开发语言·c#
二十雨辰2 小时前
[python]-函数
开发语言·python
码农水水2 小时前
中国邮政Java面试被问:容器镜像的多阶段构建和优化
java·linux·开发语言·数据库·mysql·面试·php
福楠3 小时前
C++ STL | map、multimap
c语言·开发语言·数据结构·c++·算法
ytttr8733 小时前
地震数据频率波数域变换与去噪的MATLAB实现
开发语言·matlab
极客小云3 小时前
【基于 PyQt6 的红外与可见光图像配准工具开发实战】
c语言·python·yolo·目标检测
墨瑾轩3 小时前
C# PictureBox:5个技巧,从“普通控件“到“图像大师“的蜕变!
开发语言·c#·swift
墨瑾轩3 小时前
WinForm PictureBox控件:3个让图片“活“起来的骚操作,90%的开发者都踩过坑!
开发语言·c#
Ethernet_Comm3 小时前
从 C 转向 C++ 的过程
c语言·开发语言·c++