无意中使用=而不是==

时间:2008-12-30 08:39:52

标签: c++ c compiler-construction programming-languages

似乎

if (x=y) { .... }

而不是

if (x==y) { ... } 

是许多邪恶的根源。

为什么所有编译器都不将其标记为错误而不是可配置警告?

我有兴趣找出构造if (x=y)有用的案例。

28 个答案:

答案 0 :(得分:28)

一个有用的构造例如:

char *pBuffer;
if (pBuffer = malloc(100))
{
    //continue to work here
}

编辑:
如前所述,现在已经多次投票,我可能会补充说这不是特别好的风格,但经常看到它说它很有用。我也用new看过这个,但它让我的胸部更加疼痛。

另一个较少引起争议的例子可能是:

while (pointer = getNextElement(context))
{
    //go for it, use the pointer to the new segment of data
}

这意味着当没有下一个元素时,函数getNextElement()返回NULL,以便退出循环。

答案 1 :(得分:16)

大多数情况下,编译器会尽力保持向后兼容。

改变他们在这件事上的行为以抛出错误将破坏现有的合法代码,甚至开始抛出警告将导致自动系统出现问题,通过自动编译并检查错误和警告来跟踪代码。

这是一个邪恶,我们几乎坚持使用atm,但有办法规避和减少它的危险。

示例:

   void *ptr = calloc(1, sizeof(array));
   if (NULL = ptr) {
       // some error
   }

这会导致编译错误。

答案 2 :(得分:13)

简单答案:赋值操作(例如x = y)具有一个值,该值与x中新分配的值相同。您可以直接在比较中使用它,而不是

  

x = y; if (x) ...

你可以写

  

if (x = y) ...

写入(和读取)的代码较少,这有时是一件好事,但现在大多数人都同意应该以其他方式编写以提高可读性。例如:

  

if ((x = y) != 0) ...

这是一个现实的例子。假设你想用malloc分配一些内存,看它是否有效。它可以像这样逐步编写:

  

p = malloc(4711); if (p != NULL) printf("Ok!");

与NULL的比较是多余的,因此您可以像这样重写它:

  

p = malloc(4711); if (p) printf("Ok!");

但是由于赋值操作有一个可以使用的值,你可以将整个赋值放在if条件中:

  

if (p = malloc(4711)) printf("Ok!");

这也是一样,但更简洁。

答案 3 :(得分:11)

因为它不是非法的(无论如何使用C或C ++)并且有时很有用......

if ( (x = read(blah)) > 0)
{
    // now you know how many bits/bytes/whatever were read
    // and can use that info. Esp. if you know, say 30 bytes
    // are coming but only got 10
}

如果你没有在括号内添加括号,大多数编译器会发出真正的臭味,我喜欢。

答案 4 :(得分:8)

关于if(i = 0)

的有效用法

问题在于你正在解决这个问题。 “if”表示法不是与其他语言中的两个值进行比较。

C / C ++“if”指令等待任何将计算为布尔值或null /非null值的表达式。该表达式可以包括两个值比较,和/或可以更复杂。

例如,您可以:

if(i >> 3)
{
   std::cout << "i is less than 8" << std::endl
}

这证明,在C / C ++中,if表达式不限于==和=。任何事都可以做,只要它可以被评估为真或假(C ++),或零非零(C / C ++)。

另一个C ++有效用途:

if(MyObject * pObject = dynamic_cast<MyInterface *>(pInterface))
{
   pObject->doSomething() ;
}

这些是if表达式的简单用法(请注意,这也可以在for循环声明行中使用)。确实存在更复杂的用途。

About advanced uses of if(i = 0) in C++ [Quoted from myself]

In which case if(a=b) is a good idea?发现此问题的副本后,我决定用额外的奖励完成此答案,即可变注入范围,这在C ++中是可能的,因为{{ 1}}将评估其表达式,包括变量声明,而不是限制自己比较两个操作数,就像在其他语言中完成一样:

所以,quoting from myself

另一种用途是使用所谓的C ++ Variable Injection。在Java中,有一个很酷的关键字:

if

在C ++中,你也可以这样做。我没有确切的代码(也没有我发现它的DDJ的确切文章),但是这个简单的定义应该足以用于演示目的:

synchronized(p)
{
   // Now, the Java code is synchronized using p as a mutex
}

这是同样的方法,将注入与if和for声明混合,你可以声明一个原始的foreach宏(如果你想要一个工业强度的foreach,使用boost)。

请参阅以下文章,了解不那么天真,更完整和更强大的实现:

这种错误真的发生了多少?

