【C语言】-数据在内存中的存储(1)



🦆 个人主页:深邃-

❄️专栏传送门:《C语言》《数据结构》

🌟Gitee仓库:《C语言》《数据结构》


目录

整数在内存中的存储

整数的二进制表示有原码、反码、补码三种形式,有符号整数的表示分为符号位(最高位,0 正 1 负)和数值位。

  • 正整数:原、反、补码完全相同
  • 负整数:原码直接翻译二进制,反码为原码符号位不变其余位取反,补码为反码 + 1
  • 内存中实际存储的是整数的补码,原因是可统一处理符号位与数值域、统一加减法运算(CPU 只有加法器)、原补码转换无需额外硬件电路。

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

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

c 复制代码
#include <stdio.h>
int main()
{
	int a = 0x11223344;
	return 0;
}

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

大小端概念

超过 1 个字节的数据在内存中存储存在顺序差异,分为两种模式:

  • 大端(存储)模式:数据的低位字节内容保存在内存高地址处,高位字节内容保存在内存低地址处
  • 小端(存储)模式:数据的低位字节内容保存在内存低地址处,高位字节内容保存在内存高地址处

大小端存在的原因

计算机以字节为单位分配地址,而 C 语言存在 16bit、32bit 等多字节类型,处理器寄存器宽度大于 1 字节时,多字节的存储安排方式不同,因此产生大小端模式。

  • X86 结构为小端模式,KEIL C51 为大端模式
  • 部分 ARM、DSP 为小端模式,部分 ARM 可通过硬件选择大小端

练习1

大小端判断(百度笔试题)

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

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

int check_sys()
{
	int a = 1;
	if (*(char*)&a == 1)//int*
	{
		return 1;//小端
	}
	else
	{
		return 0;//大端
	}
}

int main()
{
	if(check_sys() == 1)
		printf("小端\n");	
	else
		printf("大端\n");

	return 0;
}
c 复制代码
//优化
int check_sys()
{
	int a = 1;
	return (*(char*)&a);//返回1是小端,返回0是大端
}

内存中观察 内存中观察 内存中观察


看得出来是小端

练习2

先看一下整型提升

整型提升
在表达式里,比 int 小的整数类型,会自动先提升为 int,再参与运算。
适用类型

  • char、signed char、unsigned char
  • short、unsigned short(int 及更大类型不动)

char/short 在运算时先变 int,有符号就符号扩展,无符号就零扩展。

c 复制代码
#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;
}

打印的会是多少? 打印的会是多少? 打印的会是多少?

%d - 是以十进制的形式打印有符号的整数(认为我们要打印的数据在内存中是以有符号数的补码进行存储的)

char a = -1;

  • 10000000 00000000 00000000 00000001
    11111111 11111111 11111111 11111110
    11111111 11111111 11111111 11111111
    11111111 - a(补码)

  • %d ,以十进制形式输出一个有符号整数,按照符号位对char整型提升 11111111 11111111 11111111 11111111

  • 这是补码,变成原码打印,取反 + 1,10000000 00000000 00000000 00000001 输出为-1

signed char = char 同理

unsigned char c = -1;

  • 10000000 00000000 00000000 00000001
    11111111 11111111 11111111 11111110
    11111111 11111111 11111111 11111111
    11111111 - c(截断)
    00000000 00000000 00000000 11111111 (unsigned char无符号整型提升为0)
    无符号原码=反码=补码,直接打印

练习3

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

char a = -128;

  • 10000000 00000000 00000000 10000000
    11111111 11111111 11111111 01111111
    11111111 11111111 11111111 10000000
    10000000 - a
    11111111 11111111 11111111 10000000(%u,直接打印,补码=原码)
    printf("%u\n", a);//4,294,967,168
    %u 是以十进制的形式打印无符号的整数 - 内存中存储的是无符号数的补码

练习4

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

char a = 128; char范围:-128~127

  • 00000000 00000000 00000000 10000000 (128补码)
    10000000 - a
    11111111 11111111 11111111 10000000 (char是有符号的,按照符号位整型提升)
    %u 十进制、无符号整数的格式符,直接打印
    printf("%u\n", a); 打印为4,294,967,168

