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

目录

引言

一、整数在内存中的存储

二、大小端字节序判断

1.大小端

2、练习:设计一个程序用来验证当存储的是大端还是小端

3、练习

[4.char 和 unsigned char 的取值范围](#4.char 和 unsigned char 的取值范围)

5、练习

6、练习

7、练习

8、练习

三、浮点数在内存中的存储

1、引例

2、浮点数的存储


引言

在C语言的世界里,理解数据在内存中的存储方式是每个开发者必须掌握的基础知识。这不仅关系到程序的正确性,还直接影响到性能优化和系统级调试。无论是简单的变量,还是复杂的结构体和数组,它们在计算机内存中的表现方式都隐藏着丰富的细节和精妙的设计。本文将带你深入探索C语言中各种数据类型在内存中的存储规律,帮助你建立起对数据布局的直观理解,从而在日后的程序开发中更加游刃有余。

一、整数在内存中的存储

前面讲了,整数有原码、反码、补码,由符号位和数值位组成。那么对于整型来说,数据存放到内存中的其实是补码

二、大小端字节序判断

1.大小端

大端:高位字节数据存储到低地址处

小端:高位字节数据存储到高地址处

在VS中运行以下代码,并打开内存窗口:

cs 复制代码
int main()
{
   int r = 2;
   return 0;
}

为什么在 r 的地址处显示的是 02 00 00 00 ?而不是一串补码?

因为内存中显示的是 16 进制,所以才会出现数字2

那为什么又是 02 00 00 00 而不是 00 00 00 02?

因为在VS中数据存放是采用小端进行的

举个例子:

cs 复制代码
int main()
{
	int r = 0x11223344;
	return 0;
}

可以看到这次内存中放的是 44 33 22 11,再次验证了VS环境下 内存存储方式是按小端字节序排的

如果是大段字节序,这里应该是 11 22 33 44

2、练习:设计一个程序用来验证当存储的是大端还是小端

假设我们存放 1,在内存中无非是 00 00 00 01 和 01 00 00 00,所以我们只要创建一个整型变量,然后验证其第一个字节是否为 1 就可以了

cs 复制代码
int main()
{
	int r = 1;
	if (*((char*)&r) == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

这里说明一下,对 r 取地址,此时的地址类型为 int* ,所以要强制类型转换为 char* 类型,然后在解引用,就可以访问 r 的第一个字节了

3、练习

cs 复制代码
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 的原码:1000 0001,反码:1111 1110,补码:1111 1111

b 的原码,反码,补码与 a 一致

c 的原码:1000 0001 ,反码:1111 1111,补码:1111 1111

%d:打印十进制的整型。

所以在用 %d 打印 a 的时候,会认为 a 是一个整型,但整型有4个字节,而 a 只有一个字节,怎么办?这时会出现一个名词:整型提升

%d 会把 a 认为成 11111111 11111111 11111111 11111111,但这个是补码,对应的原码为

10000000 00000000 00000000 00000001,打印出来就是 -1 ,b 同理

c为无符号的,那么%d在整型提升的时候就会认为其是无符号的,认为c是

00000000 00000000 00000000 11111111 ,该数现在是正数,原码根补码是一样的,化为十进制就是255

所以改题目的答案为:

4.char 和 unsigned char 的取值范围

char 类型占一个字节,共8个比特位,从0000000开始,计数到11111111,换成十进制就是0~255,但这只是256个数,具体情况我们下面分析

从上图可以看到,char 类型的取值范围是 -128 ~127,共256个数。//这里画图有失误

至于unsigned char 类型,则只需把负数部分当成正数即可

所以unsigned char 的取值范围为 0 ~ 255

5、练习

cs 复制代码
int main()
{
	char a = -128;
	printf("%u\n" ,a);
	return 0;
}

a 的补码:1000 0000 ,%u 是用来打印无符号整型的 ,

整型提升后为:11111111 11111111 11111111 10000000,但%u认为该数是无符号的,为大于0的数,所以认为该码不是补码,而是原码,其转化为十进制为:4294967168

由上面两个练习可以看出,整型提升默认提升的是补码的最高位,最高位是几,整型提升的时候就补几,但是,如果这个数据本身的类型是无符号的,就只会补0,如果这个数本身有符号 ,但是打印的关键字为%u等无符号的,那么还是默认补最高位

6、练习

cs 复制代码
int main()
{
	char arr[1000];
	int i;
	for (int i = 0; i < 1000; i++)
	{
		arr[i] = -1 - i;
	}
	printf("%d" ,strlen(arr));
	return 0;
}

arr 是一个字符数组,前面提到 char 类型的范围为 -128 ~ 127 ,

而且是从0~127 再到 -128 ~ -1 在到 0如此循环的

strlen 函数 只需要接收字符串的首地址,遇到 \0 截止,所以我们需要找到 \0 的位置

arr[0] = -1 ;arr[1] = -2; ...... ; arr[127] = -128; arr[128] = 127; arr[129] = 126......; arr[255] = 0;

可见,第一个 \0 出现在 arr[255] 处,所以strlen的长度是从 arr[0] 到 arr[254] 共255

7、练习

cs 复制代码
unsigned char i = 0;
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("I hate her\n");
	}
	return 0;
}

前面提到过 unsigned char 的范围为 0~ 255,到达255 后又回到 0,

所以这个题 i 永远都符合循环条件,本题为死循环

8、练习

cs 复制代码
//x86环境下,小端字节序
int main()
{
	int arr[4] = { 1,2,3,4 };
	int* ptr1 = (int*)(&arr + 1);
	int* ptr2 = (int*)((int)arr + 1);
	printf("%x, %x" ,ptr1[-1] ,*ptr2 );
	return 0;
}
//%x 是以16进制打印数据

&arr 取的是首元素地址,类型为:int (*) [4],+1 之后,指向数组末尾外的第一个空间,类型不变,然后强制类型转化为 int* ,所以 ptr[-1] 又指向了4 ,所以第一个打印(0x)4

强制类型转化的优先级比加法高一级,所以先进行强制类型转换

arr的类型为 int [4] ,,本身也是首元素 1 的地址,强制类型转化为 int ,然后再+1,相当于地址+1,(但是现在的地址只是一个普通变量),然后再强制类型转化为 int* 类型,现在变成一个int*类型的指针,指向 1 的第二个字节,如图:

所以 *ptr2 就是16进制下的 02000000 打印出来就是 (0x)2000000

三、浮点数在内存中的存储

常见的浮点数:3.1415926 ,1E10(1.0*10^-10)

在头文件 <flat.h> 中可以查看浮点数的表示范围

1、引例

cs 复制代码
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n = %d\n",n);
	printf("*pFlpat = %f\n" ,*pFloat);
	*pFloat = 9.0;
	printf("n = %d\n" ,n);
	printf("*pFloat = %f\n" ,*pFloat);
	return 0;
}

