unsigned int用法

2023-10-30

注意使用unsigned int(无符号常数)
正如我们所知道的,编程语句都有很多的基本数据类型,如char,inf,float等等,而在C和C++中还有一个特殊的类型就是无符号数,它由unsigned修饰,如unsigned int等。大家有没想过,就是因为这些不同的类型,而使大家编写的看似非常正确的程序出现了预想不到的错误呢?

一、迷惑人的有符号下无符号数的比较操作
废话不多说,马上来看一下例子,让你先来体验一下这个奇妙的旅程,源代码文件名为unsigned.c,源代码如下:

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
	int a = -1;
	unsigned int b = 1;
 
	if(a > b)
		printf("a > b, a = %d, b = %u\n", a, b);
	else
		printf("a <= b, a = %d, b = %u\n", a, b);
	exit(0);
}

输出结果为:

看到输出结果之后,你可能会大吃一惊,-1竟然大于1,你没有看错,从输出结果上来看的确是这样。为什么会产生这样的结果呢?这还得从C语言对同时包含有符号数和无符号数表达式的处理方式讲起。

二、有符号数与无符号运算时数强制类型转换方式及底层表示

当执行一个运算时(如这里的a>b),如果它的一个运算数是有符号的而另一个数是无符号的,那么C语言会隐式地将有符号 参数强制类型为无符号数,并假设这两个数都是非负的,来执行这个运算。这种方法对于标准的算术运算来说并无多大差异,但是对于像<和>这样的运算就可能产生非直观的结果。

所以对应回上面的例子,就是它先把-1(变量a的值)这个有符号数强制转换成无符号数,然后再与1(变量b)的值,来进行比较,并假设这两个数原本都是非负的,然后进行比较。那么-1转换为无符号数后,其值为多少呢?你可以写一个小小的程序来验证一下,在32和64位的机子上,-1对应的无符号数应该是4 294 967 295,即32位的无符号数的最大值(UMax),所以if中的条件总是为真。

要想这段代码正常执行,我们需要怎么办呢?很简单,把if语句改为if(a > (int)b)即可。这样程序就会认为是两个有符号数在进行比较,-1就不会隐式地转换为无符号数而变成UMax。

可能你已经有一个问题,为什么使用强制类型,把变量b的类型变成int程序就能正常,而-1转换成无符号数为什么会是4 294 967 295呢?这就得从整型数据在计算机中的表示和C语言对待强制类型转换的方式说起。

我们知道,整数在计算机中通常是以补码的形式存在的,而-1的补码(用4个字节储存)为1111,1111,1111,1111。而C语言对于强制类型转换是怎么处理的呢?对大多数C语言的实现,处理同样字长的有符号数和无符号数之间的相互转换的一般规则是:数值可能会改变,但是位模式不变。也就是说,将unsigned int强制类型转换成int,或将int转换成unsigned int底层的位表示保持不变。

也就是说,即使是-1转换成unsigned int之后,它在内存中的表示还是没有改变,即1111,1111,1111,1111。我们知道在计算机的底层,数据是没有类型可言的,所有的数据非0即1。数据类型只有在高层的应用程序才有意义,也就是说,同样的储存表示对于应用程序而言可能对应着不同的数据,例如1111,1111,1111,1111对于有符号数而言它表示-1,但对于无符号数而言,它表示UMax,但是它们的底层存储都是一样的。现在你应该明白为什么-1转换成无符号数之后,就成了UMax了吧。

三、查看数据的底层表示

为了证明上面所说的内容,请再看下面的代码,里面有个函数show_byte,它可以把从指针start开始的len个字节的值以16进制数的形式打印出来。源文件为showbyte.c,代码如下:

#include <stdio.h>
#include <stdlib.h>
 
void show_bytes(unsigned char *start, int len)
{
	int i = 0;
	for(; i < len; ++i)
		printf(" %.2x", start[i]);
	printf("\n");
}
 
int main()
{
	int a = -1;
	unsigned int b = 4294967295;
 
	printf("a = %d, a = %u\n", a, a);
	printf("b = %d, b = %u\n", b, b);
 
	show_bytes((unsigned char*)&a, sizeof(int));
	show_bytes((unsigned char*)&b, sizeof(unsigned int));
	exit(0);
}

输出为:

分析:printf函数中,%u表示以无符号数十进制的形式输出,%d表示以有符号十进制的形式输出。通过show_bytes函数,我们可以看到,-1与4 294 967 295的底层表示是一样的,它们的位全部都是全1,即每个字节表示为ff。

四、由于无符号数减法引起的错误

你可能会说,你不会用一个无符号数与一个有符号数作比较,所以你觉得你可以放心了,但是来看看下面的两段代码。
代码1是一个求数组中前length个数据的和的函数,数组中元素的个数由参数length给出,代码如下:

float sum_elements(float a[], unsigned length)
{
	int i = 0;
	float sum = 0;
	for(i = 0; i <= length -1; ++i)
		sum += a[i];
	return sum;
}

