搜索
bottom↓
回复: 40

GCC处理unsigned char类型需要当心的一个问题

[复制链接]

出0入0汤圆

发表于 2007-8-29 10:36:36 | 显示全部楼层 |阅读模式
最近移植一个ICCAVR工程时发现的,2个编译器间的C标准方面的差异,先看下面代码:



unsigned char flashvar[2]={ 1, 2};

unsigned char gvar1=255;



int main(void)

{

   unsigned char tmp1=3;

   

   gvar1=flashvar[1]|((~tmp1)>>flashvar[1]);

   tmp1=flashvar[1]+6;



   while(1);



   return 1;

}



这段代码中(~tmp1)的结果在2个编译器中有不同解释:

ICCAVR:取反结果就是0xFC;((~tmp1)>>flashvar[1])结果是0x3F;

GCC:取反结果被扩展为0xFFFC;((~tmp1)>>flashvar[1])结果是0xFFFF(即对16位进行了移位操作,高8位ASR,低8位ROR,而不是8位移位),最终gvar1值为0xFF(和优化等级无关);



原因:

C89、C99标准中,按位操作的操作符(包括了 ~、&、^、|、<<、>>)需要整型作为操作数,所以按这些标准,字符型将被扩展为int类型作运算。



解决方法:

(((unsigned char)(~tmp1))>>flashvar[1]);

通过强制转换,把取反结果的高8位舍弃,然后移位操作中会重新扩展,此时被扩展为0x00FC,即高8位为0,再移位,结果gvar1为0x3F。



这可以算是GCC对标准支持太好后的“副作用”吧:),和我以前想象的操作方式不一样了。

附件为ISO C99规范(英文版),第79页和第91页都提到了,供参考。

另外感谢avrfreaks的lfmorrison的回答 http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=53586



点击此处打开 StandardC99.pdf

出0入0汤圆

发表于 2007-8-29 10:44:56 | 显示全部楼层
很多问题多会出现在不规范的地方。。。

出0入0汤圆

发表于 2007-8-29 11:31:32 | 显示全部楼层
这个是默认的规定了,这个问题也害我走了不少弯路,白花了好多时间查bug,说来也是自己的C语言基础不够扎实呀

出0入0汤圆

发表于 2007-8-29 18:30:01 | 显示全部楼层
楼主的帖子解开我一个一年多的疑问!去年做的一个程序中用到两个unsigned char数反码校验,用~运算符取反,执行结果不对,后来只能采用^后检查结果=0xff来判断是否互为反码!当时百思不得其解,现在楼主的帖子终于解答了这个疑问了。

出0入0汤圆

发表于 2007-8-29 18:47:18 | 显示全部楼层
这种问题是最难找的!!!

LZ厉害啊!!!

出0入0汤圆

发表于 2007-8-29 18:51:13 | 显示全部楼层
啊,太可怕了,我的程序中经常使用这几个操作符,对unsigned char 型数据进行运算。那以后多麻烦啊。

出0入0汤圆

发表于 2007-8-29 19:07:33 | 显示全部楼层
这个帖应该顶起来,让更多人看到 谢谢搂主,我也犯过这种错误,可是没有深究为什么错,唉 高手和平庸之辈的区别呀

出0入0汤圆

发表于 2007-8-29 19:11:24 | 显示全部楼层
啊,刚才我实验了一下,确实如此。请阿莫把此贴置顶。

出0入0汤圆

发表于 2007-8-29 19:47:08 | 显示全部楼层
这类问题一般都是要进入汇编调试才能发现的。嵌入式开发人员应当习惯看编译后的包含汇编的LST文件。

看来,我也要花点时间去读读C99标准了。

出0入0汤圆

发表于 2007-8-29 20:26:25 | 显示全部楼层
好貼頂起!!!

出0入0汤圆

发表于 2007-8-29 20:37:17 | 显示全部楼层
刚好也遇到了这样的问题,楼主果然是个认真的人

出0入4汤圆

发表于 2007-8-31 09:38:20 | 显示全部楼层
这种问题多能找到,超强

出0入0汤圆

发表于 2007-9-1 10:04:21 | 显示全部楼层
这种问题是最难找的!!!

