【C语言进阶学习笔记】数据在内存中的存储

一、数据类型介绍

前面我们已经学习了基本的内置类型以及他们所占存储空间的大小。

数据类型分类数据类型分类
char字符数据类型
short短整型
int整型
long长整型
long long更长的整型
float单精度浮点型
double双精度浮点型

类型的意义:

  1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
  2. 如何看待内存空间的视角

二、类型的基本归类

整型家族

整型家族整型家族
charunsigned char
signed char
shortunsigned short[int]
signed short[int]
intunsigned int
signed int
longunsigned long[int]
signed long[int]

浮点型家族

浮点数家族
float
double

构造类型

也称自定义类型

构造类型构造类型
数组类型
结构体类型struct
枚举类型enum
联合类型union
int main()
{
	int arr[10];//int [10]
	int arr2[5];//int [5]
	return 0;
}

arr数组的类型是int[10],arr2数组的类型是int[5],同样是整型数组,但两个类型不一样,数组有几个元素可以自己定义,数组类型是什么也可以自己定义。

指针类型

指针类型
int *pi
char *pc
float *pf
void *pv

空类型

void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。

三、整型在内存中的存储:原码、反码、补码

一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。
那接下来我们谈谈数据在所开辟内存中到底是如何存储的?

我们先来了解一下下面的概念:原码、反码和补码

数据在内存中以二进制的形式存储。
计算机中的有符号数有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。
原码:直接将二进制按照正负数的形式翻译成二进制就可以。
反码:将原码的符号位不变,其他位依次按位取反就可以得到了。
补码:反码+1就得到补码。
正数的原、反、补码都相同。
对于整型来说:数据存放内存中其实存放的是补码。

为什么整数在内存中存储的是补码而不是原码?

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

下面我们用一个例子来验证一下。

int main()
{
    int a = -10;
    return 0;
}

运行结果如下:
在这里插入图片描述我们来分析一下为什么-10在内存中是这样存储的。-10的原码、反码和补码如下。

原码:10000000000000000000000000001010
反码:11111111111111111111111111110101
补码:11111111111111111111111111110110
FFFFFFF6

我们可以发现数据在内存中是以补码的形式存储的。

我们再来看一个例子。以1-1为例。
CPU上没有减法器,只有加法器,用加法来模拟减法。

1-1
1+(-1)
1的原码: 00000000000000000000000000000001
-1的原码:10000000000000000000000000000001
原码相加:10000000000000000000000000000010
原码直接相加是-2
1的补码: 00000000000000000000000000000001
-1的原码:10000000000000000000000000000001
-1的反码:11111111111111111111111111111110
-1的补码:11111111111111111111111111111111
补码相加:100000000000000000000000000000000
多了一位,只能存32位,1被舍弃,最后结果是0

这个例子也可以看出整数在内存中存储的是补码。

四、大小端字节序

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

为什么会有大小端模式之分呢?

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

在这里插入图片描述

0x11是高位,0x44是低位,地址由低到高增加,0x44存放在低地址,0x11存放在高地址,所以当前编译器是小端字节序存储模式。

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

在这里插入图片描述

最简单的方法就是以1为例。1的大小端存储不同,我们只需要比较第一个字节,就可以判断是大端还是小端。

int main()
{
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

运行结果如下:
在这里插入图片描述

可知当前的编译器是小端存储模式。

我们也可以把这个判断程序写成一个函数。

int check_sys()
{
	int a = 1;
	char* p = (char*)&a;//int*
	if (*p == 1)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int main()
{
	//写代码判断当前机器的字节序
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}

	return 0;
}

我们可以再修改一下程序。

int check_sys()
{
	int a = 1;
	char* p = (char*)&a;//int*
	return *p;//返回1表示小端,返回0表示大端
}
int main()
{
	//写代码判断当前机器的字节序
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}

	return 0;
}

五、练习题

1、

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;
}

运行结果如下:
在这里插入图片描述
分析:

原码:10000000000000000000000000000001
反码:11111111111111111111111111111110
补码:11111111111111111111111111111111
存到a中的数为11111111
存到b中的数为11111111
存到c中的数为11111111

以整型的形式打印,对内存中的补码要发生整型提升,打印出来的是原码,需要把整型提升后的补码转换成原码。有符号的数整型提升高位补1,无符号数整型提升高位补0。看以什么形式打印,判断高位01是不是符号位,如果是符号位,补码高位是0,证明是正数,原反补相同。补码高位是1,证明是负数,求出原码。

a整型提升后:11111111111111111111111111111111
转换成原码:10000000000000000000000000000001
b整型提升后:11111111111111111111111111111111
转换成原码: 10000000000000000000000000000001
c整型提升后:00000000000000000000000011111111
转换成原码: 00000000000000000000000011111111

注意:

char 到底是signed char 还是unsigned char ,C语言标准并没有规定,取决于编译器
int 是 signed int ,short 是 signed short

2、

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

运行结果如下:
在这里插入图片描述
分析:

原码:10000000000000000000000010000000
反码:11111111111111111111111101111111
补码:11111111111111111111111110000000
a里面存放的是:10000000
char是有符号的,整型提升为:11111111111111111111111110000000
以无符号整型打印,补码最高位的1不是符号位,所以它是个正数,原码跟补码一样

3、

int main()
{
    char a = 128;
    printf("%u\n", a);
    return 0;
}

运行结果如下:
在这里插入图片描述

内存中存放的char类型的变量所有的可能性 。

在这里插入图片描述

上面是正数,下面是负数,存在内存中的是补码,正数原反补相同,下面的负数转换成原码如上图所示,10000000不能在减1了,10000000直接被解析成-128,我们也可以推一下,如上图所示。有符号的char的取值范围是:-128-127。

在这里插入图片描述
4、

int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i+j);
	return 0;
}

运行结果如下:
在这里插入图片描述
分析:

-20
原码:10000000000000000000000000010100
反码:11111111111111111111111111101011
补码:11111111111111111111111111101100
10
原码:00000000000000000000000000001010
反码:00000000000000000000000000001010
补码:00000000000000000000000000001010
两补码相加:11111111111111111111111111110110
相加后的反码:11111111111111111111111111110101
相加后的原码:10000000000000000000000000001010

5、

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}

运行结果如下:
死循环
在这里插入图片描述

i是unsigned int 类型,无符号整型大于等于0,i>=0条件恒成立,所以会死循环。

6、

int main()
{
    char a[1000];
    int i;
    for (i = 0; i < 1000; i++)
    {
        a[i] = -1 - i;
    }
    printf("%d\n", strlen(a));//找到\0 -> 0
    return 0;
}

运行结果如下:
在这里插入图片描述

-1 -2 -3 …-127 -128 127 126 125…3 2 1 0 -1 -2 …-127 -128 127
求strlen(a)找\0,就是找0
128+127=255

有符号的char范围为-128-127,无符号的char范围为0-255

7、

unsigned char i = 0;
int main()
{
    for (i = 0; i <= 255; i++)
    {
        printf("hello world\n");
    }
    return 0;
}

运行结果如下:
死循环
在这里插入图片描述

无符号的char取值范围是0-255,判断条件恒成立,所以会死循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值