如果我告诉你这是一段有错的代码,可能你也不太相信,因为这个函数的一切看起来是这么的自然,因为数据的长度(或个数)肯定是一个非负数,所以把length声明为一个unsigned很合理,计算的数据个数和返回类型也正确。的确如此,但是这都是在length不为0的情况,试想,当调用函数时,把0作为参数传递给length会发生什么事情?回想一下前面我们所说的知识,因为length是unsigned类型,所以所有的运算都被隐式地被强制转换为unsigned类型,所以length-1(即0-1 = -1),-1对应的无符号类型的值为UMax,所以for循环将会循环UMax次,数组也会越界,发生错误。那么如何优化上面的代码呢?其实答案非常简单,你也可以自己想一想,这里就给出答案吧,就是把for循环改为:

for(i = 0; i < length; ++i)

因为去除了length-1,所以当length为0时也能正常比较。

接下来是代码2,它是一个判断第一个字符串是否长于第二个字符串,若是,返回1,若否返回0,代码如下:

int strlonger(char *s1, char *s2)
{
	return strlen(s1) - strlen(s2) > 0;
}

如果我又跟你说这段代码是有bug,你现在找不找得出来呢,还是认为这段代码是没有任何问题的呢?说真的就这么看这个函数好像的确是没有什么问题,但是如果你知道了strlen函数的原型,可能你就会有点明白了,在Linux下可用man 3 strlen命令查看,strlen函数的原型为:

size_t strlen(const char *s);

注意这里有一个数据类型size_t,它被定义在stdio.h文件中,其实它就是unsigned int,一个字符串的长度当然不可能为负,这样的定义显然是合理的,但是有时却因为这样,而存在不少的问题,如函数strlonger的实现。当s1的长度大于等于s2时,这个函数并没有什么问题,但是你可以想像,当s1的长度小于s2的长度时,这个函数会返回什么吗?没错,因为此时strlen(s1) - strlen(s2)为负(从数学的角度来解释的话),而又由于程序把它作为unsigned为处理,则此时的值肯定是一个比0大的值。换句话来说,这个函数只有在strlen(s1) == strlen(s2)时返回假,其他情况都返回真。

下面是我的测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int strlonger(char *s1, char *s2)
{
	return strlen(s1) - strlen(s2) > 0;
}
 
int main()
{
	char s1[] = "abc";
	char s2[] = "cd";
 
	if(strlonger(s1, s2))
		printf("s1 is longer than s2, s1 = %s, s2 = %s\n", s1, s2);
	else
		printf("s1 is shorter than s2, s1 = %s, s2 = %s\n", s1, s2);
 
	if(strlonger(s2, s1))
		printf("s2 is longer than s1, s2 = %s, s1 = %s\n", s2, s1);
	else
		printf("s2 is shorter than s1, s2 = %s, s1 = %s\n", s2, s1);
}

运行结果如下:

从运行结果来看,确实如此,只要s1与s2长度不等,就返回真。那么我们在怎么样改善这段代码呢?其实答案也是很简单的,所函数改为如下即可:

int strlonger(char *s1, char *s2)
{
	return strlen(s1) > strlen(s2);
}

这样就可以利用两个无符号数进行直接的比较,而不会因为减法而出现负数(数学上来说)而影响比较结果。

五、建议
这么看来,unsigned还真是一个危险的东西,大家还是要谨慎使用啊。其实个人建议,没有什么必要的原因,就不要使用unsigned,即使有时它看起来是那么的合理,因为有它在的运算,很多时候会产生非直观的错误,而且这种错误还非常难发现。如果你要使用的话,则尽量避免有符号数与无符号数的比较运算和避免减法运算,在很多时候,在unsigned的世界里,x-y>0与x>y都是不等价的。
————————————————
版权声明:本文为CSDN博主「ljianhui」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ljianhui/article/details/10367703

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

unsigned int用法 的相关文章