我也碰到过!1!

出0入0汤圆

发表于 2007-9-1 10:12:39 | 显示全部楼层

出0入0汤圆

发表于 2007-9-1 13:58:18 | 显示全部楼层
谢谢楼主,已经遇到这个问题了,还以为程序哪里出错了。

出0入8汤圆

发表于 2007-9-7 22:59:14 | 显示全部楼层
hao tie

出0入0汤圆

发表于 2007-9-9 15:06:33 | 显示全部楼层
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddd                    dddddddddddddddd                          dddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
ddddddddddddddd   dddddddddddddddddddddddddddddddddddddd   ddddddddddddddddddddddddd
ddddddddddddddd   ddddddddddddddddddddddddddddddddddddd   dddddddddddddddddddddddddd
ddddddddddddddd   dddddddddddddddddddddddddddddddddddd   ddddddddddddddddddddddddddd
ddddddddddddddd   ddddddddddddddddddddddddddddddddddd   dddddddddddddddddddddddddddd
ddddddddddddddd   dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd                              dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddddddddddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddddddddddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddddddddddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   ddddddddddd   dddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddddd   ddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   ddddddddd   dddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddd      dddddddddd   dddddddddddd
dddddddd dddddd   dddddddddddddddddddddddd   ddddddd   dd   ddddddddd   dddddddddddd
ddddddddd   ddd   dddddddddddddddddddddddd   dddddd   dddd   dddddddd   dddddddddddd
dddddddddd   dd   dddddddddddddddddddddddddddddddd   dddddd    ddddddddddddddddddddd
dddddddddddd      ddddddddddddddddddddddddddddddd   dddddddd   ddddddddddddddddddddd
dddddddddddddd    dddddddddddddddddddddddddddddd   dddddddddd   dddddddddddddddddddd
ddddddddddddddddddddddddddddddddddddddddddddddd   dddddddddddd   ddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddd   dddddddddddddd   dddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddd   dddddddddddddddddd   dddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddd    dddddddddddddddddddddd   ddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd

出0入0汤圆

发表于 2007-9-12 20:33:31 | 显示全部楼层
恩,这个问题大家应当注意,

出0入0汤圆

发表于 2007-12-28 17:07:48 | 显示全部楼层
谢谢楼主

出0入0汤圆

发表于 2007-12-28 23:33:05 | 显示全部楼层
强悍的帖子啊,顶

出0入0汤圆

发表于 2007-12-29 00:50:56 | 显示全部楼层
IAR上也有同样问题,而且做得更绝。如果来句 if ( a == ~b),a,b均为unsigned char,它干脆就不编译这句,当if(0)处理。我开始百思不得其解,加了个中间变量临时解决。后来把同样代码弄到GCC上,发现虽然GCC有编译这句,但总是判断有误。我查看其编译汇编代码,发现取反按16位处理。可惜没早看到这贴,走了不少弯路。

出0入0汤圆

发表于 2007-12-29 00:56:43 | 显示全部楼层
原来如此,我说以前有段数值的处理总是有问题,最后全部强制转换了才好,原来是样子的。

出0入0汤圆

发表于 2007-12-29 03:17:26 | 显示全部楼层
以前也出现过这个问题,可惜没找到原因!
顶起来,让更多人看到!

出0入0汤圆

发表于 2007-12-29 14:48:31 | 显示全部楼层
顶,呵呵,

出50入0汤圆

发表于 2007-12-29 18:06:39 | 显示全部楼层
请问下:

这段代码中(~tmp1)的结果在2个编译器中有不同解释:
ICCAVR:取反结果就是0xFC;((~tmp1)>>flashvar[1])结果是0x3F;
GCC:取反结果被扩展为0xFFFC;((~tmp1)>>flashvar[1])结果是0xFFFF(即对16位进行了移位操作,高8位ASR,低8位ROR,而不是8位移位),最终gvar1值为0xFF(和优化等级无关);

GCC:取反结果被扩展为0xFFFC;((~tmp1)>>flashvar[1])结果是0xFFFF ?(应该是0x3FFF吧?)

出0入0汤圆

