三目运算符中的未定义行为

4

我必须分享这个:

我在以下条件运算符的微不足道错误上卡了整整两天。

这是一个简单的修正,但我想知道:

  1. 为什么有错的代码编译通过了?
  2. 这个错误在做什么?
  3. 为什么这么难以追踪到它?

有错的代码:

 std::map<int, some_class>   my_map;
int key_ctr = 0;
 //...
std::map<int, some_class>::iterator it_next   =  
                                            key_ctr == 0  ?
                                 it_next  =  my_map.begin()      // BUG!!!
                                 :
                                 it_next  =  --my_map.end();     // BUG!!!!

  // .....

显然,我错误地编写了条件运算符。当我最终找到并纠正这个错误时,一切都完全正常:
正确代码:
 std::map<int, some_class>   my_map;
int key_ctr = 0;
 //...
std::map<int, some_class>::iterator it_next   =  
                                            key_ctr == 0  ?
                                 my_map.begin()              // CORRECTED!
                                 :
                                 --my_map.end();             // CORRECTED!

当我的程序接近有问题的部分时,它就像陷入了无限循环一样卡住了。使用 valgrind 运行后,我得到了类似以下的信息:

....
==24570== Warning: set address range perms: large range [0x1a7731000, 0x1c5f79000) (defined)
==24570== Warning: set address range perms: large range [0x1c5f79000, 0x1e47c1000) (defined)
==24570== Warning: set address range perms: large range [0x1e47c1000, 0x203009000) (defined)
==24570== Warning: set address range perms: large range [0x203009000, 0x221851000) (defined)
.....
==3733== More than 10000000 total errors detected.  I'm not reporting any more.

这完全没有帮到我,反而让我误入歧途(我以为是堆上分配了太多内存)。

再次提醒:

  1. 为什么有错误的代码还能编译通过?
  2. 错误代码在做什么?
  3. 为什么这个错误难以追踪?

谢谢。


2
标题有点误导人,问题不在于三元运算符,而在于使用未初始化的变量。 - David Rodríguez - dribeas
严重缺少括号,确诊了。不开玩笑。 - Deer Hunter
@DavidRodríguez-dribeas 哪个是未初始化的变量? - john
2
我喜欢这部分 -“为什么错误的代码编译了?” 如果您设计出一种不会编译错误代码的编译器,那么您将成为亿万富翁。 - Luchian Grigore
@DavidRodríguez-dribeas 哦,是的,在这种情况下绝对是可能的,但该语句听起来更为普遍 :) - Luchian Grigore
显示剩余3条评论
2个回答

7

1) 编译器只检查语法和形式正确的程序,逻辑错误需要自己发现。

2) 这是未定义行为,原因如下:


whatever_non_POD_type it_next = condition ? it_next = whatever1 : 
                                            it_next  = whatever2; 

实际上,您可以缩小范围到:
It it_next = it_next = whatever;

不论"whatever"是什么,重要的是在完整语句执行之前(到达;符号),it_next没有被初始化。这就是

It it_next = ...

这部分试图做的事情是什么?但首先,它试图评估右边的内容。即it_next = whatever。这会调用it_next.operator = (whatever)。所以你在未初始化对象上调用了成员函数。这是未定义的行为。Ta-da!!!

3) 所有未定义的行为都难以追踪。这就是为什么您至少应该知道常见情况的原因。


什么是未定义行为?it_next被三元运算符中的两个表达式赋值,然后进行了自我赋值。我没有看到未定义行为。 - Praetorian
1
@LuchianGrigore 感谢您的解释。这真的很棘手。我不奇怪 OP 感到有点委屈。非 POD 部分是我忽略的。 - john
好的,gcc也会抱怨 int a = a = 10;。但在这种情况下,情况要糟糕得多,因为迭代器是一个UDT,而不是一个原始指针(这正是我当时所想的)。 - Praetorian

7

3 为什么追踪它如此艰难?

因为你没有开启编译器警告?

$ g++ -std=c++0x -pedantic -Wall -Werror -g    m.cc   -o m
cc1plus: warnings being treated as errors
m.cc:10: error: operation on ‘it_next’ may be undefined
m.cc: In function ‘void __static_initialization_and_destruction_0(int, int)’:
m.cc:6: error: operation on ‘it_next’ may be undefined
make: *** [m] Error 1

尽管我的程序相当庞大,涉及大量库。 因此,-Werror将导致来自外部库(我无法控制的)的警告使编译崩溃。 但我无法修复这些问题,因为它们在外部库中。 - cmo
1
@Matthew Clang有一个很棒的-Wno-system-headers命令行参数,它可以使其在任何指定为-isystem /path/to/library的目录中生成来自文件的警告。我希望其他编译器也有类似的功能。 - Claudio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接