C、复合赋值——算术运算,转换可能会丢失信息
Posted
技术标签:
【中文标题】C、复合赋值——算术运算,转换可能会丢失信息【英文标题】:C, compound assignment - arithmetic operation, conversion may loose information 【发布时间】:2015-11-27 10:47:13 【问题描述】:有没有办法将操作写得简短并抑制警告?
BYTE data[10];
int i = 0;
int offset = 0;
for (i = 0; i < 7; i++)
// some code
data[offset] |= (1 << i); // WARNING: conversion may loose information
我的尝试:
data[offset] |= (BYTE) (1 << i); // failed
data[offset] |= (1 << (BYTE) i); // failed
编译器:西门子的 TIA-V13 WinCC
解决方案:没有复合赋值运算符!
【问题讨论】:
没有short
。并显示BYTE
的定义。
您可能需要添加产生警告的编译器信息以及如何定义BYTE
。
这段代码是正确的,你的编译器给出了错误的警告。也许研究如何关闭这部分代码的警告,或查阅编译器文档以获取此警告。要获得有关特定编译器的答案,您必须提及您正在使用的编译器和版本
@Jongware:对于 gcc,它是 -Wconversion
。够坏;这个开关在其他方面非常有用。
@Magisch:许多开发标准都会出现警告错误,并要求编译时没有任何警告。生成这些警告的选项通常非常有用,因此应该启用它。并且有非常好的复合赋值用例应该被警告。
【参考方案1】:
你没有定义BYTE
;我假设它与 stdint.h
s uint8_t
相同(应该始终优先于自制类型)。
a |= b
(大致)转换为a = a | b
,包括整数提升作为常规算术转换的一部分。对于给定的类型,这意味着在执行操作之前将a
转换为int
,从而产生int
结果。最终分配将截断此分配的 int
,可能会丢失信息(可能是符号),因为 int
至少有 16 位宽。
转换b
没有帮助,因为转换是为操作完成的,您必须转换结果。
改造也是解决办法:
uint8_t a;
a = (uint8_t)(a | b);
明确告诉编译器你知道你在做什么并闭嘴。只要确保您真的知道自己在做什么!
这不是很优雅,但却是抑制警告的唯一方法。 OTOH,该行为在算术上与简单赋值版本一致。
编辑:在可能的情况下,您应该使用无符号整数常量。更重要的是,当您对无符号变量进行操作时。作为一般规则,尽量避免混合有符号和无符号操作数。如果这不可能,请确保您知道发生了什么并捕获所有无效案例。
Edit2:对于某些实现/有符号整数表示以及某些类型和操作的组合,可以证明不会丢失任何信息,因此警告已过时。但是,这似乎需要了解在生成警告的阶段可能无法获得的机器详细信息。
也许 C 标准应该将复合赋值定义为隐式包含最终转换。这不是唯一的遗产。 OTOH,这种行为可能会给不知道这一点的人带来另一个方向的混乱(请注意,在 C 中,赋值会产生结果!)。两害相权取其轻?
【讨论】:
我同意您的详尽解释。除了如果按位或的所有操作数都是字节,那么当编译器将结果截断回字节时,操作前的任何整数提升都不会导致信息丢失。编译器应该知道这一点并(确实)闭嘴。 @PaulOgilvie:嗯,它可以。那是因为执行了int
的操作,因此签名。无论如何,标准实际上并没有走那么远。它只是将操作数转换为int
(因此|
是在int
上执行,而不是uint8_t
),结果也是int
。请注意,如果结果是例如,事情会变得更加复杂。 int8_t
。然后结果是特定于实现的。所以,虽然我也觉得这种行为有些讨厌,但它是正确的。
不,它不能;松散的信息。对字节进行符号扩展并应用按位或然后截断回字节仍然不会丢失任何信息。是的,高位符号扩展的一位丢失了,但无论如何它们从来都不是字节的一部分。同样,在我看来,编译器应该知道并闭嘴。
@PaulOgilvie:您只需考虑 2s 补码表示。但标准并不要求这一点,但也有例如标志/大小,这会很好地松开标志。
编译器为西门子TIA-V13 WinCC。我没有定义 BYTE,也找不到定义。而且我不能告诉编译器忽略这个警告。唯一的办法是:长算术运算。 data[offset] = (BYTE)(data[offset] | (1 << i));
以上是关于C、复合赋值——算术运算,转换可能会丢失信息的主要内容,如果未能解决你的问题,请参考以下文章