发表于 2007-12-29 18:14:04 | 显示全部楼层
好帖,以后要注意一下才行,因为经常用GCC,
24楼的问题,LZ不是解释了吗?“即对16位进行了移位操作,高8位ASR,低8位ROR,而不是8位移位”
是算术移位补符号位的原因

出0入0汤圆

发表于 2008-1-2 22:02:59 | 显示全部楼层

出0入0汤圆

发表于 2008-1-3 01:24:08 | 显示全部楼层
我习惯这样修饰:

uint8_t flashvar[2]={ 1, 2};
uint8_t gvar1=255;
uint8_t tmp1=3;

这样就不会出现楼主所说的问题。

出0入0汤圆

发表于 2008-1-3 08:27:59 | 显示全部楼层
typedef unsigned char uint8_t;
stdint.h里是这样定义uint_t的,楼上的确定这样不会出同样的问题?

出0入0汤圆

 楼主| 发表于 2008-1-3 10:35:06 | 显示全部楼层
定义为uint8_t并不会改变这个结果。因为avr-gcc默认的整型是16位的,那么按c标准,这些运算总是会把8位扩展为编译器设置的整型大小(即16位)。
另外,虽然avr-libc手册上说uint8_t是定义成:
typedef unsigned char uint8_t;
但实际上不是这样的,手册是Doxygen生成的,stdint.h里这些整型定义于:
#if defined(__DOXYGEN__)
...(这里是手册里看到的定义)
#else
...(这里是实际定义)
#endif
默认情况下,这两处定义是等价的。但是如果更改编译参数的话,就不同了(也涉及到代码优化的一个方法)。
实际定义拥有更好的移植性和优化性。
建议用avr-gcc的话,不要像我在一楼里那样定义变量,而是用uint8_t之类(我现在移植过来的代码已经全改成此类定义了)。

出0入0汤圆

发表于 2008-1-3 14:07:26 | 显示全部楼层
请问什么是ASR,全称

出0入0汤圆

发表于 2008-1-4 10:43:25 | 显示全部楼层
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddd                    dddddddddddddddd                          dddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
ddddddddddddddd   dddddddddddddddddddddddddddddddddddddd   ddddddddddddddddddddddddd
ddddddddddddddd   ddddddddddddddddddddddddddddddddddddd   dddddddddddddddddddddddddd
ddddddddddddddd   dddddddddddddddddddddddddddddddddddd   ddddddddddddddddddddddddddd
ddddddddddddddd   ddddddddddddddddddddddddddddddddddd   dddddddddddddddddddddddddddd
ddddddddddddddd   dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd                              dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddddddddddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddddddddddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddddddddddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   ddddddddddd   dddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddddd   ddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   ddddddddd   dddddddddddd   dddddddddddd
ddddddddddddddd   dddddddddddddddddddddddd   dddddddd      dddddddddd   dddddddddddd
dddddddd dddddd   dddddddddddddddddddddddd   ddddddd   dd   ddddddddd   dddddddddddd
ddddddddd   ddd   dddddddddddddddddddddddd   dddddd   dddd   dddddddd   dddddddddddd
dddddddddd   dd   dddddddddddddddddddddddddddddddd   dddddd    ddddddddddddddddddddd
dddddddddddd      ddddddddddddddddddddddddddddddd   dddddddd   ddddddddddddddddddddd
dddddddddddddd    dddddddddddddddddddddddddddddd   dddddddddd   dddddddddddddddddddd
ddddddddddddddddddddddddddddddddddddddddddddddd   dddddddddddd   ddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddd   dddddddddddddd   dddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddd   dddddddddddddddddd   dddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddd    dddddddddddddddddddddd   ddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd

出0入0汤圆

发表于 2008-1-5 09:59:28 | 显示全部楼层
所以,这个帖子提醒大家,细节上不要想当然,编译器手册、规范还是要啃一下的。

出0入0汤圆

发表于 2011-8-13 09:04:33 | 显示全部楼层
厉害呀!我也是这两天做红外遥控做了取反发现问题的。看来以后得多多学习标准的东西啦。

出0入0汤圆