结果

这说明,浮点数的存储方式和整数的存储方式不一样

2、浮点数的存储

根据国际标准IEEE,任何浮点数可以表示为:

:表示符号位,当S = 0 时,表示正数, S = 1 ,表示负数

M :表示有效数字,M是大于等1,小于2的

:表示指数位

例如:

float 类型占4个字节,共32个比特位,在内存中的作用如下:

double类型也是与此相似,这里就不多说了

注意:E为无符号数,但是E会有负数的情况出现,所以存放一个E需要加上1271003 来达到所有的E都大于等于0,从而变为无符号数

相关推荐
Fanxt_Ja2 小时前
【JVM】三色标记法原理
java·开发语言·jvm·算法
蓝婷儿2 小时前
6个月Python学习计划 Day 15 - 函数式编程、高阶函数、生成器/迭代器
开发语言·python·学习
love530love2 小时前
【笔记】在 MSYS2(MINGW64)中正确安装 Rust
运维·开发语言·人工智能·windows·笔记·python·rust
slandarer3 小时前
MATLAB | 绘图复刻(十九)| 轻松拿捏 Nature Communications 绘图
开发语言·matlab
狐凄3 小时前
Python实例题:Python计算二元二次方程组
开发语言·python
roman_日积跬步-终至千里3 小时前
【Go语言基础【3】】变量、常量、值类型与引用类型
开发语言·算法·golang
roman_日积跬步-终至千里3 小时前
【Go语言基础】基本语法
开发语言·golang·xcode
Felven3 小时前
C. Basketball Exercise
c语言·开发语言
可乐鸡翅好好吃3 小时前
通过BUG(prvIdleTask、pxTasksWaitingTerminatio不断跳转问题)了解空闲函数(prvIdleTask)和TCB
c语言·stm32·单片机·嵌入式硬件·bug·keil