怎么理解c语言中的无符号整数(C语言的无符号类型)

我们学习了有符号和无符号数,可以知道,在一个字节中存放了8位的数据,例如1111 1111这样的8位二进制数据,就有:

(1) 如果把这8位二进制数据当作“有符号”数看待,那么,最高位是符号位,其余7位是数值位;

(2) 如果把这8位二进制数据当作“无符号”数看待,那么,没有符号位,8位数据都是数值位;

可以看到,同一个二进制数据,当中有符号和无符号数看待的时候,它们表示的结果是不一样的。

在C语言中,提供了unsigned这个关键字来表示无符号数。例如:

unsigned char x;

就是定义了x这个变量是无符号类型,所以它表示的数值没有符号位。例如存放1111 1111数据,因为无符号数没有符号位,全部8位数据是数值位,所以表示255这个数值。表示的最小数据就是0000 0000,既0 数值。如下是一个测试的例子。

程序运行的结果如下:

可以看到,对于255这个整数,其二进制表示为1111 1111,那么,就有:

(1) 把255赋给a1变量,当作有符号数看待,所以,符号位是1,表示负数,数值位是111 1111,那么,要表示为十进制,就是数值位取反,得到000 0000,然后,加1,得到000 0001,所以,二进制1111 1111表示为有符号数是-1。所以与输出的a1变量值是-1一致。

(2) 把255赋给a2变量,当作无符号数看待,所以,8位二进制数据1111 1111全部存储到a2变量中当作数值位,所以,a2变量输出是255。

对于a3变量的操作,我们是把一个负数赋值给无符号数a3,那么,由于-1这个数值其在1个字节中表示的二进制数为1111 1111,所以,就相当于把1111 1111数值赋给a3变量,那么,a3变量中就有1111 1111这样的数据,与a2变量存储的数据是一样的。所以,输出的数值与a2变量一样。

对于char、short、int、long 类型,可以使用unsigned关键字来修饰,定义无符号类型的数据。但是,对于 float、double类型的数据,由于是浮点数,所以,不可以使用unsigned关键字来修饰。

,

c语言32位整数补码(无符号整数编码和有符号整数补码的表示)

C语言支持多种整型数据类型来表示有限范围的整数。每种类型都能用关键字来指定大小,这些关键字包括char、short、long,同时还可以指示被表示的数字是非负数(声明为unsigned)、或者可能是负数(默认)。另外,为这些不同的大小分配的字节数根据程序编译为 32 位还是 64 位而有所不同。

long的大小与int一样大,属于历史遗留问题,历史上的int在16位平台时代是2个字节,long是4个字节,所以long要long,等到32位平台将int提升到32位一个字长时,int和long的长度就一样。