发表于 2013-9-16 14:41:38 | 显示全部楼层
"定义为uint8_t并不会改变这个结果。因为avr-gcc默认的整型是16位的,那么按c标准,这些运算总是会把8位扩展为编译器设置的整型大小(即16位)。
另外,虽然avr-libc手册上说uint8_t是定义成:
typedef unsigned char uint8_t;
但实际上不是这样的,手册是Doxygen生成的,stdint.h里这些整型定义于:
#if defined(__DOXYGEN__)
...(这里是手册里看到的定义)
#else
...(这里是实际定义)
#endif
默认情况下,这两处定义是等价的。但是如果更改编译参数的话,就不同了(也涉及到代码优化的一个方法)。
实际定义拥有更好的移植性和优化性。
建议用avr-gcc的话,不要像我在一楼里那样定义变量,而是用uint8_t之类(我现在移植过来的代码已经全改成此类定义了)。"

可能我还是没有看懂用uint8的好处,不是不能解决整型提升的问题吗?

出0入0汤圆

发表于 2013-9-16 15:26:43 | 显示全部楼层
虽然是旧帖,但内容确实不错哦

出0入0汤圆

发表于 2013-9-16 16:57:20 | 显示全部楼层
本帖最后由 wxty 于 2013-9-16 17:00 编辑

提到前面来,望高人指点。
仔细看了楼主的回复,可能我还是没有看懂用uint8的好处,不是也不能解决整型提升的问题吗?

以下引用楼主上面的回帖:
"定义为uint8_t并不会改变这个结果。因为avr-gcc默认的整型是16位的,那么按c标准,这些运算总是会把8位扩展为编译器设置的整型大小(即16位)。
另外,虽然avr-libc手册上说uint8_t是定义成:
typedef unsigned char uint8_t;
但实际上不是这样的,手册是Doxygen生成的,stdint.h里这些整型定义于:
#if defined(__DOXYGEN__)
...(这里是手册里看到的定义)
#else
...(这里是实际定义)
#endif
默认情况下,这两处定义是等价的。但是如果更改编译参数的话,就不同了(也涉及到代码优化的一个方法)。
实际定义拥有更好的移植性和优化性。
建议用avr-gcc的话,不要像我在一楼里那样定义变量,而是用uint8_t之类(我现在移植过来的代码已经全改成此类定义了)。"

第一句话说不会改变结果,最后一句话说建议用uint8_t之类,

出0入0汤圆

发表于 2013-9-16 21:22:08 来自手机 | 显示全部楼层
涨知识了           

出0入0汤圆

发表于 2013-9-16 22:49:54 | 显示全部楼层
wxty 发表于 2013-9-16 16:57
提到前面来,望高人指点。
仔细看了楼主的回复,可能我还是没有看懂用uint8的好处,不是也不能解决整型提升 ...
默认情况下,这两处定义是等价的。但是如果更改编译参数的话,就不同了(也涉及到代码优化的一个方法)。


GCC的AVR移植版本有一个“-mint8”参数,GCC官方手册说明:
-mint8
    Assume int to be 8-bit integer. This affects the sizes of all types: a char is 1 byte, an int is 1 byte, a long is 2 bytes, and long long is 4 bytes. Please note that this option does not conform to the C standards, but it results in smaller code size.

使用这个参数意味着int类型被降为1个字节,那么当执行1个字节变量的比较、移位时,就不存在提升类型的问题了。由于减少了提升带来的运算开销,编译后的代码占用会减少,而且不影响运行速度(应该说16位变8位运算后,反而运行速度更快了些)。

但这是有副作用的,如果你的变量全是用int、long等方式定义的,一旦使用该参数,会导致所有变量的长度减少一半,因而数值范围减少一半,如果不仔细检查,可能会因为范围溢出导致运行错误。而且4字节得用long long,可能会引起和库函数的不兼容。

不过如果代码中变量都用int16_t等方式定义的,不受该参数影响,依然会是2个字节长度,所以这个时候int16_t并不等价于int。

出0入0汤圆

发表于 2013-9-18 02:14:23 | 显示全部楼层
感谢楼主分享,长见识了
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-29 20:12

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表