很少。事实上,我还没有记住一个,我8年来一直很专业。 我猜它发生了,但是在8年后,我确实产生了相当数量的错误。只是因为这种错误并没有发生,让我在挫折中记住它们。

在C中,由于缓冲区溢出会导致更多错误,例如:

#define synchronized(lock) \
   if (auto_lock lock_##__LINE__(lock))

synchronized(p)
{
   // Now, the C++ code is synchronized using p as a mutex
}

事实上,微软被烧得太厉害了,因为他们在Visual C ++ 2008中添加了一个警告,贬低了strcpy !!!

如何避免大多数错误?

针对此错误的第一个“保护”是“转向”表达式:因为您无法为常量赋值,所以:

void doSomething(char * p)
{
   strcpy(p, "Hello World, how are you \?\n") ;
}

void doSomethingElse()
{
   char buffer[16] ;
   doSomething(buffer) ;
}

无法编译。

但是我发现这个解决方案很糟糕,因为它试图隐藏在风格背后的应该是一般的编程实践,即:任何不应该改变的变量应该是不变的。

例如,而不是:

if(0 = p) // ERROR : It should have been if(0 == p). WON'T COMPILE !

尝试“const”尽可能多的变量将使您避免大多数拼写错误,包括那些不在“if”表达式中的错误:

void doSomething(char * p)
{
   if(p == NULL) // POSSIBLE TYPO ERROR
      return ;

   size_t length = strlen(p) ;

   if(length == 0) // POSSIBLE TYPO ERROR
      printf("\"%s\" length is %i\n", p, length) ;
   else
      printf("the string is empty\n") ;
}

当然,它并不总是可能的(因为一些变量确实需要改变),但我发现我使用的大多数变量都是常量(我一直初始化它们,然后只读它们)。

结论

通常,我会看到使用if(0 == p)表示法的代码,但没有使用const-notation。

对我来说,就像有一个垃圾桶用于可回收物品,另一个用于不可回收物品,然后最后将它们放在同一个容器中。

所以,不要嘲笑一种简单的习惯习惯,希望它能让你的代码变得更好。它不会。尽可能使用语言结构,在这种情况下,在可用时使用if(0 == p)表示法,并尽可能使用const关键字。

答案 5 :(得分:7)

'if(0 = x)'成语旁边是无用的,因为当双方都是变量('if(x = y)')和大多数(全部?)的时候你应该是没有帮助使用常量变量而不是幻数。

我从不使用这个成语的其他两个原因,恕我直言,它使代码不易读取,说实话,我发现单个'='是非常小的罪恶的根源。如果你彻底测试你的代码(显然我们都这样做),这种语法错误很快就会出现。

答案 6 :(得分:5)

许多编译器会检测到这一点并发出警告,但前提是警告级别设置得足够高。

例如:

 ~> gcc -c -Wall foo.c
foo.c: In function ‘foo’:
foo.c:5: warning: suggest parentheses around assignment used as truth value

答案 7 :(得分:5)

迭代的标准C语言:

list_elem* curr;
while ( (curr = next_item(list)) != null ) {
  /* ... */
}

答案 8 :(得分:4)

它取决于language.Java将其标记为错误,因为在if括号内只能使用布尔表达式(除非两个变量都是布尔值,在这种情况下赋值也是布尔值)。

在C语言中,测试malloc返回的指针是一种非常常见的习惯用法,或者在fork之后我们处于父进程或子进程中:

if ( x = (X*) malloc( sizeof(X) ) {
   // malloc worked, pointer != 0

if ( pid = fork() ) {
   // parent process as pid != 0

如果您要求它,C / C ++编译器会以足够高的警告级别发出警告,但不能将其视为语言允许的错误。除非,然后再次要求编译器将警告视为错误。

每当与常量进行比较时,一些作者建议使用测试常量 == 变量,以便编译器检测用户是否忘记了第二个等号。

if ( 0 == variable ) {
   // the compiler will complaint if you mistakenly 
   // write =, as you cannot assign to a constant

无论如何,您应该尝试使用尽可能高的警告设置进行编译。

答案 9 :(得分:4)

这真的是一个常见错误吗?当我自己学习C时,我了解了这一点,作为一名教师,我偶尔会警告我的学生并告诉他们这是一个常见错误,但我很少在实际代码中看到它,即使是初学者。当然不会比其他操作员错误更频繁,例如写“&amp;&amp;”而不是“||”。

因此,编译器没有将其标记为错误(除非它是完全有效的代码)的原因可能是不是很多邪恶的根源。

答案 10 :(得分:3)

我认为C和C ++语言设计人员注意到禁止它没有真正的用途,因为

  • 无论如何,编译器可以发出警告
  • 禁止它会在语言中添加特殊情况,并删除可能的功能。

允许它没有复杂性。 C ++只是说需要隐式转换为bool的表达式。在C中,有其他答案详述的有用案例。在C ++中,他们更进一步,并允许这一个:

if(type * t = get_pointer()) {
    // ....
}

实际上,t的范围仅限于if及其实体。

答案 11 :(得分:2)

作为条件的赋值是合法的C和C ++,并且任何不允许它的编译器都不是真正的C或C ++编译器。我希望任何不能与C语言明确兼容的现代语言(如C ++那样)都会认为它是错误的。

在某些情况下,这允许简洁的表达式,例如惯用语while (*dest++ = *src++);来复制C中的字符串,但总体来说它不是很有用,我认为它在语言设计中是一个错误。根据我的经验,这很容易犯这个错误,并且在编译器没有发出警告时很难发现。

答案 12 :(得分:2)

我个人认为这是最有用的例子。

假设您有一个函数read(),它返回读取的字节数,您需要在循环中使用它。使用

要简单得多
while((count = read(foo)) > 0) {
    //Do stuff
}

而不是尝试从循环头中获取分配,这将导致像

这样的事情
while(1) {
    count = read(foo);
    if(!(count > 0))
        break;
    //...
}

count = read(foo);
while(count > 0) {
    //...
    count = read(foo);
}

第一个构造感觉很尴尬,第二个构造以令人不愉快的方式重复编码。

当然,除非我为此错过了一些精彩的成语......

答案 13 :(得分:2)

在一个条件语句中,赋值运算符有很多很好的用途,并且总是看到每个人都有关于每一个的警告是很痛苦的。什么是好的将是你的IDE中的一个功能,让你突出显示所有使用赋值而不是相等检查的地方 - 或 - 在你写这样的东西之后:

if (x = y) {
然后那条线闪烁了几次。足以告诉你,你已经做了一些不是完全标准的东西,但不是那么烦人。

答案 14 :(得分:2)

部分原因与个人风格和习惯有关。我不可知读取if(kConst == x)或if(x == kConst)。我没有在左边使用常量,因为从历史上看,我没有犯这个错误,而且我按照我的说法编写代码或想要阅读它。我认为这是个人决定,作为个人责任的一部分,成为一名自我意识,改进工程师。例如,我开始分析我正在创建的bug的类型,并开始重新设计我的习惯,以便不制作它们 - 类似于左边的常量,只是与其他东西。

也就是说,历史上编译器警告非常糟糕,尽管多年来这个问题已经众所周知,但直到80年代后期才在生产编译器中看到它。我还发现,处理可移植的项目有助于清理我的C,包括不同的编译器和不同的口味(即警告)和不同的细微语义差异。

答案 15 :(得分:2)

 if ((k==1) || (k==2)) is a conditional
 if ((k=1)  || (k=2) ) is BOTH a conditional AND an assignment statement
  • 这是解释*

与大多数语言一样,C按运算符优先顺序在最内层到最外层工作。

首先,它尝试将k设置为1,然后成功。

Result: k = 1 and Boolean = 'true'

下一步:它将k设置为2,然后成功。

Result: k = 2 and Boolean = 'true'

下一步:它评估(true || true)

Result: k still = 2, and Boolean = true

最后,它解决了条件:If(true)

  Result: k = 2 and the program takes the first branch.

在将近30年的编程中,我没有看到使用此构造的正当理由,但如果存在,则可能与故意混淆代码的需要有关。

当我们的一个新人遇到问题时,这是我要寻找的事情之一,同时不会在字符串上粘贴终结符,并将调试语句从一个地方复制到另一个地方而不是更改'%我要'%s'来匹配他们正在倾销的新领域。

这在我们的商店中很常见,因为我们经常在C和Oracle PL / SQL之间切换; if(k = 1)是PL / SQL中的正确语法。

答案 16 :(得分:2)

尝试观看

if( life_is_good() )
    enjoy_yourself();

作为

if( tmp = life_is_good() )
    enjoy_yourself();

答案 17 :(得分:1)

在我15年的发展过程中,我只有这个错字。我不会说这是我要注意的事项列表中的首要问题。无论如何我也避免使用那个构造。

另请注意,某些编译器(我使用的编译器)会对该代码发出警告。警告可以被视为任何编译器的错误。它们也可以被忽略。

答案 18 :(得分:1)

将常数放在比较的左侧是防御性编程。当然,你永远不会犯这样一个愚蠢的错误,忘记额外的'=',但是谁知道其他人。

答案 19 :(得分:1)

D编程语言将此标记为错误。为了避免以后想要使用该值的问题,它允许类似C ++的声明类似于for循环。

if(int i = some_fn())
{
   another_fn(i);
}

答案 20 :(得分:1)

编译器不会将其标记为错误,因为它是有效的C / C ++。但你可以做的(至少使用Visual C ++)是打开警告级别,以便将其标记为警告,然后告诉编译器将警告视为错误。无论如何,这是一个很好的做法,因此开发人员不会忽略警告。

如果你实际上意味着=而不是==那么你需要更加明确它。 e.g。

if ((x = y) != 0)

理论上,你应该能够做到这一点:

if ((x = y))

覆盖警告,但似乎并不总是有效。

答案 21 :(得分:1)

C / C ++中的“低级”循环结构非常常见,例如副本:

void my_strcpy(char *dst, const char *src)
{
    while((*dst++ = *src++) != '\0') { // Note the use of extra parentheses, and the explicit compare.
        /* DO NOTHING */
    }
}

当然,for循环的赋值很常见:

int i;
for(i = 0; i < 42; ++i) {
    printf("%d\n", i);
}

我确信在if语句的 之外阅读作业更容易:

char *newstring = malloc(strlen(src) * sizeof(char));
if(newstring == NULL) {
    fprintf(stderr, "Out of memory, d00d!  Bailing!\n");
    exit(2);
}

// Versus:

if((newstring = malloc(strlen(src) * sizeof(char))) == NULL) // ew...

确保分配明显,thuogh(与前两个示例一样)。不要隐藏它。

至于意外使用......这种情况不会发生在我身上。一个常见的保护措施是将您的变量(左值)放在比较的右侧,但这对以下内容不起作用:

if(*src == *dst)

因为==的两个oprands都是左值!

至于编译器......谁能怪他们?编写编译器很困难,你应该为编译器编写完美的程序(还记得GIGO吗?)。有些编译器(肯定是最知名的)提供了内置的lint样式检查,但这当然不是必需的。有些浏览器不会验证HTML和Javascript的每个字节,所以编译器为什么会这样?

答案 22 :(得分:1)

有几种策略可以帮助发现这一点......一个是丑陋的,另一个通常是一个宏。这实际上取决于你如何阅读你的口语(从左到右,从右到左)。

例如:

if ((fp = fopen("foo.txt", "r") == NULL))

Vs的:

if (NULL == (fp = fopen(...)))

有时可以更容易地读/写(首先)您的测试,这使得更容易发现任务与测试。然后引入大多数讨厌这种风格的comp.lang.c人。

所以,我们引入assert():

#include <assert.h>

...
fp = fopen("foo.txt", "r");
assert(fp != NULL);
...

当你处于一个错综复杂的条件中间或者结束时,断言()是你的朋友。在这种情况下,如果FP == NULL,则引发abort()并传送违规代码的行/文件。

所以如果你oops:

if (i = foo)

insted of

if (i == foo)

接着是

assert (i > foo + 1)

......你很快就会发现这样的错误。

希望这会有所帮助:)

简而言之,在调试时,反转参数有时会有所帮助.assert()是你的长寿朋友,可以在生产版本的编译器标志中关闭。

答案 23 :(得分:1)

你问为什么它有用,但不断质疑人们提供的例子。它很有用,因为它很简洁。

是的,所有使用它的例子都可以重写 - 作为更长的代码片段。

答案 24 :(得分:0)

RegEx样本

RegEx r;

if(((r = new RegEx("\w*)).IsMatch()) {
   // ... do something here
}
else if((r = new RegEx("\d*")).IsMatch()) {
   // ... do something here
}

指定一个值测试

int i = 0;
if((i = 1) == 1) {
   // 1 is equal to i that was assigned to a int value 1
}
else {
   // ?
}

答案 25 :(得分:0)

正如其他答案中所指出的,有些情况下,在条件中使用赋值可以提供简短但可读的代码,可以满足您的需求。此外,许多最新的编译器会警告您,如果他们看到他们期望条件的任务。 (如果你是开发的零警告方法的粉丝,你会看到这些。)

我开发的一种习惯让我不被这种(至少在C-ish语言中)所困扰,如果我正在比较的两个值中的一个是常数(或者不是合法的左值),我把它放在比较器的左侧:if (5 == x) { whatever(); }然后,如果我不小心输入if (5 = x),代码将无法编译。

答案 26 :(得分:0)

在实践中我不这样做,但是要做一个好的建议:

if ( true == $x )

如果您遗漏了等号,将$x分配给true显然会返回错误。

答案 27 :(得分:0)

这就是为什么写作更好:

0 == CurrentItem

而不是:

CurrentItem == 0

如果您键入=而不是==。

,编译器会发出警告