【问题标题】:Stack overflow error when the range of numbers is over 40数字范围超过 40 时堆栈溢出错误
【发布时间】:2016-01-14 22:36:31
【问题描述】:

该程序应该提示输入一系列数字,然后为该范围内的数字吐出杂耍序列,但每当我输入超过 40 的范围时,我都会收到堆栈溢出错误,不知道为什么谢谢 “juggler_seq.exe 中 0x77354A3E (ntdll.dll) 处的未处理异常:0xC00000FD:堆栈溢出(参数:0x00000001、0x00092FF4)。”

// juggler_seq.cpp : 定义控制台应用程序的入口点。 //

#include "stdafx.h"
// Example program
#include <iostream>
#include <string>
#include <math.h>
#include <sstream>
#include <list>
template <typename T>

std::string to_string(T value){
    std::ostringstream os;
    os << value;
    return os.str();
}

std::string jugglers(long int n, std::string ans = ""){
    std::string num;
    if (n == 1){
        //checks for base case if 1 returns the seqence of numbers
        return ans + "1";
    }
    else{
        //checks for even odd
        if (n % 2 == 0){
            ans = ans + to_string(n) + ",";
            //ans now adjusted to include most recent number calculated in the sequence
            return jugglers(long int(pow(n, (1.0 / 2.0))), ans);
            //passes the most recent number into the funtion again until the sequence converges to 1
            //also passes the string ans with all previous numbers in sequence to keep track of numbers in the sequence
        }
        else{
            num = to_string(n);
            ans = ans + to_string(n) + ",";
            return jugglers(long int(pow(n, (3.0 / 2.0))), ans);
        }
    }
}

int main()
{
    int s, e;
    int high = 0;
    std::string usrstr;
    std::list<std::string> ans;
    std::list<std::string>::iterator it;
    std::string n;
    std::stringstream ss;
    std::cout << "whats the starting point: ";
    getline(std::cin, usrstr);
    std::stringstream(usrstr) >> s;
    std::cout << "\nwhats the end point: ";
    getline(std::cin, usrstr);
    std::stringstream(usrstr) >> e;
    for (long int y = s; y != e + 1;y++){

        ans.push_back(jugglers(y));
    }
    std::string com = "";
    int count = 0;
    int ref = 0;
    for (it = ans.begin(); it != ans.end(); it++){
        std::cout << *it<<std::endl;
        std::string a = *it;
        if (a.size()>com.size()){
            com = a;
            ref = count;
        }
        count += 1;

    }
    std::cout << "the ref is: " << ref + s << " the answer is : " << com << "\n";
    return 0;
} 

【问题讨论】:

  • 通常堆栈溢出是由于超出数组末尾或执行太多递归调用引起的。您的第一步是使用内置调试器或 gdb 缓慢而痛苦地逐步完成您自己的代码,并找出它发生的位置以及原因。 :-) 玩得开心!这就是编程的全部意义所在。好吧,有一个很酷的程序也有帮助。但是找到这些错误也很有趣。 :-)
  • 使用您最喜欢的调试器单步调试代码并检查 a) 是否一切正常,b) 递归深度是否变大,c) n 是否变大。
  • 您使用n^1/2 递归调用偶数,使用n^3/2 调用奇数。在堆栈溢出之前,它们不会在奇数和偶数之间翻转吗?
  • juggler sequence 可能会导致一些非常大的数字。例如,从 37 开始,达到的最大值为 24906114455136。因此,系统上的 long 至少需要为 64 位。即使这样,我也不认为使用pow 会起作用,因为double 只有大约16 位的精度。

标签: c++ runtime-error overflow


【解决方案1】:

我看到的问题:

  1. 语法。

    return jugglers(long int(pow(n, (1.0 / 2.0))), ans);
    

    应该是编译器错误。正确的方法是:

    return jugglers((long int)(pow(n, (1.0 / 2.0))), ans);
    

    或者,更好的是,

    return jugglers(static_cast<long int>(pow(n, (1.0 / 2.0))), ans);
    

    同样的问题存在于行中

    return jugglers(long int(pow(n, (3.0 / 2.0))), ans);
    
  2. 整数溢出

    当调用pow 返回的数字变得太大而无法容纳long int 时,下一次调用pow 会导致错误。在我的测试环境中,数字 163 导致了这个问题。在某个时刻n 的值达到-9223372036854775808 并停留在那里,导致堆栈溢出。这与LONG_MIN-9223372036854775807 非常接近。

  3. 我看不出n 不会在奇数和偶数之间翻转的任何原因。这也会导致堆栈溢出。在前面的例子中,n 的值由于奇数序列而不断增加。

我能够通过将参数类型更改为 unsigned long 并将第一个检查更改为:

if (n <= 1){

但是,这只是一个技巧。当n 达到整数溢出点时,任何后续代码都会受到未定义行为的影响。

在我的测试用例中,163 的输出是:

163,2081,94931,29249071,158186025767,62914706160224992,250828041,3972502044577,7917648072381635584,2813831564,53045,12217059,42702176063,8824193242915619,1

从输出中可以看出,终止相当突然,不合逻辑。

【讨论】:

    【解决方案2】:

    您的代码没问题。问题来自长整数溢出。

    我在ideone 上对其进行了一些额外的显示测试:n 到 36 时,递归函数会收敛。使用 37,您会溢出 long int 可以容纳的最大值。然后你的递归被破坏,没有收敛,这会触发堆栈溢出。

    如果您使用long long,它将起作用(直到下一次容量溢出):

    std::string jugglers(long long int n, std::string ans = ""){
        ...
            return jugglers((long long int)(pow(n, (1.0 / 2.0))), ans);
        ...
            return jugglers((long long int)(pow(n, (3.0 / 2.0))), ans);
    }
    

    防止数值溢出导致堆栈溢出:

    如果你想让它更健壮,你可以尝试预测溢出:

    • jugglers() 的偶数分支上,您将使用较小的数字进行递归调用(因为将幂提高到 1/2 意味着平方根,这里 n>1)。所以不会有什么不好的事情发生。
    • 在奇数分支上,您将使用大于 n 的数字进行递归调用。如果存在整数溢出,则提高 ot 的 3/2 次方将计算出一个小于当前值的数字。很容易监控这种情况并停止并显示错误消息:

          ... 
          long long int r = (long long int)(pow(n, (3.0 / 2.0)));
          if (r>n)
              return jugglers(r, ans);
          else {
              std::cout<< "Integer overflow at "<<n<<std::endl; 
              return ""; 
          }
          ...
      

    这种方法在这里有效,但不是那么干净,因为您执行的操作会导致数字溢出并在之后检测到它。

    另一种方法是在提高功率之前检查 n 是否导致问题:

           if (n>pow(std::numeric_limits<long long int>::max(), 2.0/3.0)) {
                std::cout<<std::endl<<"Fatal error for "<<n<<std::endl; 
                return "";
            }
    

    【讨论】:

    • @user3386109 有趣的评论。不过,您假设 IEC559 编码。您应该避免使用硬编码的数字,而更喜欢(1&lt;&lt;numeric_limits&lt;double&gt;::digits)(请参阅here)。另请注意,如果更大的整数是 2 的幂,则可以毫无损失地表示它们(但在这个专用于奇数的代码分支中,情况并非如此;-))。
    猜你喜欢
    • 2014-10-16
    • 1970-01-01
    • 2016-05-23
    • 2015-09-27
    • 2010-09-10
    • 2013-09-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多