随机推荐

  • 向日葵远程控制端 for Mac

    向日葵远程控制端 for Mac是Mac平台上一款免费的远程桌面控制软件 向日葵远程控制软件免费版支持Mac OS X 10 10 3以上系统 相比客户端的向日葵远程软件 这款控制端功能更加齐全 向日葵客户端仅支持远程桌面 而向日葵控制端则
  • 虚拟主机也是虚拟服务器,虚拟主机和云主机之间的区别是什么

    其实在云计算不断发展的今天 虚拟主机和云主机之间的竞争 也越来越强烈了 此时不少人问虚拟主机和云主机之间的区别是什么 小编表示目前云主机在市场的应用中的优势越来越明显了 很多个人站长和企业们 也都是会使用云主机 因此大家就不妨看看虚拟主机和
  • MIPI CSI接口调试方法: data rate计算

    mipi DPHY 的规格书里对 data rate 有定义上下限值 最低 data rate为 80M bps 最高为 2500M bps 由于mipi 传输时是双采样 这样的话 实际的差分时钟的上下限就是 40MHz 1250 MHz
  • mac 安装adb工具

    1 安装homebrew 在终端输入下面的命令 ruby e curl fsSL https raw githubusercontent com Homebrew install master install 2 安装adb brew in
  • conda install & pip install区别 & 查看当前环境下包的情况

    conda可以方便开发管理python环境 包可以conda 安装有时使用pip整理一下不同点 conda 创建环境 conda create name a conda env python 3 6 名为a conda env python
  • SVG图标配置

    在开发项目的时候我们经常会用到svg矢量图 而且我们使用SVG以后 页面上加载的不再是图片资源 这对页面性能来说也是个很大的提升 项目背景 vue3 vite ts 安装依赖 npm install vite plugin svg icon
  • 202320读书笔记|《宋词》——竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生

    202320读书笔记 宋词 竹杖芒鞋轻胜马 谁怕 一蓑烟雨任平生 宋词 韩震主编 偶然从书友那加入书架的书 宋词挺喜欢李清照的词以及知否的 菩萨蛮 诗集 词 俳句 短歌我都很喜欢 是轻松有趣又简短的 这本书里有一些是上学时候背过的 看到的时
  • log4j2 入门学习与总结

    log4j2 2 3 入门学习与总结 大纲 log4j2 配置文件加载 log4j2 配置文件结构图 log4j2 配置文件说明 log4j2 Appenders child Filter ThresholdFilter 的使用 log4j
  • 【云原生之K8S】Yaml文件详解

    目录 一 K8S支持的文件格式 1 1 yaml和json的主要区别 二 YAML 2 1 查看API资源版本标签 2 2 编写资源配置清单 编写nginx test yaml资源配置清单 创建资源对象 查看创建的pod资源 2 3 创建s
  • C++中的适配器

    C primer中关于适配器的定义 适配器 adaptor 是使一种事物的行为类似于另外一事物的行为的一种机制 这个定义初学时觉得好抽象 为了理解C 中的适配器 不妨先了解物理上的适配器 物理上的适配器就是一个接口转换器 它可以是一个独立的
  • 我如何搞懂Javascript系列之原型和原型链

    理解原型 JavaScript 常被描述为一种基于原型的语言 每个对象拥有一个原型对象 对象以其原型为模板 从原型继承方法和属性 原型对象也可能拥有原型 并从中继承方法和属性 一层一层 以此类推 这种关系常被称为原型链 构造函数创建对象 J
  • JS常用方法

    1 删除数组中指定对象指定元素 let arr name xiaowang id 1 name xiaozhang id 2 createDate xiaoli id 3 删除id为1的对象 其中i为index 1可选择性填写 含义为删除当
  • 传递颜色

    由于颜色在片元着色器中 故不能用attribute 用uniform得到并传递 11
  • python中国古代数学问题——二鼠打洞

    任务描述 九章算术 的 盈不足篇 里有一个很有意思的老鼠打洞问题 原文是这么说的 今有垣厚十尺 两鼠对穿 大鼠日一尺 小鼠亦一尺 大鼠日自倍 小鼠日自半 问 何日相逢 各穿几何
  • 剑指 Offer 10- I. 斐波那契数列(java+python)

    写一个函数 输入 n 求斐波那契 Fibonacci 数列的第 n 项 即 F N 斐波那契数列的定义如下 F 0 0 F 1 1 F N F N 1 F N 2 其中 N gt 1 斐波那契数列由 0 和 1 开始 之后的斐波那契数就是由
  • C/C++语言 从日期格式字符串中提取年月日时分秒

    系列文章目录 文章目录 系列文章目录 前言 一 日期格式字符串 二 strftime函数 1 描述 2 声明 3 形参 4 返回值 三 strptime函数 1 形参 2 示例 3 函数封装 总结 前言 上一篇文章 C C 语言 获取系统时
  • MES管理系统如何帮助制造企业打造透明化工厂

    在制造型企业的运营中 车间现场管理至关重要 然而 面临着信息传递速度慢 跨部门协作困难 生产进度无法及时掌握 制造品质不良 设备故障不能及时处理等困境 企业需要寻求有效的解决方案 MES生产管理系统作为针对制造企业车间生产过程控制和管理的解
  • 如何在使用中文输入法的时候打出英文字符

    解决方法很简单 只需要按CTRL 就可以实现中英文字符切换 这样在按字母的时候还是会显示拼音 但输入字符时都是英文字符了 避免了频繁的shift切换
  • 【Qt学习】07:绘图与绘图设备

    OVERVIEW 绘图与绘图设备 一 QPainter 二 QPainterDevice 1 QPixmap 2 QBitmap 3 QImage 4 QPicture 绘图与绘图设备 一 QPainter Qt 的绘图系统允许使用API在
  • unsigned int用法

    注意使用unsigned int 无符号常数 正如我们所知道的 编程语句都有很多的基本数据类型 如char inf float等等 而在C和C 中还有一个特殊的类型就是无符号数 它由unsigned修饰 如unsigned int等 大家有