13

gcc 有一个有用的标志-Wconversion,当从较宽类型到较窄类型的隐式转换时会产生警告,可能会丢失信息。不幸的是,它有以下......无益的......行为。


考虑这个程序:

int main(void) {
  short x = 1;
  x = x+x;
  return 0;
}

用 -Wconversion 编译它会产生

nonsense.c: In function 'main':
nonsense.c:3:8: warning: conversion to 'short int' from 'int' may alter its value [-Wconversion]

这很公平;在大多数平台上,如果发生这种情况,这将做一些你可能不会想到的事情x==0x8000。(您收到此警告的实际机制:操作数+受“通常的算术转换”的影响,这会将它们扩大为 int;因此结果也是 int 类型;然后分配回x是从更广泛的隐式转换到更窄的类型。)但是假设您确实期望并打算这样做。(您正在模拟 16 位处理器或 16 位移位寄存器;或者您知道x此代码中可能值的范围,并且它永远不会溢出。)您可以通过显式强制转换来告诉编译器:

int main(void) {
  short x = 1;
  x = (short)(x+x);
  return 0;
}

然后它就不会抱怨了。


到目前为止,一切都很好。但是,如果导致问题的赋值是复合赋值 - +=, *=,<<=等 - 那么据我所知,没有办法摆脱这个警告,因为代码中没有任何一点可以您可以插入显式演员表。

这意味着,例如,你不能拥有所有

  • -Wconversion 在您项目的顶级编译器标志中,以捕获它打算处理的所有真正错误。
  • 复合赋值运算符代码中任何位置的任何实例都适用于短于int.
  • 无警告构建。

这似乎很伤心。


所以,问题是:有没有解决这个问题的好方法?我可以看到以下方式:

  • 用于#pragma GCC diagnostic ...禁用尽管没有错误但已知会引发警告的代码位中的警告。(缺点:丑陋,特定于编译器。)
  • 将复合赋值扩展为更长的单个赋值并插入显式强制转换。(缺点:超级丑。)
  • 关掉-Wconversion。(缺点:此警告在其他地方很有用。)
  • 忍受警告。(缺点:与 using 不兼容-Werror;在任何情况下,让您的代码在没有警告的情况下编译是一种很好的做法,因为“无警告”和“某些警告”之间的区别比“某些警告”和“某些警告”之间的区别更容易发现“更多警告”。)

所有这些似乎都不令人满意,我想知道我是否缺少更聪明的东西。

(注意:如果这确实是事实,我当然会接受“不,就是这样”的答案。)


因此,看起来答案似乎是这确实是预期的行为,没有办法阻止它,这比上面列出的要好得多。(Mark B 观察到您可以编写一个与显式转换相同+=但具有相同功能的函数。ecatmur 建议使用operator+=函数定义一个新类型来执行显式转换。)

我接受马克 B 的回答;标题中问题的实际答案只是“是”:-)。

4

2 回答 2

1

在 RHS 上使用用户定义的类型会起作用:

struct cast_result {
    int t;
};
char &operator+=(char &l, cast_result r) { return l = static_cast<char>(l + r.t); }

int main() {
    char c = 'a';
    c += cast_result{2};
}

您可以使用用户定义的文字等来美化这一点,但它仍然相当难看。

我认为问为什么需要对窄类型进行算术是公平的。在 C 的设计中通常假设窄类型的效率低于或可能低于int.

于 2012-09-28T15:17:43.760 回答
1

我假设在这种情况下警告的逻辑是它在功能上与复合运算符实现的操作相同。复合运算符语法应该做一个非常明显的复合操作,在这种情况下,你想明确地说你知道你在做什么。当您想明确存在意图时,更明确地拼写代码总是会获胜:您只编写一次,但更长的拼写版本将使未来的维护者完全清楚您的意图是什么。

我建议只是分解操作并拼出演员表。您的维护人员真的不会介意,我怀疑他们会认为这很丑陋。

或者,创建一个scalar_mult(short&, short)函数来隐藏演员表。然后你表现出明确的意图避免在每个表达式中进行强制转换。

最后,您也许可以完全避免使用较短的类型,从而避免警告。

于 2012-09-28T15:08:21.503 回答