【问题标题】:Is dividing by zero accompanied with a runtime error ever useful in C++?除以零并伴随运行时错误在 C++ 中有用吗?
【发布时间】:2011-10-17 06:51:42
【问题描述】:

根据 C++ 标准 (5/5) 除以零是未定义的行为。现在考虑这段代码(有很多无用的语句来防止编译器优化代码):

int main()
{
    char buffer[1] = {};
    int len = strlen( buffer );
    if( len / 0 ) {
        rand();
    }
}

Visual C++ 像这样编译if-statement:

sub         eax,edx 
cdq 
xor         ecx,ecx 
idiv        eax,ecx 
test        eax,eax 
je          wmain+2Ah (40102Ah) 
call        rand

很明显,编译器看到代码要除以零——它使用xor x,x 模式将ecx 清零,然后在整数除法中为第二个操作数提供服务。此代码在运行时肯定会触发“整数除以零”错误。

IMO 此类情况(当编译器知道代码将始终除以零时)值得出现编译时错误 - 标准并不禁止这种情况。这将有助于在编译时而不是在运行时诊断此类情况。

然而,我与其他几位开发人员交谈,他们似乎不同意 - 他们的反对意见是“如果作者想除以零来... emm... 测试错误处理怎么办?”

在没有编译器意识的情况下故意除以零并不难 - 使用 __declspec(noinline) Visual C++ 特定函数装饰器:

__declspec(noinline)
void divide( int what, int byWhat )
{
    if( what/byWhat ) {
       rand();
    }
}

void divideByZero()
{
    divide( 0, 0 );
}

它更具可读性和可维护性。当他“需要测试错误处理”并且在所有其他情况下都有很好的编译时错误时,可以使用该功能。

我错过了什么吗?是否有必要允许发射编译器知道除以零的代码?

【问题讨论】:

  • 我假设您的问题仅适用于基本类型。我说的对吗?
  • @R. Martinho Fernandes:是的,让我们认为它仅限于基本类型。
  • 我看到的问题是,实际上只有少数错误会在编译时被捕获,其余的是真正的运行时错误。我认为没有另一类具有这种二元性的错误。这有点奇怪。
  • 另外我希望有一个警告,以便可以根据向后可比性/测试等的需要打开/关闭它
  • 我不相信编译器会因为存在未定义的行为而拒绝一个格式良好的程序,但显然它可以。 1.3,在未定义行为的定义下:“允许的未定义行为范围从......到终止翻译......(发布诊断消息)。”它在注释中而不是适当的规则中,但对我来说已经足够了。但是,我仍然不希望看到编译器对这个特定选项过于激进。

标签: c++ visual-c++ compiler-construction divide-by-zero


【解决方案1】:

可能有一些代码在从未调用的函数中意外被零除(例如,由于某些特定于平台的宏扩展),这些代码将不再与您的编译器一起编译,从而使您的编译器变得不那么有用。

此外,我在实际代码中看到的大多数除以零错误都是依赖于输入的,或者至少不适合静态分析。也许不值得进行检查。

【讨论】:

  • 好的,但是那些功能会被破坏,不是吗?
  • 最后一行对我来说似乎最重要。如果 99% 的时间编译器都不知道它会发生,那么在剩下的情况下,你真正获得了多少?
  • 我认为最后一段就是答案。编译器已经足够慢了。当它几乎永远不会帮助用户时,是否值得再检查一个错误?几乎所有除以零都取决于输入,那么通过在编译时检查它可以获得多少?
  • @sharptooth:也许,也许不是。如果它们从未被调用的原因是也是它们包含被零除的原因怎么办?它更像是关于缺少返回语句的警告......如果它只发生在i < 0,但i < 0 意味着我调用的某个函数将引发我无意捕获的异常,那么该函数非常好形成并且硬错误将是不必要的侵略性..
【解决方案2】:

除以 0 是未定义的行为,因为它可能会在某些平台上触发硬件异常。我们都希望有一个性能更好的硬件,但由于没有人认为具有 -INF/+INF 和 NaN 值的整数是合适的,所以这毫无意义。

现在,因为这是未定义的行为,可能会发生有趣的事情。我鼓励您阅读 Chris Lattner 关于未定义行为和优化的文章,我将在这里简单举个例子:

int foo(char* buf, int i) {
  if (5 / i == 3) {
    return 1;
  }

  if (buf != buf + i) {
    return 2;
  }

  return 0;
}

因为i被用作除数,所以它不为0。因此,第二个if微不足道,可以优化掉。

面对这样的转变,任何希望除以 0...理智行为的人都会大失所望。

【讨论】:

  • 恐怕在这个sn-p中找不到assert。
  • 嗯,第二个if 语句是非常正确的,可以优化掉。我猜这就是他的意思。
  • @sharptooth:我认为他指的是“if (buf != buf + i)”测试。因为 i 不能为零(或者我们在 UB-land),所以 test 是微不足道的,它和它后面的所有东西都可以合法地替换为“return 2”。
  • @jalf: 是的,对不起,我改变了例子:x
  • 很好的例子。更好的是,整个函数可以简单地替换为return 2' since there is no integer value for i`,其中5/i == 3
【解决方案3】:

在整数类型(int, short, long 等)的情况下,我想不出有任何用处来故意除以零。

但是,对于 IEEE 兼容硬件上的浮点类型,显式除以零非常有用。您可以使用它来生成正负无穷大 (+/- 1/0),而不是数字 (NaN, 0/0) 值,这很有帮助。

在排序算法的情况下,您可以使用无穷大作为初始值,表示大于或小于所有可能的值。

出于数据分析的目的,您可以使用 NaN 来指示丢失或无效的数据,然后可以对其进行妥善处理。例如,Matlab 使用显式 NaN 值来抑制绘图中的缺失数据等。

虽然您可以通过宏和 std::numeric_limits(在 C++ 中)访问这些值,但能够自己创建它们很有用(并且可以让您避免大量“特殊情况”代码)。它还允许标准库的实现者避免求助于骇客(例如手动组装正确的 FP 位序列)来提供这些值。

【讨论】:

    【解决方案4】:

    如果编译器检测到除以 0,则编译器错误绝对没有错。您与之交谈的开发人员是错误的-您可以将该逻辑应用于每个编译错误。除以 0 是没有意义的。

    【讨论】:

    • ...或者您可以只投票赞成,并说在可通过的零分答案上没有 cmets 的反对票是为傻瓜... +1
    【解决方案5】:

    在编译时检测除以零是您希望成为编译器警告的事情。这绝对是个好主意。

    我不喜欢 Microsoft Visual C++,但 G++ 4.2.1 做这样的检查。尝试编译:

    #include <iostream>
    
    int main() {
        int x = 1;
        int y = x / 0;
        std::cout << y;
        return 0;
    }
    

    它会告诉你:

    test.cpp: In function ‘int main()’:
    test.cpp:5: warning: division by zero in ‘x / 0’
    

    但考虑到它,错误是一个滑坡,精明的人知道不要花费太多业余时间攀登。想想我写的时候为什么G++没有什么可说的:

    int main() {
        while (true) {
        }
        return 0;
    }
    

    你认为它应该编译那个,还是给出一个错误?它应该总是发出警告吗?如果您认为它必须干预所有此类情况,我急切地等待您编写的编译器副本,它只编译保证成功终止的程序! :-)

    【讨论】:

    • MSVC 确实会发出警告。 warning C4723: potential divide by 0
    猜你喜欢
    • 2015-06-05
    • 2013-04-06
    • 2012-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-30
    相关资源
    最近更新 更多