当前位置: 首页 > news >正文

【C语言类型转换坑】乘法溢出隐患发现与正确写法

一、在时间计算中埋下的类型溢出隐患

在嵌入式开发或工业应用中,使用 clock_gettime 获取当前时间戳,然后将其转换为毫秒是非常常见的操作,例如:

struct timespec period;
clock_gettime(CLOCK_MONOTONIC, &period);
uint64_t current_ticks_ms = period.tv_sec * 1000 + period.tv_nsec / 1000000;

看似没问题的代码,在某些系统中却可能出现莫名其妙的数值异常。比如运行超过25天后,current_ticks_ms 的值突然变成一个很大的负数,或者值不再持续增长。这种情况大多数是由于整型乘法溢出引起的。


二、复现溢出的示例代码

我们使用一段简化的代码来模拟上述问题:

#include <stdio.h>
#include <stdint.h>
#include <time.h>int main() {int32_t sec = 2147483; // 秒数约为 24.8 天uint64_t ms = sec * 1000; // 想要得到毫秒数printf("ms = %lu\n", ms);return 0;
}

1、输出结果

ms = 18446744071562051616

这个值显然不对,本应该是 2147483000,却输出了一个接近 uint64_t 最大值的数。


2、问题分析

问题出在这一行:

uint64_t ms = sec * 1000;
  • secint32_t 类型,取值为 2147483。
  • 1000 是整型常量,默认也是 int,乘法运算发生在 int32_t 范围内。
  • 乘积为 2147483000,超过了 int32_t 最大值(2147483647),发生了溢出。
  • 然后将结果赋值给 uint64_t将一个负数隐式转换成了一个超大值

三、类型转换对运算结果的影响

为了更清楚地演示,我们对比两种写法的结果:

int32_t small_data = 2147483647;
int32_t small_data2 = 2147483647;uint64_t correct = (uint64_t)small_data * 1000;
uint64_t wrong   = small_data2 * 1000;printf("correct = %lu\n", correct);
printf("wrong   = %lu\n", wrong);

1、输出

correct = 2147483647000
wrong   = 18446744071562067968

2、差异说明

  • correct:先将 small_data 转换为 uint64_t,乘法在 64 位无符号整数域中执行,结果正确。
  • wrong:乘法在 int32_t 中先执行,溢出后为负值,再隐式转换为 uint64_t,导致结果错误。

四、如何避免此类溢出问题

1、使用强制类型转换确保精度

关键点在于运算前的显式转换

uint64_t ms = (uint64_t)period.tv_sec * 1000 + period.tv_nsec / 1000000;

这样能确保所有运算在 uint64_t 范围内进行,避免中间结果溢出。

2、避免隐式类型提升陷阱

很多开发者误以为 uint64_t = int32_t * int 会自动在乘法中使用 64 位宽度,其实不是。运算发生在参与者类型的最大宽度之间,只有当参与者之一是 uint64_t 时,乘法才在 64 位中进行。


五、总结

1、实际场景容易出错

时间戳转换、内存计算、数据放大等场景中,常见的 int * 常量 操作一不注意就会埋下隐患,尤其在使用 clock_gettime 获取运行时间等长时间运行场景。

2、推荐写法

始终在乘法发生前就显式转换:

(uint64_t)变量 * 常量

3、养成良好习惯

  • 养成对整型操作提前进行类型检查的意识
  • 在关键计算中优先使用 uint64_t 并配合强制类型转换
  • 使用编译器警告参数(如 -Wconversion)检测隐式类型转换风险
http://www.lryc.cn/news/605276.html

相关文章:

  • 嵌入式系统分层开发:架构模式与工程实践(二)(创建任务篇(二))
  • CSS-in-JS 动态主题切换与首屏渲染优化
  • 人类语言驱动物理机制建模的AIVC
  • Zynq SoC 中断控制系统设计与实现:基于 GPIO 的中断驱动开发
  • 亚马逊Kiro重塑AI编程:从“氛围编码”到规范驱动的革命
  • 论文研读(2025 KDD):细粒度人体轨迹建模
  • Python多线程利器:重入锁(RLock)详解——原理、实战与避坑指南
  • C++代码题部分(1)
  • 模型相关类代码回顾理解 | BatchNorm2d\fc.in_features\nn.Linear\torchsummary
  • c#_文件的读写 IO
  • C#_创建自己的MyList列表
  • 【36】C# WinForm入门到精通 —— flowLayoutPanel 控件 拖拽大小 “在父容器中停靠” ,“取消在父容器中停靠”
  • 独立站如何吃掉平台蛋糕?DTC模式下的成本重构与利润跃升
  • Java内存模型(JMM)
  • 地图可视化实践录:显示高德地图和百度地图
  • ica1靶机攻略
  • C#垃圾回收机制:原理与实践
  • 分享一个FPGA寄存器接口自动化工具
  • 时序数据库厂商 TDengine 发布 AI 原生的工业数据管理平台 IDMP,“无问智推”改变数据消费范式
  • 做题笔记:某大讯飞真题28道
  • 万字深度详解DHCP服务:动态IP地址分配的自动化引擎
  • 100万QPS短链系统如何设计?
  • 基于C语言实现的KV存储引擎(一)
  • 3 运算符与表达式
  • 【CVPR2025】FlowRAM:用区域感知与流匹配加速高精度机器人操作策略学习
  • 架构实战——架构重构内功心法第一式(有的放矢)
  • 《Computational principles and challenges in single-cell data integration》
  • SpringMVC 6+源码分析(一)初始化流程
  • 2021 年 NOI 最后一题题解
  • 项目文档太多、太混乱怎么解决