IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 避坑必看:C++移植C语言结构体char短整形导致生产事故分析 -> 正文阅读

[C++知识库]避坑必看:C++移植C语言结构体char短整形导致生产事故分析

在C语言中,整数类型有很多类似UINT32这样的指代类型,以便在不同的字长的硬件里,确定使用特定长度(如32比特)的整数。在C中,从整形往字符串的转换一般是调用一些函数,如printf, 并人为指定一个类型说明符,比如 %d 来完成。若迁移C的工程到C++,则要额外注意类型的敏感性。在产生字符串时,不指定类型的C++调用,会把char作为字符处理,导致诡异的问题。

下面这个例子就是最近协助解决的一个非常典型的类型问题,在一个生产系统里存在多年,直到传感器扩容后才爆发,造成了经济损失。

acc

1. C示意程序

老版软件,使用一个结构体存储传感器的电压(代表温度),并插入到数据库里。由于是控制台程序,采用的是管道重定向,即直接生成INSERT语句,而后定向到数据库的控制台客户端中执行。

#include <stdio.h>
typedef struct tag_item{
	unsigned int timestamp;
	int vol;
	char  machine_id;
} ITEM;

int main()
{
	ITEM item;
	item.timestamp = 1646587837;
	item.machine_id = 66;
	item.vol = 12;
	printf("INSERT INTO mlog(tmstmp,vol,machine) VALUES (%u, %d, 'M_%d');\n"
		   , item.timestamp
		   , item.vol
		   , item.machine_id
		   );
	return 0;
}

此时,会输出:

INSERT INTO mlog(tmstmp,vol,machine) VALUES (1646587837, 12, 'M_66');

这个例子里,machine字段在数据库里是字符串,因此采用引号包裹。但这种字符串类型的字段,是不会检查“66”这个整形的有效性的。

2. C++Qt示意程序

新版软件,是这样处理的:

#include <QCoreApplication>
#include <QTextStream>
struct tag_item{
	long long timestamp;
	int vol;
	char  machine_id;
};

int main(int argc, char * * argv)
{
	QCoreApplication a(argc,argv);
	tag_item item;
	item.timestamp = 1646587837;
	item.vol = 12;
	item.machine_id = 66;

	QString sql = QString("INSERT INTO mlog(tmstmp,vol,machine) VALUES (%1,%2,'M_%3');\n")
			.arg(item.timestamp)
			.arg(item.vol)
			.arg(item.machine_id);

	QTextStream stm(stdout);

	stm << sql;
	return 0;
}

程序输出为:

INSERT INTO mlog(tmstmp,vol,machine) VALUES (1646587837,12,'M_B');

注意到了吧?66被作为char类型的ascii码,转换为了"B"

3. 问题的隐藏

这个系统部署了3年了,产生无数的记录,但是一直都很正常。

用户不知道正常的ID应该是M66,M67,负责的工人在界面记录的是 M_B,M_C。工人知道M_B在电镀车间,M_X在制氧车间的液箱里。如果当时安装传感器的师傅知道内部规则,就会发现水箱上贴的标记是诡异的。由于工程一期的传感器不多,传感器的取值恰好位于字母区,这个问题就一直隐藏在这里。

此外,上位机上的程序过于简单,传感器首次上线,只是检查传感器的ID有无重复,并不检查取值。如果发现了新的传感器,则直接插入,并提示车间助理录入位置、温度报警范围等参数。系统部署以来,在界面上展示的始终就是 M_B这样的字母ID!就连公司的客服也以为这是正常的。

4. 问题爆发和修复

直到今年夏天疫情结束,生产线重启前,趁机批量更换并扩容,导致出现了反斜杠(92号传感器的ASCII)转译问题:

INSERT INTO mlog(tmstmp,vol,machine) VALUES (1646587837,12,'M_\');

而这些有问题的语句导致事务的失败和回滚,同一个事务内的所有INSERT全部丢失。由此带来的温控系统得不到及时、正确的反馈,液体温度过热,生产线停机。解决的方法很简单, 强制转换为qint8:

	QString sql = QString("INSERT INTO mlog(tmstmp,vol,machine) VALUES (%1,%2,'M_%3');\n")
			.arg(item.timestamp)
			.arg(item.vol)
			.arg((qint8)item.machine_id);

但是损失是很大的,造成了液料损失,和管道清洗损失共计20多万元。

5. 问题测试

问题根源是没有正确估计到C++的自动类型判断带来的潜在问题。C++自动通过类型来决定策略,可以用下面的例子具体观察:

#include <QCoreApplication>
#include <QTextStream>
#include <iostream>
struct tag_item{
	long long timestamp;
	int vol;
	char  machine_id;
};

template <typename T>
void test(T v)
{
	QTextStream stm(stdout);
	stm << "typeid=" << typeid(v).name() <<"\n";
	stm << "\tTest QTextStream : "
		<< v
		<< "\n";

	stm << "\tTest QString()   : "
		<< QString("%1").arg(v)
		<< "\n";

	stm.flush();

	std::cout << "\tTest iostream    : "
		<< v
		<< "\n";
}

int main(int argc, char * * argv)
{
	QCoreApplication a(argc,argv);
	tag_item item;
	item.timestamp = 1646587837;
	item.vol = 12;
	item.machine_id = 66;

	test(item.machine_id);
	test((char)		item.machine_id);
	test((qint8)	item.machine_id);
	test((__int8_t)	item.machine_id);
	test((unsigned char)	item.machine_id);
	test((quint8)			item.machine_id);
	test((int)				item.machine_id);
	return 0;
}

输出为:

typeid=c
	Test QTextStream : B
	Test QString()   : B
	Test iostream    : B
typeid=c
	Test QTextStream : B
	Test QString()   : B
	Test iostream    : B
typeid=a
	Test QTextStream : 66
	Test QString()   : 66
	Test iostream    : B
typeid=a
	Test QTextStream : 66
	Test QString()   : 66
	Test iostream    : B
typeid=h
	Test QTextStream : 66
	Test QString()   : 66
	Test iostream    : B
typeid=h
	Test QTextStream : 66
	Test QString()   : 66
	Test iostream    : B
typeid=i
	Test QTextStream : 66
	Test QString()   : 66
	Test iostream    : 66

可见,无论是用标准C++或者Qt,都存在char类型的自动转换注意事项。

(生产环境为传感器嵌入式Linux系统+Linux上位机)

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:32:27  更:2022-07-03 10:35:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 16:31:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码