搜索
bottom↓
回复: 43

从坑里刚爬出来, 大家来围观一下, 涨涨姿势.

[复制链接]

出0入0汤圆

发表于 2014-10-26 22:39:10 | 显示全部楼层 |阅读模式
从坑里刚爬出来, 发个帖子.

先说起因.
因为要用到unix时间戳, 可恶的是AVR-GCC自带的库里面居然不包括time.h, 还好我要尔不高, 我只用到mktime()和localtime()两个函数而已, 而自己造轮子的话还是略有难度, 于是下载了glibc的源代码, 打算把这两个函数扒出来, 结果一看代码傻眼了, 比我想像的复杂, 那好吧, 先Google再说.所幸搜索之一通后找到一个从linux内核里扒出来的mktime()版本和一个网友自己写的localtime().
下面这个就是从linux内核里扒出来的mktime():

  1. static time_t _mktime(int year,
  2.                       int mon,
  3.                       int day,
  4.                       int hour,
  5.                       int min,
  6.                       int sec)
  7. {
  8.     if (0 >= (int)(mon -= 2))
  9.     {                       /* 1..12 -> 11,12,1..10 */
  10.         mon += 12;          /* Puts Feb last since it has leap day */
  11.         year -= 1;
  12.     }

  13.     return ((((time_t)(year/4 - year/100 + year/400 + 367*mon/12 + day) + year*365L - 719499L
  14.              )*24 + hour    /* now have hours */
  15.             )*60 + min      /* now have minutes */
  16.         )*60 + sec;         /* finally seconds */
  17. }
复制代码


然后在电脑使用GCC编译, 测试, 没问题之后用AVR-GCC编译, 下载到板子上一试, 居然有问题.

先说一下时间相关的代码逻辑:
先从RTC(PCF8563)中获取日期信息, 填入struct tm结构体, 然后用mktime()转换成unix时间戳. RTC获取时间的接口函数返回的即是这个时间戳.
在外部使用细分时间的时候再将获取到的时间戳使用localtime()转换回来, 得到日期等信息.

现在可以描述一下问题现像了:
当初始化的时间(绝对不会错)读回来后用localtime()转出来一看, 驴头不对马嘴.
更惨的是手头没调试器, 只能从串口打印调试信息.

于是打印读到的unix时间戳一看, 错的. 很自然的怀疑是调用mktime()的时候结构体信息填错了, 打印了一堆信息后证明没问题.

然后怀疑缓冲溢出, 野指针等, 结果证明均无问题. 在源文件里差写了1/5的调试代码后, 终于怀疑到mktime()头上来了.

无论如何, 我还是有点不相信, 一来这是在linux内核里扒出来的, 二来我已经在电脑上用GCC编译测试过了, 确实没问题.再说这段代码这么短, 按过程手算一遍结果都是对的啊.

最先怀疑的是那个return表达式太长, 编译器在处理表达式时出了问题. 虽然连我自己都不相信, 但我依然还是将它拆分成几部分. 结果证明, 与表达式无关, 但真相就快浮出水面了.

先看一段代码.

  1. int main(void)
  2. {
  3.     uint32_t i;
  4.     int j;

  5.     j = 1999;

  6.     i = j * 365 - 719499;

  7.     return 0;
  8. }
复制代码


你能看出来这段代码那里有问题吗? 很明显没什么问题, 在电脑上计算结果也是对的, 无任何警告, 当然在AVR-GCC里也没任何警告, 如图, 使用-Wall选项警告全开哦.



诡异的是算出来的结果是错的. 或许我说过这句话后你已经知道是怎么回事了, 但是mktime()这个函数里看起来可不是那么明显.

然后打印了好几次调试信息后, 我把代码改成这样:

  1. int main(void)
  2. {
  3.     uint32_t i;
  4.     int j;

  5.     j = 1999;

  6.     i = 1999 * 365 - 719499;

  7.     return 0;
  8. }
复制代码


这次TM终于报了个警告: warning: integer overflow in expression !!

草, 瞬间明白了, 栽到整形提升上了.

如果还有没明白的童鞋, 我来解释一下:

参数j是int型的, 365 和 719499 都足够用int表示, 所以右值的类型是int型. 很不幸的是当乘法计算完毕后结果就溢出了! 为什么会溢出呢? 因为在AVR-GCC中, int类型占用的空间为两个字节! 即sizeof(int) = 2. 在PC上不会溢出的原因是因为在PC平台上int类型占用4个字节!

