【问题标题】:Merge Sort Segmentation Fault Only On Large Arrays仅在大型数组上出现合并排序分段错误
【发布时间】:2020-05-18 07:35:33
【问题描述】:

我正在努力实现几种不同的排序方法,由于某种原因,我的合并排序算法不适用于大型数据集。该排序将适用于 115,000 个单词,但在达到 135,000 个单词时停止工作。一旦我达到这个高度,我最终会遇到分段错误。我不明白段错误来自哪里。该排序对包含 5K 到 125K 字符串的文本文件有效。

readFile 数组被初始化为文本文件中的字数。调试时,传递给mergeSort() 函数的最后一个数字似乎如下:

#0  0x0000000000402a87 in merge (inputString=0x7fffffbde790, from=0, mid=67499, to=134999) at mergeSort.cpp:102
    n1 = 67500
    n2 = 67500
    i = 0
    j = 0
    k = 32767
    L = <error reading variable L (value requires 2160000 bytes, which is more than max-value-size)>
    R = <error reading variable R (value requires 2160000 bytes, which is more than max-value-size)>
#1  0x0000000000402921 in mergeSort (inputString=0x7fffffbde790, from=0, to=134999) at mergeSort.cpp:88
    mid = 67499
void mergeSort(string readFile[], int from, int to) {
    if (from < to) {
        int mid = from + (to - from) / 2;
        mergeSort(readFile, from, mid);
        mergeSort(readFile, mid + 1, to);
        merge(readFile, from, mid, to);
    }
}
void merge(string readFile[], int from, int mid, int to) {
    int n1 = mid - from + 1;
    int n2 = to - mid;

    string L[n1];
    string R[n2];

    for (int i = 0; i < n1; i++) {
        L[i] = readFile[from + i];
    }
    for (int i = 0; i < n2; i++) {
        R[i] = readFile[mid + i + 1];
    }

    int i = 0;
    int j = 0;
    int k = from;

    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            readFile[k] = L[i];
            i++;
        } else {
            readFile[k] = R[j];
            j++;
        }
        k++;
    }
    while (i < n1) {
        readFile[k] = L[i];
        i++;
        k++;
    }
    while (j < n2) {
        readFile[k] = R[j];
        j++;
        k++;
    }
}

【问题讨论】:

  • 递归例程可能导致分段错误(由于堆栈溢出)。阅读*.com/a/12146513/3656081 以获取可能的建议。如果此代码要投入生产,您可能希望重组程序以进行迭代。
  • 替代方案是 Bottom_Up 迭代合并排序,请参阅Merge sort - Wikipedia。使用典型的递归合并排序,您将在 Linux 上达到 4M 默认堆栈限制,大约为 100,000 int
  • 您的问题缺少minimal reproducible example 和错误发生时的错误输出(最好是完整的回溯)。
  • @rcgldr 到最后 n1 和 n2 最终都是 67,500。这可能是一个愚蠢的问题,但我假设分配有 135,000 个元素的数组和分配有 67,500 个元素的两个数组在堆栈上的数量相同,对吗?
  • @rcgldr 我也更新了我的回溯。

标签: c++ segmentation-fault mergesort


【解决方案1】:

您在merge 函数中将临时数组分配为自动变量。当这些数组的大小变得太大时,您将缺乏堆栈空间来分配它们并获得未定义的行为(例如 堆栈溢出)。

要处理任意大的数组,您应该使用mallocnew 分配临时数组并相应地释放它们。要限制分配的数量,您可以在包装器中分配一个临时数组,并在 mergeSort 函数中递归地传递它。

这是在 merge 函数中分配临时数组的简单修复:

void merge(string readFile[], int from, int mid, int to) {
    int n1 = mid - from + 1;
    int n2 = to - mid;

    string *L = new string[n1];
    string *R = new string[n2];

    for (int i = 0; i < n1; i++) {
        L[i] = readFile[from + i];
    }
    for (int i = 0; i < n2; i++) {
        R[i] = readFile[mid + i + 1];
    }

    int i = 0;
    int j = 0;
    int k = from;

    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            readFile[k] = L[i];
            i++;
        } else {
            readFile[k] = R[j];
            j++;
        }
        k++;
    }
    while (i < n1) {
        readFile[k] = L[i];
        i++;
        k++;
    }
    while (j < n2) {
        readFile[k] = R[j];
        j++;
        k++;
    }
    delete[] L;
    delete[] R;
}

这是一个更精细的版本,可能更有效,分配一个临时数组:

void merge(string readFile[], size_t from, size_t mid, size_t to, string aux[]) {
    size_t i, j, k;

    for (i = from; i < to; i++) {
        aux[i] = readFile[i];
    }

    i = from;
    j = mid;
    k = from;

    while (i < mid && j < to) {
        if (aux[i] <= aux[j]) {
            readFile[k++] = aux[i++];
        } else {
            readFile[k++] = aux[j++];
        }
    }
    while (i < mid) {
        readFile[k++] = aux[i++];
    }
    while (j < to) {
        readFile[k++] = aux[j++];
    }
}

void mergeSort(string readFile[], size_t from, size_t to, string aux[]) {
    if (to - from > 1) {
        size_t mid = from + (to - from) / 2;
        mergeSort(readFile, from, mid, aux);
        mergeSort(readFile, mid, to, aux);
        merge(readFile, from, mid, to, aux);
    }
}

void mergeSort(string readFile[], size_t n) {
    string *aux = new string[n];
    mergeSort(readFile, 0, n, aux);
    delete[] aux;
}

【讨论】:

  • ~135,000 个单词转化为~18 个递归级别(仅超过 17 个)。当调用merge时,它分配2个std::string数组,每个条目应该只包含“header”对象信息,实际的字符串内存是从堆中分配的,当merge( ) 退出。我不清楚这是否是错误的原因。
  • string 对象在我的 64 位模式下的系统上的大小为 24 字节。我同意递归不应该是问题,但在堆栈上分配 3.24 兆字节似乎很可能导致 堆栈溢出 :)
  • 我同意。然而,在 merge() 中使用 new 和 delete 应该可以解决问题,但会产生不必要的开销。我正在使用不支持可变长度数组的 Visual Studio,并且必须使用 _alloca() (从堆栈分配)来实现等效,并且失败应该发生在初始调用中,而不是稍后发生较小数组的递归级别。
  • @rcgldr:使用_alloca() 分配构造对象数组不适合胆小的人:)
  • 那不是我的真正意思,只是解释VS不支持可变长度数组,这些数组是在堆栈外分配的。正如您已经指出的那样,这很可能是问题所在,在初始调用中分配了 3.24 兆字节,但 OP 认为它来自一个深度嵌套的调用,这似乎不太可能。最简单的解决方法是在 OP 的合并函数中使用 new[] 和 delete[] 。如果这是针对一堂课,优化后的版本可能会明显表明学生获得了太多帮助。
最近更新 更多