不同word size对应的最值的16进制编码(其中的U表示Unsigned,T表示Two's comlement(补码)):

注意上面的16进制与二进制的对应关系:

7:0111

8:1000

F:1111

可以看出有以下数量关系:

1 无符号数编码

假设有一个整数数据类型,有位(word size or width)。将其写成向量,将其视为二进制表示的无符号数,则每一位取值范围为0或1。

用函数(Binary to Unsigned的缩写,长度为)表示为:

函数将一个长度为w 的0、1 串映射到非负整数。

例:

可以用长度为的指向右侧箭头的条表示每个位的位置 i。每个位向量对应的数值就等于所有值为 1 的位对应的条的长度之和。

w位二进制所能表示的无符号整数的范围:

2 有符号整型数据的补码编码

目前,最常见的计算机有符号整型数的计算机表示方式为补码形式。在补码中,将字的最高有效位解释为负权(negative weight)。这里通过函数 B2T_w(Binary to Two's-complement的缩写)来表示:

例:

最高有效位也称为符号位,它的“权重”为,是无符号表示中权重的负数。符号位被设置为1 时,表示值为负,而当设置为0 时,值为非负。

补码 它的负数 = 模;

补码和它的负数的二进制位只有最低位是相同的,其它位都是相补:

补码 && 它的负数 = 1;

这也是经常有以下表述的来由:

对于负数,补码等于反码 1。

补码使用最高位表示符号,但与无符号整数编码表示的总体值域的个数是一致的:。

我们用向左指的条表示符号位具有负权重。于是,与一个位向量相关联的数值是由可能的向左指的条和向右指的条加起来决定的。

最高位以外的其它位相对于最高位而言,是一个此涨彼消的状态。

w位二进制所能表示的有符号整数的范围:

负整数的范围比正整数的范围大1,多出来的数就是100…000(二进制序列,最小的负数),该数取反加1等于模,截断后就是0了。

3 有符号整数与无符号整数之间的转换

C 语言允许在各种不同的数字数据类型之间做强制类型转换。遵循底层的位表示不变,而按不同类型的编码规则进行解释的原则。

如果是相关类型的隐式转换,C语言设置了一系列的转换规则。

如果是不相关类型的强制转换,C语言会在位级层面按编码规则做重新解释(解码)。

例如,假设变量x 声明为int,u 声明为unsigned,表达式(unsigned)x 会将x 的值转换成一个无符号数值,而(int)u 将u 的值转换成一个有符号整数。将有符号数强制类型转换成无符号数。另外,一个表达式中,如果同时存在无符号整数与有符号整数,计算时,有符号整数会隐式转换为无符号整数。

对于有符号整数的正整数部分,与无符号整数是一致的。

对于对于有符号整数的负整数部分,与无符号整数只有最高位的解释不同,其它位是一致的。

所以有以下数量关系:

负整数x→U = x

u→负整数x = U –

由于C 语言对同时包含有符号和无符号整型数表达式的隐式转换,会出现一些奇特的行为。当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C语言会隐式地将有符号整数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。就像我们将要看到的,这种方法对于标准的算术运算来说并无多大差异,但是对于像<和>这样的关系运算符来说,它会导致非直观的结果。

有符号数到无符号数的隐式强制类型转换导致了某些非直观的行为。而这些非直观的特性经常导致程序错误,并且这种包含隐式强制类型转换的细微差别的错误很难被发现。

4 扩展一个整型数字的位表示

一个常见的运算是在不同字长的整数之间转换,同时又保持数值不变。

对于有符号整数和无符号整数,两者在扩展时,高位的符号扩展有所区别:

short sx = val; /* -12345 */
unsigned short usx = sx; /* 53191 */
int x = sx; /* -12345 */
unsigned ux = usx; /* 53191 */

在采用补码表示的32 位大端法机器上的内存布局:

有符号整型使用1来做符号扩展。

无符号整型使用0来做符号扩展。

有符号整数在做右移运算时,也会存在符号扩展的问题。

5 截断数字

假设我们不用额外的位来扩展一个数值,而是减少表示一个数字的位数。例如下面代码中这种情况:

int x = 53191;
short sx = (short) x; /* -12345 */
int y = sx; /* -12345 */

当我们把x 强制类型转换为short 时,我们就将 32 位的int 截断为了 16 位的short int。

当将一个w位的整型数截断为一个k位整型数字时,我们会丢弃高w-k位。

无符号整数取模:

有符号整数的补码取模后要做转换 :

6 整数运算与溢出

C语言的整型数字是一个有限字长的表示,当在计算时存在溢出时,会做模运算处理。

当两个w位的无符号整型相加时,其结果可能是一个w 1位的无符号数。

当两个w位的无符号整型相加时其和等于或超过时,就会发生溢出,其结果为和与的模运算:

补码加法存在正溢出与负溢出:

首先要明白,一个正数和一个负数相加,结果一定不会溢出(因为结果的绝对值一定小于两个加数的绝对值,两个加数都表达出来了,结果一定能表达出来。)

所以,发生溢出的情况一定是符号相同的两个数相加。

分情况讨论:

① 正整数pa 正整数pb = r

符号位0,数位相加,如果结果的符号位变成1了,那一定是两个加数的最高位相加进上来的。

假设将一个长度 w=4 的0、1 串映射到有符号整数,其最大的正数只能是0111,也就是7。

0111 0001 = 1000 // 7 1 = 8-2^4 = -8,向下溢出

0111 0011 = 1010 // 7 3 = 10-2^4 = -6,向下溢出

发生向下溢出,判断的方式之一是:r<pa || r<pb。

② 负整数na 负整数nb = r

符号位都是1,所以符号位一定会进位。数位相加,如果最后符号位是0,说明结果变成正的了,那一定是发生溢出了(负 负!=正)。

假设将一个长度 w=4 的0、1 串映射到有符号整数,其最小的负数只能是1000,也就是-8。

1000 1111 = 11000 ->0110 // -8 (-1) = -9 2^4 = 7,向上溢出

1011 1011 = 10110 ->0110 // -5 (-5) = -10 2^4 = 6,向上溢出

另外一种情况,没有改变符号位的溢出,属正常溢出,正如有符号位扩展、算术移位一样:

1100 1100 = 10000 ->0000 // -4 (-4)= -8, 正常溢出

1111 1111 = 11110 ->1110 // -1 (-1)=-2, 正常溢出

发生向上溢出,判断的方式之一是:r<pa || r<pb。

CPU的标志寄存器有一个溢出标志位,反映有符号数加减运算是否溢出。如果运算结果超过了8位或者16位有符号数的表示范围,则OF置1,否则置0。

7 整数的乘除

① 两整数乘除的结果还是一个整数类型(类型一致),如果是除法,可能存在舍入(舍入到零,向上舍入,向下舍入)的情况;

② 两整数乘法要考虑溢出的情况。

③ 位运算也是一种特殊的整数乘除,乘数与除数是2的不同的整数幂(当一个整数乘除一个常数时,这个常数如果不是某个整数次幂,可以拆解成不同的幂次的加减)。

8 代码示例

8.1 带符号数产生意外结果的例子。这个例子会造成无限循环,因为sizeof会返回unsigned int 类型,由此带来的结果是,i – sizeof(char)这个表达式的求值结果将会是 unsigned int (隐式转换 !!),i 会隐式转换为unsigned,i从 0 减 1 后变成-1,其二进制编码是 0xFFFFFFFF,转换成无符号数是2^32-1,从而产生无限循环,有时候你需要特别留心这种不经意的错误 !

int n = 10, i;
for (i = n - 1 ; i - sizeof(char) >= 0; i--)
printf("i: 0x%x\n",i);

if (-1 > 0U) // -1的二进制编码是0xFFFFFFFF,转变成无符号数是2^32-1
printf("You Surprised me!\n");

8.2 以下是2002年的freeBSD内核的部分代码,其中包含了漏洞,假设恶意人员将负值作为maxlen传入这个函数,有发生什么情况?

以下size_t的类型是typedef unsigned int size_t的类型定义:

#define KSIZE 1024
char kbuf[KSIZE];
/* Copy at most maxlen bytes from kernel region to user buffer */
int copy_from_kernel(void *user_dest, int maxlen) {
int len = KSIZE < maxlen ? KSIZE : maxlen;
memcpy(user_dest, kbuf, len);
return len;
}

/* Declaration of library function memcpy */
void *memcpy(void *dest, void *src, size_t n);

/* Malicious Usage */
void getstuff() {
char mybuf[MSIZE];
copy_from_kernel(mybuf, -512); // -512转变成无符号数后是2^31 512
}

8.3 给定一个有序的整型数组,请编程实现二分查找算法。

高德纳在《计算机程序设计的艺术》指出,虽然早在1946年就有人将二分查找的方法公诸于世,但直到1962年才有人写出没有bug的二分查找程序,可见,写一个安全的代码并不容易,你是不是一不小心就写出像下面这样的二分查找代码了?

int binary_search(int a[], int len, int key){
int low = 0;
int high = len - 1;

while ( low <= high ) {
int mid = (low high)/2; // 提示:这里有溢出Bug!
if (a[mid] == key) {
return mid;
}
if (key < a[mid]) {
high = mid - 1;
}else{
low = mid 1;
}
}
return -1;
}

ref

Randal E. Bryant, David R. O’Hallaron《Computer Systems:A Programmers Perspective》

-End-

,

怎么听懂狗狗的语言(怎么听懂狗狗的语言)

很多人在养狗之前做了很多功课,什么生活习惯,饮食,训练等,却独独少了怎么去读懂狗狗的身体语言。虽然狗狗不会说人话,但它们却一直在用自己的方式跟我们进行沟通,你真的听懂它们要表达的东西了吗?现在就让我们来看看狗狗都有哪些身体语言。怎么听懂狗狗的语言(怎么听懂狗狗的语言)如何听懂毛孩子说话——狗狗身体语言解析一、叼玩具给你养狗狗的你是否经常看到一个现象,当你回到家时,你的狗狗第一时间不是奔向你,而是慌里慌张地跑回房间叼着它的玩具放到你面前。不用觉得奇怪,也不用担心是不是你的狗狗眼里只有玩具。这只是一种它们向你表达爱意的方式——把自己最好的东西给你,欢迎你回家。这时候你可以选择抚摸它,或者陪它一起玩耍,它就能因为得到你的回应而心满意足。怎么听懂狗狗的语言(怎么听懂狗狗的语言)二、用爪子扒拉你有时候当你在家里玩手机或看电视,你会发现你的狗狗很喜欢过来时不时扒拉你。注意,这时候不要不理它或者觉得它烦,而是要学会认真倾听它的心声。一般这种时候,狗狗一般会表达几种意思,你可以看情况而定,可能是想让你带它出去玩了,会时不时看向大门;也可能是它太无聊了,想要你陪它玩一会儿;还有一种可能是你忘了给它狗粮,觉得饿了跟你讨要东西吃。怎么听懂狗狗的语言(怎么听懂狗狗的语言)三、原地不停转圈圈狗狗喜欢原地转圈圈,其中比较少人知道的是,狗狗此时感到有点尴尬了。就像人尴尬时会摸摸脖子搓搓手那样,到了狗狗这里就是转圈圈。这种情况一般是出现在它非常调皮被你叫名字警告的时候,它犯错不小心被你发现了,为了掩饰自己,就会尴尬地转圈圈,这种常发于厚脸皮的狗狗身上。怎么听懂狗狗的语言(怎么听懂狗狗的语言)四、坐姿端正盯着你这种情况还是很常见的,我猜你一般遇到狗狗坐姿笔直看着你的时候,都是你在吃饭对不对?这个意思就非常明显了,狗狗想要一起享用你手中的美食。如果你手中的食物恰好是适合狗狗吃的,那你可以适当地给它吃一些,如果是不可以给它吃的,你记得就要背着它吃哦!不然它就会一直盯着你,直到你觉得不好意思。不过建议狗狗平时还是以狗粮为主,一般来讲狗粮中含有的多种元素已经足以补充狗狗基础所需。就像“馋不腻狗粮”,以鸡肉鸭肉鱼肉等作为蛋白质的主要 源,再添加胡萝卜南瓜等蔬菜粗纤维帮助肠胃蠕动,可以满足狗狗日常营养所需。怎么听懂狗狗的语言(怎么听懂狗狗的语言)五、耳朵向后倒这个现象一般会出现在狗狗被主人大声训斥的时候,如果你再仔细一点去观察,你会发现它同时还会带着委屈的小眼神看着你。这是因为狗狗犯错了因责备而感到害怕恐惧的表现,它是在跟你说“我错了”,不过它下次还敢。有些狗狗犯错后不仅耳朵往后仰,表情可怜,还会不停扒拉你,这是它们求饶的一种表现。建议在狗狗犯错的时候可以纠正它但不要太过凶它,当它知错能改的时候也可以适时奖励它一些小零食,如“馋不腻羊奶酪”,含有丰富的钙磷,在训练的时候还可以帮助磨牙。怎么听懂狗狗的语言(怎么听懂狗狗的语言)结语:你还知道哪些关于狗狗的身体语言呢?怎么听懂狗狗的语言(怎么听懂狗狗的语言)

本文Hash:8e82648d309a85b0684978115ef9a8c272305c04

声明:此文由 nihao 分享发布,并不意味 赞同其观点。文章内容仅供参考,此文如侵犯到您的合法权益,请联系我们。

Baidu
map