续篇:深入理解char的存储和字节序问题

引言:在上篇文章中我们讲解了有关数据在内存中存储的相关内容,并初步了解了数据在内存中存储的大小端问题。我们知道c语言中大多数据类型有着明确的定义,例如整型分为有符号整型和无符号整型,但是唯独char类型是个个例,有人会说char类型是可以打印出负数的,它是有符号类型的。事实上,C99标准并未规定char类型是有符号的还是无符号的,这取决于编译器和硬件平台,就VS2022而言,char类型被默认为是有符号类型的,具体类型可通过查看头文件limits.h得知,若是CHAR_MIN的值为0,char则为无符号类型,若是SCHAR_MIN的(-128),char则是有符号型的,本篇将通过几道例题带领大家深入理解char类型和大小端字节序相关问题。(以VS2022为例)

 

一.对于signed char和unsigned char取值范围

在介绍例题之前,首先要先为大家介绍一下signed char和unsigned char取值范围是多少以及是如何计算出来的,有助于后续问题的理解。

注意:对于负数而言,内存中存储的补码一定要转换为原码再计算值。

同理对于unsigned char:

通过上面两张图我们得到了signed char和unsigned char取值范围如下

signed char:-128~127

unsigned char:0~255

为了方便记忆,可以用时钟图辅助,脑海有了这样一幅图我们便能快速计算出目标值:

 

eg1:

#include <stdio.h>
int main()
{
	//x86平台
	char a = -1;//有符号类型
	//10000000000000000000000000000001  ——原码
	//11111111111111111111111111111110  ——反码
	//11111111111111111111111111111111  ——补码
	//但char类型只能存储一个字节即8个bit,所以会发生“截断”
	//11111111(只取补码的后8位)
	//最终以%d的形式打印,所以需要进行位数提升,对于有符号类型,前面需要用符号位补齐
	//11111111111111111111111111111111  ——提升后补码
	//10000000000000000000000000000001  ——最终需打印出的原码 -1
	signed char b = -1;
	//与char同理
	unsigned char c = -1;
	//1000000000000000000000000000001  ——原码
	//1111111111111111111111111111111  ——补码
	//11111111  ——截断
	//对于unsigned char来说,提升的时候前面用0补齐
	//0000000000000000000000011111111  ——提升后的补码
	//正数的原码,反码,补码相同所以最终打印的结果就是:
	//0000000000000000000000011111111  —— 255(符号位为0)
	printf("a=%d,b=%d,c=%d", a, b, c);
	return 0;
}

结果如何呢?

a=-1,b=-1,c=255

上文我们已经知道在vs2022中 char类型被认为是有符号类型的,所以对于char和signed的char输出结果是一致的。这里要着重强调一下unsigned char类型,有人会说unsigned类型怎么能存储负数呢?实际上,数据在内存中存储的时候该怎么存就怎么存,而数据对应的类型实际上是决定内存是如何看待数据的,根据不同类型对存进去的数据进行截断后才是最终存进去的内容,所以不要先入为主地纠结于类型对应的范围。

小小总结一下char类型究竟是如何计算的:1.首先根据具体数值写出原码,计算补码(负整数才存在原,反补的差别,正整数三者均相同)。2.根据数据对应的类型发生截断。3.根据打印所需类型发生自动类型转换,例如从char到int需要进行提升,若是signed char,高位补符号位,若是unsigned char,高位补0。4.二进制转换。

eg2:(1):

#include <stdio.h>
int main()
{
	char a = -128;
	//10000000000000000000000010000000 ——原码
	//11111111111111111111111101111111
	//11111111111111111111111110000000 ——补码
	//10000000 ——a(截断)

	printf("%u\n", a);//%u 无符号10进制整数
	//%u的形式打印,是认为a中存放的是无符号数
    //a是char类型,首先要整型提升(因为是char,所以高位补1)
	//11111111111111111111111110000000 ——a
	//又因为%u认为它a为无符号整数,所以不存在原反补,最高位1也不是符号位了
	//11111111111111111111111110000000 ——a:4294967168
	return 0;
}

(2): 

#include <stdio.h>
int main()
{
	char b = 128;
	//00000000000000000000000010000000 ——原码
	//01111111111111111111111101111111 ——反码
	//01111111111111111111111110000000 ——补码
	//10000000 ——b
	printf("%u\n", b);
	//00000000000000000000000010000000 ——整型提升(高位补符号位0)
	//对于%u,原反补相同
	//00000000000000000000000010000000 ——b:4294967168
	return 0;
}

 这两个的结果如何呢?

a=4294967168

b=4294967168

eg3:(1):

#include<stdio.h>
int main()
{
	char a[1000] = { 0 };
	int i;
	for (i = 0;i < 1000;i++)
	{
		a[i] = -1 - i;
		//-1 -2 -3...-127,-128,127,126...3,2,1,0,-1,-2......
	}
	printf("%d", strlen(a));
	//strlen在遇到'\0'的时候停下来,返回'\0'之前的字符数
	//即:-1 -2 -3...-127,-128,127,126...3,2,1的个数
	//255
	return 0;
}

eg4:

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

 

 结果会一直循环打印hello world,这是因为unsigned char 取值范围是0~255,当i=255时再加1就会变成0,结果会陷入死循环。

(2):

#include <stdio.h>
#include<windows.h>
int main()
{
	unsigned int i;//i恒>=0
	for (i = 9; i >= 0; i--)
	{
		//死循环
		//9,8,7,6,5,4,3,2,1,0,一个非常大的正整数.....
		printf("%u\n", i);
		Sleep(1000);
	}
	return 0;
}

eg5:

#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);
	return 0;
}

结果如下:

4,2000000

让我们详细分析一下这个代码:

注意:指针+1是根据指针类型的不同跳过不同的字节;而整型+1就是单纯+1,头脑一定要清醒。

以上便是延续上篇的例题内容,创作不易,望大家多多关注,感谢。

  • 27
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值