知道原因后解决就很简单了, 因为是在计算乘法时溢出的, 所以将365改为365L即可, 或者将j强制转换为uint32类型亦可.

(全文完)

后记: 折腾了大半天, 累死我了. 昨天睡觉扭了脖子, 疼的很, 先睡了.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……

出0入0汤圆

发表于 2014-10-26 23:07:21 | 显示全部楼层
恩恩                           

出0入0汤圆

发表于 2014-10-26 23:21:06 | 显示全部楼层
恩,涨了点知识,以后会注意

出0入42汤圆

发表于 2014-10-27 00:24:58 | 显示全部楼层
我一般这样处理:

如果是简单的计算或循环计数,用int
如果涉及到较大的数值就用uint32_t 或 uint16_t,具体用哪个需要估算中间值的范围

记得刚学C语言的时候书上说int是16位的,实际试验的时候发现是32位的,所以对这个映象很深刻

出0入17汤圆

发表于 2014-10-27 07:38:48 来自手机 | 显示全部楼层
还是那句话: 现象越奇怪,错误越低级

出0入0汤圆

发表于 2014-10-27 07:58:49 | 显示全部楼层
好!楼主辛苦了!

出0入0汤圆

发表于 2014-10-27 07:59:31 | 显示全部楼层
涨知识了,记住了

出140入158汤圆

发表于 2014-10-27 08:07:23 | 显示全部楼层
这是基本常识,折腾只能怪自己基础不扎实

出75入88汤圆

发表于 2014-10-27 08:14:37 | 显示全部楼层
radar_12345 发表于 2014-10-27 07:38
还是那句话: 现象越奇怪,错误越低级

这个才是经验总结啊

出0入0汤圆

 楼主| 发表于 2014-10-27 08:23:06 来自手机 | 显示全部楼层
wshtyr 发表于 2014-10-27 00:24
我一般这样处理:

如果是简单的计算或循环计数,用int

不同平台之间程序的移植尤其要注意,很多PC平台上的代码用的是标准类型,而糟糕的是不同平台对标准类型的长度定义不一样,溢出了都不清楚在哪里,更别说警告了。

出0入0汤圆

发表于 2014-10-27 08:24:54 | 显示全部楼层
楼主用的什么编辑器,配色看起来挺舒服的

出0入0汤圆

发表于 2014-10-27 08:27:07 | 显示全部楼层
是蛮舒服的

出0入0汤圆

发表于 2014-10-27 08:27:28 | 显示全部楼层
做嵌入式多了,逐步就抛弃了使用int
我的程序,只使用U8 U16 U32 U64和S8 S16 S32 S64这种二次定义的类型
一是移植的时候方便,二是数据sizeof是确定的

当然,从执行速度和产生代码大小上看,int因为和CPU的字长一致,往往是最好的

出0入0汤圆

 楼主| 发表于 2014-10-27 08:27:43 来自手机 | 显示全部楼层
amigenius 发表于 2014-10-27 08:07
这是基本常识,折腾只能怪自己基础不扎实

你肯定没仔细看贴。这个问题估计大部分人不会一眼看出来。

出0入0汤圆

发表于 2014-10-27 08:33:26 | 显示全部楼层
涨知识,Mark!

出140入158汤圆

发表于 2014-10-27 08:40:16 | 显示全部楼层
DevLabs 发表于 2014-10-27 08:27
你肯定没仔细看贴。这个问题估计大部分人不会一眼看出来。

我肯定没仔细看,因为这种问题,瞧一眼就知道是什么问题了。对于第二句话,您要么是把大部分人看扁了,要么是您指只有中国人才算是人。

出0入0汤圆

发表于 2014-10-27 08:51:46 | 显示全部楼层
int类型在不同的平台上长度是不一样的
所以我一般不用int类型。
16位我用short,32位用long

出0入8汤圆

发表于 2014-10-27 08:52:05 | 显示全部楼层
一起来长长姿势~

出0入0汤圆

 楼主| 发表于 2014-10-27 08:57:49 | 显示全部楼层
风轻云淡 发表于 2014-10-27 08:24
楼主用的什么编辑器,配色看起来挺舒服的

gVim,配色是solarized。

出0入0汤圆

发表于 2014-10-27 09:09:40 | 显示全部楼层
radar_12345 发表于 2014-10-27 07:38
还是那句话: 现象越奇怪,错误越低级

非常有道理

出0入0汤圆

发表于 2014-10-27 09:15:00 | 显示全部楼层
仿真器你没用上?

出0入0汤圆

 楼主| 发表于 2014-10-27 10:32:58 | 显示全部楼层
