在C和C ++中,哪些方法可以防止在需要等价(==)的情况下意外使用工作分配(=)?


15

在C和C ++中,编写带有严重错误的以下代码非常容易。

char responseChar = getchar();
int confirmExit = 'y' == tolower(responseChar);
if (confirmExit = 1)
{
    exit(0);
}

错误是if语句应该是:

if (confirmExit == 1)

按照编码,它将每次退出,因为confirmExit发生了变量分配,然后confirmExit将其用作表达式的结果。

是否有防止这种错误的好方法?


39
是。打开编译器警告。将警告视为错误,这不是问题。
马丁·约克


9
在给定的示例中,解决方案很简单。你给它分配一个布尔值,因此使用它作为一个布尔值if (confirmExit)
确保

4
问题在于,当他们选择将=用作赋值运算符并将==用作相等性比较时,该错误是C语言“设计者”所犯的。ALGOL之所以使用:=,是因为他们特别想使用=进行相等性比较,而PASCAL和Ada遵循了ALGOL的决定。(值得注意的是,当国防部要求基于C的条目进入DoD1计划,最终产生了Ada时,贝尔实验室拒绝了,他说:“ C尚不存在,而且对于国防部关键任务来说永远不会足够强大“希望国防部和承包商能在此方面听到贝尔实验室的
声音

4
@John,选择符号不是问题,事实是赋值也是一个返回赋值的表达式,允许a = ba == b在条件内使用。
Karl Bielefeldt 2012年

Answers:


60

最好的技术是提高编译器的警告级别。然后,如果有条件,它将警告您有关潜在的分配。

确保使用零警告编译代码(无论如何应该这样做)。如果您想成为书呆子,则将编译器设置为将警告视为错误。

使用Yoda条件句(将常量放在左侧)是大约十年前流行的另一种技术。但是,它们使代码更难阅读(并且由于它们的阅读方式不自然(因此除非您是Yoda),因此得以维护),并且提供的好处没有增加警告级别(带来更多警告的额外好处)之外。

警告实际上是代码中的逻辑错误,应予以纠正。


2
绝对要遵循警告,而不是Yoda条件句-您可以忘记先做条件常量,就像忘记做==一样容易。
迈克尔·科恩

8
我对此感到惊讶,因为对该问题的投票最多的答案是“将编译器警告变为错误”。我期待着if (0 == ret)恐怖。
詹姆斯

2
@詹姆斯:...更不用说它不会工作a == b
Emilio Garavaglia

6
@EmilioGaravaglia:有一个简单的解决方法:编写0==a && 0==b || 1==a && 1==b || 2==a && 2==b || ...(重复所有可能的值)。不要忘记强制性的...|| 22==a && 22==b || 23==a && 24==b || 25==a && 25==b ||...错误,否则维护程序员将不会有任何乐趣。
user281377

10

您总是可以做一些根本的事情,例如测试软件。我什至不表示自动化的单元测试,只是每位有经验的开发人员都会通过两次运行他的新代码,一次确认退出而一次不退出来摆脱习惯。这就是大多数程序员认为它不是问题的原因。


1
当程序的行为是完全确定性的时,易于执行。
詹姆斯

3
这个特殊问题可能很难测试。我已经看到人们被rc=MethodThatRarelyFails(); if(rc = SUCCESS){不止一次地咬伤,特别是如果该方法仅在难以测试的情况下失败时,尤其如此。
Gort机器人

3
@StevenBurnap:这就是模拟对象的用途。正确的测试包括测试故障模式。
Jan Hudec 2012年

这是正确的答案。
莫妮卡(Monica)

6

防止在表达式中错误使用赋值的传统方法是将常量放在左侧,将变量放在右侧。

if (confirmExit = 1)  // Unsafe

if (1=confirmExit)    // Safe and detected at compile time.

编译器将报告非法分配错误,并将其报告给类似于以下内容的常量。

.\confirmExit\main.cpp:15: error: C2106: '=' : left operand must be l-value

修改后的条件为:

  if (1==confirmExit)    

如下面的注释所示,许多人认为这是不合适的方法。



13
应该注意的是,以这种方式做事会使您相当不受欢迎。
riwalk

11
请不要推荐这种做法。
詹姆斯

5
如果您需要将两个变量相互比较,那么它也不起作用。
dan04'8

有些语言实际上允许将1
赋给

4

我同意每个人所说的“编译器警告”,但我想添加另一种技术:代码审查。如果您有审查所有提交的代码的政策,最好在提交之前进行审查,那么在审查期间很可能会遇到这种情况。


我不同意。特别是=而不是==可以通过查看代码轻松地通过。
西蒙(Simon)

2

首先,提高警告等级永远不会造成伤害。

如果您不希望条件语句在if语句本身中测试赋值的结果,那么多年来,与许多C和C ++程序员一起工作,并且从未听说过先比较常量if(1 == val)是一件坏事,那么您可以尝试这种构造。

如果您的项目负责人批准您这样做,则不必担心其他人的想法。真正的证明是您或其他人以后几个月或几年后都可以理解您的代码。

但是,如果您打算测试分配的结果,则使用更高的警告可能会(可能会)将分配捕获为常数。


我对任何看到Yoda有条件并且不立即理解它的非初学者程序员都表示怀疑。这只是一个翻转,不难阅读,当然也没有某些评论所声称的那么糟糕。
underscore_d

@underscore_d在我的大多数雇主中,有条件的工作都被拒绝了。想法是最好将分配与条件分配分开。即使要牺牲另一行代码,也要保持清晰的原因是持续的工程因素。我在代码库很大且积压了大量维护工作的地方工作。我完全理解有人可能要分配一个值,并根据分配产生的条件进行分支。我看到它在Perl中执行得更多,如果意图很明确,我将遵循作者的设计模式。
octopusgrabbus

我只在考虑Yoda条件句(例如此处的演示),而不是在分配任务(如在OP中)。我不介意前者,但也不太喜欢后者。我故意使用的唯一形式是if ( auto myPtr = dynamic_cast<some_ptr>(testPtr) ) {nullptr如果强制转换失败,它将避免在范围上保持无用-这大概是C ++在条件条件下具有这种有限分配能力的原因。是的,是的,我想说的是,定义应该有自己的用语-一目了然,而且不太容易出现各种各样的想法。
underscore_d

@underscore_d根据您的评论编辑的答案。好点子。
octopusgrabbus

1

像往常一样晚了,但是静态代码分析是关键

现在,大多数IDE都在对编译器进行语法检查之外提供SCA,并且可以使用其他工具,包括那些实现MISRA(*)和/或CERT-C准则的工具。

声明:我是MISRA C工作组的成员,但我以个人身份发布。我也独立于任何工具供应商


-2

仅使用左手分配,编译器警告会有所帮助,但您必须确保获得正确的级别,否则您将被无意义的警告所淹没,或者不会被告知想要看到的警告。


4
您会惊讶于现代编译器生成的毫无意义的警告。您可能不会认为警告很重要,但是在大多数情况下,您只是错了。
克里斯托弗·普罗沃斯特

查阅《编写实心代码》这本书amazon.com/Writing-Solid-Microsoft-Programming-Series/dp/…。首先要对编译器进行讨论,并从警告消息和更广泛的静态分析中获得多少收益。
DeveloperDon

@KristofProvost我设法让Visual Studio在同一行代码中为我提供了10个完全相同的警告的副本,然后,当“解决”此问题时,它对同一行代码产生了10条相同的警告,指出原来的方法更好。
倒骆驼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.