EXC_BAD_ACCESS(code=2, address= … ) occurs when the size of target array exceeds particular number while sorting

huangapple go评论70阅读模式
英文:

EXC_BAD_ACCESS(code=2, address= ... ) occurs when the size of target array exceeds particular number while sorting

问题

我尝试比较了三种算法的排序时间:合并排序、希尔排序和快速排序。对于合并排序,我使用了带有结构体的链表来进行原地排序,只是切换每个节点的地址,而不是复制和粘贴数据。

然而,当目标数组的大小超过某个特定数值时 - 大约在130760~130770之间 - 当递归调用合并方法时,会抛出EXC_BAD_ACCESS错误(code=2, address= ... )。
数组的大小确实很重要,因为较小的大小不会产生异常。

合并排序类的代码如下:

template <typename T>
class MergeSort {
private:
    struct Node {
        T data;
        Node* next;
        Node(T d) : data(d), next(nullptr) {}
    };

    Node* head;
    int size;
    long count = 1;

    void split(Node** head, Node** left, Node** right) {
        Node* fast = *head;
        Node* slow = *head;
        while (fast->next && fast->next->next) {
            fast = fast->next->next;
            slow = slow->next;
        }
        *left = *head;
        *right = slow->next;
        slow->next = nullptr;
    }

    Node* merge(Node* left, Node* right) {
        Node* result = nullptr;
        count++;
        if (!left) return right;
        if (!right) return left;
        if (left->data <= right->data) {
            result = left;
            result->next = merge(left->next, right);
        } else {
            result = right;
            result->next = merge(left, right->next);
        }
        return result;
    }

    void mergeSort(Node** headRef) {
        Node* head = *headRef;
        Node* left = nullptr;
        Node* right = nullptr;
        if (!head || !head->next) return;
        split(&head, &left, &right);
        mergeSort(&left);
        mergeSort(&right);
        *headRef = merge(left, right);
    }


public:
    MergeSort() : head(nullptr), size(0) {}

    void insert(T data) {
        Node* newNode = new Node(data);
        newNode->next = head;
        head = newNode;
        size++;
    }

    void sort() {
        mergeSort(&head);
        std::cout << "Total merge count: " << count << std::endl;
    }

    void override(T arr[]) {
        Node* current = head;
        int i = 0;
        while (current) {
            arr[i++] = current->data;
            current = current->next;
        }
    }
};

异常来自于错误读取MergeSort实例本身的地址。

在这种情况下,“this”的实际地址是0x16d4a3448,而尝试读取的地址是0x16cca7ff0。

我发现两个地址之间的差值始终是8369240,这是一个非常有趣的数字。

问题总是在指向null的节点接近“right->next”时发生。

我还尝试更改数组元素的类型,但并没有改变数组的关键大小,因此堆栈溢出可能不是问题。

似乎由于某种原因,如果数组的大小超过了某个固定值,它会读取实例的错误地址,但我不知道为什么会发生这种情况。

供您参考,我目前在M1 MacBook Air上使用Clion 2022.3.1工作。

我尝试更改元素类型并监视right->next指向的内容,但这都没有显著影响关键大小。

我还在Clion中创建了另一个项目,这稍微改变了关键大小,但没有太大变化。

我发现使用lldb的AddressSanitizer对监视内存泄漏会有所帮助,但我不知道如何使用它。如果您能提供一些信息,那将非常感谢。(我不太熟悉调试工具,因为我不是CS专业的,只是初学者)

提前感谢您的帮助。

英文:

I tried to compare the time spent on sorting of 3 algorithms: merge, shell, quick. For the merge sorting, I implemented linked list with struct for the in-place sorting - just switching the address of each node, not copying and paste the data.

However, when the target array size exceeds the particular number - it is around 130760~130770 - it throws error of EXC_BAD_ACCESS(code=2, address= ... ) when it calls merge method recursively.
The size does matter because the lesser size doesn't reproduce the exception.

The code of merge sorting class is the following:

template &lt;typename T&gt;
class MergeSort {
private:
struct Node {
T data;
Node* next;
Node(T d) : data(d), next(nullptr) {}
};
Node* head;
int size;
long count = 1;
void split(Node** head, Node** left, Node** right) {
Node* fast = *head;
Node* slow = *head;
while (fast-&gt;next &amp;&amp; fast-&gt;next-&gt;next) {
fast = fast-&gt;next-&gt;next;
slow = slow-&gt;next;
}
*left = *head;
*right = slow-&gt;next;
slow-&gt;next = nullptr;
}
Node* merge(Node* left, Node* right) {
Node* result = nullptr;
count++;
if (!left) return right;
if (!right) return left;
if (left-&gt;data &lt;= right-&gt;data) {
result = left;
result-&gt;next = merge(left-&gt;next, right);
} else {
result = right;
result-&gt;next = merge(left, right-&gt;next);
}
return result;
}
void mergeSort(Node** headRef) {
Node* head = *headRef;
Node* left = nullptr;
Node* right = nullptr;
if (!head || !head-&gt;next) return;
split(&amp;head, &amp;left, &amp;right);
mergeSort(&amp;left);
mergeSort(&amp;right);
*headRef = merge(left, right);
}
public:
MergeSort() : head(nullptr), size(0) {}
void insert(T data) {
Node* newNode = new Node(data);
newNode-&gt;next = head;
head = newNode;
size++;
}
void sort() {
mergeSort(&amp;head);
std::cout &lt;&lt; &quot;Total merge count: &quot; &lt;&lt; count &lt;&lt; std::endl;
}
void override(T arr[]) {
Node* current = head;
int i = 0;
while (current) {
arr[i++] = current-&gt;data;
current = current-&gt;next;
}
}
};

The exception came from reading of the wrong address of MergeSort instance itself.

enter image description here

In this case, the real address of "this" was 0x16d4a3448, and the address attempted to read was 0x16cca7ff0.

I figured out that the difference of the two address was always 8369240 in decimal number when the error was thrown, which is a pretty interesting number.

The problem was also always occurred when the node pointing null was close to the "right->next".

I also tried changing the type of elements of array but it didn't change the critical size of array. So the stack overflow might not be the problem.

It seems that for some reason it reads wrong address of instance if the size of array exceeds some fixed value, but I have no idea why this is happening to me.

For your information, I'm currently working with Clion 2022.3.1 on M1 macbook air.

I tried changing the type of elements and monitoring what right->next points to.
It both didn't affect the critical size pretty much.

I also made another project in the Clion and it changed the critical size a bit, not much.

I found that using AddressSanitizer of lldb would be helpful for the monitoring memory leakage, but I couldn't figure out how to use it. If you can give me some information, please. (I'm not friendly with debugging tools because I don't major in CS, just beginner)

Thank you in advance.

答案1

得分: 0

合并函数在合并每个节点时会调用自身。这将导致大型列表出现堆栈溢出。将merge()更改为迭代。

英文:

The merge function calls itself for every node that is merged. This will lead to stack overflow for large lists. Change merge() to be iterative.

huangapple
  • 本文由 发表于 2023年1月8日 22:25:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/75048516.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定