神秘的堆栈溢出

Mysterious Stack Overflow

本文关键字:栈溢出 堆栈      更新时间:2023-10-16

所以我写了一个程序,利用欧几里得算法来查找 2 个整数的 GCD。

用户输入一个 int (n(,然后程序获取 8 和 n 之间的每个可能的整数组合,找到它们各自的 GCD(递归(,并打印哪些 GCD 计算需要最多的模运算。

我让程序工作,但我在 n=50 左右出现堆栈溢出,它至少需要工作到 3000。

我已经查看了一段时间的代码,但找不到问题。

#include<iostream>
#include <math.h>
using namespace std;
int cost, gcd, greatestCost, n, beginningA, beginningB, finalA, finalB, finalGCD, iteration;
void findGCD(int num1, int num2, int startingCost) {
    //findGCD
    //finds GCD of every combination (a,b) from i to n
    //prints those with the greatest number of modulus operations
    int a = num1;
    int b = num2;
    cost = startingCost;
    cost++;
    if (b%a > 0) {
        //cout << "gcd(" << b << "," << a << ") = ";
        findGCD(b%a, a, cost);
    }       
    else {
            gcd = a;
            if (cost > greatestCost) {
                greatestCost = cost;
                finalA = beginningA;
                finalB = beginningB;
                finalGCD = gcd;
            }
            //cout << "gcd(" << b << "," << a << ") = " << gcd << " With a  cost of: " << cost << endl;
            //do next iteration (2,8), (3,8) etc...
            if (++beginningA <= beginningB) {           //beginning A goes from 1-i first
               findGCD(beginningA, beginningB, 0);
            }
            else {
                    if (beginningA <= n) {      //begin next cycle with new b value (1,9), (2,9) while b <= n
                        beginningA = 1;                     //reset to 1 so it will increment from 1-i again
                        cout << "At i=" << iteration++ << "; gcd(" << finalA << "," << finalB << ") = " << finalGCD << 
                                " took " << greatestCost << " modulus operations" << endl;
                        findGCD(beginningA, ++beginningB, 0);           
                    }
                    else    //When it tries to continue iterating with a number > n
                            //print the last, most intensive, iteration and stop
                        cout << "At i=" << iteration++ << "; gcd(" << finalA << "," << finalB << ") = " << finalGCD << 
                        " took " << greatestCost << " modulus operations" << endl;
                  }
        }
}
int main() {
    greatestCost = 0;       //cost of the iteration with the most modulus operations
    beginningA = 1;         
    beginningB = 8;
    iteration = 8;
    cout << "Enter an integer greater than 8 " << endl; //receive n from user
    cin >> n;
    if (n <= beginningB)                                //begin GCD search, granted user input > 8
        cout << "Error!!! integer must be greater than 8";
    else
        findGCD(beginningA, beginningB, 0);     //algorithm begins at (1,8) 
    return 0;
}

在这一点上,我唯一能想到的问题就是我在C++中做了一些我不应该做的事情(我是C++新手,是从 java 转移过来的(

示例输出

我尝试过的事情:

  • 将 GCD 函数拆分为 2
  • 仅通过函数传递引用

首先,您的解释不清楚,从您的代码中,我了解到,对于每个8<=i<=n,您都会采取所有可能x, y y<=ix<=y的位置,并计算哪个gcd需要最多的步骤。

我已经重写了您的代码,以便 findGCD 只找到 2 个数字的 gcd,同时增加一些全局成本变量。

#include<iostream>
#include <math.h>
using namespace std;
int cost, gcd, greatestCost, n, beginningA, beginningB, finalA, finalB, finalGCD, iteration;
int findGCD(int a, int b) {
    cost++;
    if (b%a > 0)
        return findGCD(b%a, a);
    else
        return a;
}
int main() {
    greatestCost = 0;       //cost of the iteration with the most modulus operations
    beginningA = 1;         
    beginningB = 8;
    iteration = 8;
    cout << "Enter an integer greater than 8 " << endl; //receive n from user
    cin >> n;
    if (n <= beginningB)                                //begin GCD search, granted user input > 8
        cout << "Error!!! integer must be greater than 8";
    else {
        for ( int i = beginningB; i <= n; i++ ) {
            int greatestCost = 0, gcd0 = 1, i0 = 0, j0 = 0;
            for ( int t = beginningB; t <= i; t++ )
                for ( int j = 1; j <= t; j++ ) {
                    cost = 0;
                    int gcd = findGCD(j, t);
                    if ( cost > greatestCost ) {
                        greatestCost = cost;
                        gcd0 = gcd;
                        i0 = t;
                        j0 = j;
                    }
                }
            cout << "At i=" << i << "; gcd(" << j0 << "," << i0 << ") = " << gcd0 <<
                                     " took " << greatestCost << " modulus operations" << endl;
        }
    }
    return 0;
}

你得到的堆栈溢出是由使用太深的递归调用引起的:每次调用函数时,都会在(调用(堆栈中创建一个新的堆栈帧(保存局部变量、参数和可能的其他东西(。仅当从函数返回(通常或通过异常(时,才会释放此帧。但是对于递归调用,您不会在从第二个函数调用返回之前从第一个函数调用返回,而第二个函数调用又只在第三个函数调用之后返回,依此类推。因此,堆栈帧堆积在堆栈上,通常大小约为 8 kB,直到堆栈的所有可用内存都被使用:这就是堆栈溢出(你放了太多,因此它溢出(。

这可以通过使用迭代(使用循环(来解决:

一个外部值从 8 递增到

用户提供的最大值,另一个内部值从 0 递增到外部循环的当前迭代变量的值。这为您提供了要操作的所有值对。

计算最大公约数及其成本应分解为函数。

剩下的唯一事情是从循环中调用该函数,以及如何跟踪最大值。

#include <iostream>
#include <vector>
#include <utility>
using namespace std;
unsigned gcd(unsigned a, unsigned b, unsigned * const cost) {
  if (cost) {
    *cost = 0;
  }
  while (b != 0) {
    auto const rest = a % b;
    if (cost) {
      ++(*cost);
    }
    a = b;
    b = rest;
  }
  return a;
}
int main() {
  unsigned const n = 3500;
  unsigned greatestCost = 0;
  vector<pair<unsigned, unsigned>> pairs;
  for (unsigned b = 8; b <= n; ++b) {
    for (unsigned a = 0; a <= b; ++a) {
      unsigned cost;
      gcd(a, b, &cost);
      if (cost == greatestCost) {
        pairs.emplace_back(a, b);
      } else if (cost > greatestCost) {
        pairs.clear();
        pairs.emplace_back(a, b);
        greatestCost = cost;
      }
    }
  }
  cout << "Greatest cost is " << greatestCost << " when calculating the GCD of " << endl;
  for (auto const & p : pairs) {
    cout << "(" << p.first << ", " << p.second  << ")" << endl;
  }
  return 0;
}

(直播(

特别要注意的是,我没有使用任何全局变量。


以上可能会让你觉得递归是一种不可用、无用的构造。事实并非如此。许多算法使用递归最清晰地表达。当将递归调用作为最后一个语句时,可以使用称为尾部调用优化的优化: 然后被调用的函数重用调用函数的堆栈帧,因此不再使用任何内存。

不幸的是,由于各种原因,这种优化在像C++这样的语言中实现非常棘手。

其他语言,主要是函数式语言,使用它,因此也使用递归而不是循环。这种语言的一个例子是 Scheme,它甚至需要实现才能进行上述优化。


最后一点:您可以在此处使用递归调用实现 GCD 计算,因为如您所见,最大深度将17 + 1,该深度应该足够小以适合任何(嵌入式系统之外(调用堆栈。不过,我仍然会使用迭代版本:它具有更好的性能,更适合该语言的习语,并且是"更安全"的方式。