Vmao 发表于 2014-10-27 09:15
仿真器你没用上?

手边没有仿真器.
后来装了AVR studio软件仿真, 但AVR Studio软件仿真跑的巨慢, 放弃.

出0入0汤圆

发表于 2014-10-27 10:42:36 | 显示全部楼层
radar_12345 发表于 2014-10-27 07:38
还是那句话: 现象越奇怪,错误越低级

总结的很正确。

出0入0汤圆

发表于 2014-10-27 13:08:39 | 显示全部楼层
涨知识              

出0入42汤圆

发表于 2014-10-27 20:43:10 | 显示全部楼层
DevLabs 发表于 2014-10-27 10:32
手边没有仿真器.
后来装了AVR studio软件仿真, 但AVR Studio软件仿真跑的巨慢, 放弃. ...

gVim握爪

刚学的时候想砸键盘,现在越来越好用了

出0入0汤圆

发表于 2014-10-27 21:07:33 | 显示全部楼层
现象越奇怪,错误越低级      

出0入12汤圆

发表于 2014-10-28 01:27:24 来自手机 | 显示全部楼层
现象越奇怪,错误越低级。超赞同!

出0入0汤圆

发表于 2014-10-28 06:55:17 来自手机 | 显示全部楼层
学习一下

出0入0汤圆

 楼主| 发表于 2014-10-28 07:25:43 | 显示全部楼层
wshtyr 发表于 2014-10-27 20:43
gVim握爪

刚学的时候想砸键盘,现在越来越好用了

现在使用别的编辑器也不自觉的去按ESC

出0入0汤圆

发表于 2014-10-28 09:54:52 来自手机 | 显示全部楼层
有意思,以前也碰到过

出0入93汤圆

发表于 2014-10-28 10:04:32 | 显示全部楼层
yu_studio 发表于 2014-10-27 08:51
int类型在不同的平台上长度是不一样的
所以我一般不用int类型。
16位我用short,32位用long ...

万一某个编译器上long是64位你怎么办,还是免不了掉坑里
C语言跨平台上,其实他们自己也做过很多努力的,只是使用者以各种原因忽略。C语言可移植数据类型:
  1. #include <stdint.h>
复制代码

出0入0汤圆

发表于 2014-10-29 12:20:57 | 显示全部楼层
涨知识了,记住了

出0入0汤圆

 楼主| 发表于 2014-10-29 12:33:42 | 显示全部楼层
takashiki 发表于 2014-10-28 10:04
万一某个编译器上long是64位你怎么办,还是免不了掉坑里
C语言跨平台上,其实他们自己也做过很多努力的, ...

现在我一般使用 <stdint.h> 和 <stdbool.h>.
然后字符串数组为了使用库函数时编译器不产生警告, 还是使用 char.

出0入0汤圆

发表于 2014-10-29 12:35:39 | 显示全部楼层
radar_12345 发表于 2014-10-27 07:38
还是那句话: 现象越奇怪,错误越低级

支持这观点,真神!

出0入0汤圆

发表于 2014-10-29 12:40:03 | 显示全部楼层
习惯用uint32、uint16了,一般for里的i可能会用下int

出0入0汤圆

发表于 2014-10-29 14:18:32 | 显示全部楼层
有时候一些越低级的错误越难发现

出0入0汤圆

发表于 2014-10-29 14:29:17 | 显示全部楼层
涨知识了,学习学习

出0入0汤圆

发表于 2014-10-29 14:40:18 | 显示全部楼层
不错,涨姿势了。。。

出0入0汤圆

发表于 2014-10-29 15:03:54 | 显示全部楼层
wshtyr 发表于 2014-10-27 00:24
我一般这样处理:

如果是简单的计算或循环计数,用int

int不固定是16位或32位,选16位芯片开发,在开发环境中认为16位,32位芯片,开发环境就认为是32位。好像short型是固定16位的。

出0入0汤圆

发表于 2014-10-29 15:29:57 | 显示全部楼层

出0入0汤圆

发表于 2014-10-29 15:39:14 | 显示全部楼层
C99都有stdint.h,用这个类型定义变量吧

出10入0汤圆

发表于 2014-10-29 15:45:56 | 显示全部楼层
为了避免这类问题,我一般使用u8,u16,u32

出0入0汤圆

发表于 2014-10-29 16:24:52 | 显示全部楼层
现象越奇怪,错误越低级

出0入0汤圆

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

本版积分规则

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

GMT+8, 2024-8-26 10:22

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

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