成环



看这俩串代码

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

练习5

c 复制代码
#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 a[1000];

  • 成环范围:-128~127
  • -1 -2 -3 ... -128 127 126 ...3 2 1 0 -1 -2
  • printf("%d", strlen(a));//求字符串的长度,其实是统计\0(0)之前字符的个数
  • 128+127=255

练习6

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

unsigned char i = 0;//0~255

int main()
{
    //死循环了
    for (i = 0; i <= 255; i++)
    {
        printf("%u: hello world\n", i);
    }
    return 0;
}

unsigned 类型永远 ≥ 0,
成环,unsigned char 环:0 ~ 255范围0-255(2⁸ − 1),永远没法超过255

练习7

c 复制代码
#include <stdio.h>
#include <windows.h>

int main()
{
    unsigned int i;
    //>=0
    for (i = 9; i >= 0; i--)
    {
        printf("%u\n", i);
        Sleep(100);//休眠100毫秒
    }
    return 0;
}

unsigned 类型永远 ≥ 0
成环,unsigned int环范围0-4294967295(2³² - 1),永远没法超过4294967295

练习 8

c 复制代码
#include <stdio.h>
//X86环境 小端字节序
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);//?

    //%x 是以16进制的形式打印数据
    
    return 0;
}


分步解析

  1. 数组内存布局(X86 小端字节序)
c 复制代码
int a[4] = {1, 2, 3, 4};

int 占 4 字节,小端序:低字节存低地址

数组在内存中的十六进制表示(连续地址)

c 复制代码
a[0] = 1  →  01 00 00 00
a[1] = 2  →  02 00 00 00
a[2] = 3  →  03 00 00 00
a[3] = 4  →  04 00 00 00
  1. 第一部分:ptr1[-1]
c 复制代码
int *ptr1 = (int *)(&a + 1);
  • &a 是整个数组的地址,类型是 int(*)[4]
  • &a + 1 跳过整个数组(16 字节),指向数组末尾之后
  • ptr1[-1] 等价于 *(ptr1 - 1),回退一个 int,指向 a[3]
    结果:4
  1. 第二部分:*ptr2(核心难点)
c 复制代码
int *ptr2 = (int *)((int)a + 1);
  • (int)a:把数组首地址强转为整数
  • +1:地址向后移动 1 个字节
  • 再强转成 int*,从这个新地址读取 4 个字节

内存偏移后读取的数据:
原数组开头:01 00 00 00 02 00 00 00 ...
偏移 1 字节后:00 00 00 02

小端序解析:
02 00 00 00 → 整数 2
按十六进制 %x 打印 → 2000000

结果:2000000

c 复制代码
printf("%x, %x", ptr1[-1], *ptr2);
// 输出:4, 2000000

严重错误 严重错误 严重错误

用x64环境会导致(int) a 截断,将8字节指针截断为四字节int大小的指针,所以此题是设计好的题

相关推荐
Lyyaoo.2 小时前
【Java基础面经】Java 注解的底层原理
java·开发语言·python
妙蛙种子3112 小时前
【Java设计模式 | 创建者模式】 抽象工厂模式
java·开发语言·后端·设计模式·抽象工厂模式
做怪小疯子2 小时前
LeetCode刷题——15.动态规划模式
算法·leetcode·动态规划
xiaoye-duck2 小时前
《算法题讲解指南:优选算法-字符串》--61.最长公共前缀,62.最长回文子串,63.二进制求和,64.字符串相乘
c++·算法·字符串
如竟没有火炬2 小时前
搜索二维矩阵
数据结构·python·算法·leetcode·矩阵
chh5632 小时前
从零开始学C++--类和对象
java·开发语言·c++·学习·算法
森屿~~2 小时前
PlatEMO 深度实战解析——从底层架构到 CMOPs 与 MMO 算法魔改
算法
♛识尔如昼♛2 小时前
C 基础(5) - 运算符、表达式和语句
c语言
郝学胜-神的一滴2 小时前
自动微分实战:梯度下降的迭代实现与梯度清零核心解析
人工智能·pytorch·python·深度学习·算法·机器学习