AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 78848937
Accepted
hubenhau
hubenhau
Asked: 2024-08-08 22:16:43 +0800 CST2024-08-08 22:16:43 +0800 CST 2024-08-08 22:16:43 +0800 CST

SIGSEGV ao tentar resolver grandes labirintos com A*

  • 772

Estou implementando um algoritmo de localização de caminhos A* em C++ para resolver um labirinto, mas estou encontrando uma falha de segmentação (SIGSEGV) quando o tamanho do labirinto é alto (~10.000x10.000 ou maior).

O erro ocorre na ~__shared_count()função shared_ptr_base.hafter node = queue.top()é chamada na linha 58 e dezenas de ~Node()desconstrutores são chamados depois disso.

Editar: O erro realmente ocorre na _Sp_counted_base<_S_atomic>::_M_release()função agora, embora eu não saiba o que mudei.

Aqui estão as partes do meu código que devem ser relevantes. Sou novo no uso de ponteiros compartilhados, então não tenho certeza de qual é o problema ou como corrigi-lo.

principal.cpp

#include <iostream>
#include <queue>
#include <random>
#include <vector>
#include <algorithm>
#include <stack>
#include <fstream>

#include "Node.hpp"

constexpr uint16_t maze_size = 10000;

void solve(const uint16_t start_x, const uint16_t start_y, const uint16_t goal_x,
           const uint16_t goal_y,
           const std::vector<std::vector<std::pair<bool, bool> > > &walls) {
    std::vector<std::vector<bool> > visited(maze_size, std::vector<bool>(maze_size, false));

    // Min heap for our queue
    std::priority_queue<Node, std::vector<Node>, std::greater<> > queue;

    Node node(start_x, start_y, goal_x, goal_y);

    visited[node.x][node.y] = true;

    while (!node.solved(goal_x, goal_y)) {
        // Add all valid neighbors to the queue
        {
            const auto x = node.x;
            const auto y = node.y;
            const auto ptr = std::make_shared<Node>(node);
            // Right
            if (x < maze_size - 1 && !walls[x][y].first && !visited[x + 1][y]) {
                queue.emplace(x + 1, y, goal_x, goal_y, ptr);
                visited[x + 1][y] = true;
            }
            // Left
            if (x > 0 && !walls[x - 1][y].first && !visited[x - 1][y]) {
                queue.emplace(x - 1, y, goal_x, goal_y, ptr);
                visited[x - 1][y] = true;
            }
            // Down
            if (y < maze_size - 1 && !walls[x][y].second && !visited[x][y + 1]) {
                queue.emplace(x, y + 1, goal_x, goal_y, ptr);
                visited[x][y + 1] = true;
            }
            // Up
            if (y > 0 && !walls[x][y - 1].second && !visited[x][y - 1]) {
                queue.emplace(x, y - 1, goal_x, goal_y, ptr);
                visited[x][y - 1] = true;
            }
        }
        node = queue.top();
        queue.pop();
    }
}

Nó.hpp

#ifndef NODE_HPP
#define NODE_HPP

#include <cstdint>
#include <memory>

class Node {
public:
    uint16_t x, y;
    uint32_t steps;
    uint32_t value;

    std::shared_ptr<Node> parent;

    Node(const uint16_t x, const uint16_t y, const uint16_t goal_x, const uint16_t goal_y,
         const std::shared_ptr<Node> &parent = nullptr) : x(x), y(y), parent(parent) {
        steps = parent ? parent->steps + 1 : 0;
        value = abs(static_cast<int32_t>(goal_x) - static_cast<int32_t>(x)) +
                abs(static_cast<int32_t>(goal_y) - static_cast<int32_t>(y)) +
                steps;
    }

    [[nodiscard]] bool solved(const uint16_t goal_x, const uint16_t goal_y) const {
        return x == goal_x && y == goal_y;
    }

    // Overload the > operator for the priority queue
    bool operator>(const Node &rhs) const {
        if (value == rhs.value) {
            return steps < rhs.steps;
        }
        return value > rhs.value;
    }
};

#endif

Saída Valgrind

valgrind --leak-check=full ./Tests 
==5385== Memcheck, a memory error detector
==5385== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==5385== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==5385== Command: ./Tests
==5385== 
Maze loaded10000
==5385== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==5385== 
==5385== Process terminating with default action of signal 11 (SIGSEGV)
==5385==  Access not within mapped region at address 0x1FFE801FF8
==5385== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==5385==    at 0x116230: std::_Sp_counted_ptr_inplace<Node, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:613)
==5385==  If you believe this happened as a result of a stack
==5385==  overflow in your program's main thread (unlikely but
==5385==  possible), you can try to increase the size of the
==5385==  main thread stack using the --main-stacksize= flag.
==5385==  The main thread stack size used in this run was 8388608.
==5385== 
==5385== HEAP SUMMARY:
==5385==     in use at exit: 239,026,864 bytes in 556,422 blocks
==5385==   total heap usage: 3,380,473 allocs, 2,824,051 frees, 374,594,816 bytes allocated
==5385== 
==5385== LEAK SUMMARY:
==5385==    definitely lost: 0 bytes in 0 blocks
==5385==    indirectly lost: 0 bytes in 0 blocks
==5385==      possibly lost: 0 bytes in 0 blocks
==5385==    still reachable: 239,026,864 bytes in 556,422 blocks
==5385==         suppressed: 0 bytes in 0 blocks
==5385== Reachable blocks (those to which a pointer was found) are not shown.
==5385== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==5385== 
==5385== For lists of detected and suppressed errors, rerun with: -s
==5385== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

Você poderia tentar reproduzir o erro usando a seguinte função para carregar este arquivo labirinto . Desculpe pelo tamanho. Você pode carregá-lo usando esta função:

std::vector<std::vector<std::pair<bool, bool> > > readVectorFromFile(const std::string &filename) {
    std::vector<std::vector<std::pair<bool, bool> > > vec;
    std::ifstream inFile(filename, std::ios::binary);
    if (!inFile) {
        std::cerr << "Error opening file for reading: " << filename << std::endl;
        return vec;
    }

    // Read the size of the outer vector
    size_t outerSize;
    inFile.read(reinterpret_cast<char *>(&outerSize), sizeof(outerSize));

    vec.resize(outerSize);

    for (auto &innerVec: vec) {
        // Read the size of each inner vector
        size_t innerSize;
        inFile.read(reinterpret_cast<char *>(&innerSize), sizeof(innerSize));

        innerVec.resize(innerSize);

        // Read each pair in the inner vector
        for (auto &pair: innerVec) {
            inFile.read(reinterpret_cast<char *>(&pair.first), sizeof(bool));
            inFile.read(reinterpret_cast<char *>(&pair.second), sizeof(bool));
        }
    }

    inFile.close();
    return vec;
}

O principal seria algo como:

int main() {
    auto walls = readVectorFromFile("maze.dat");
    std::cout << "Maze loaded" << std::endl;

    solve(0, 0, maze_size - 1, maze_size - 1, walls);

    return 0;
}
c++
  • 3 3 respostas
  • 68 Views

3 respostas

  • Voted
  1. Best Answer
    MSalters
    2024-08-08T22:29:28+08:002024-08-08T22:29:28+08:00

    Você não definiu explicitamente, Node::~Node()mas o compilador fez isso por você. Lá dentro ele chama std::shared_ptr<Node>::~shared_ptr. Por sua vez, isso chama Node::~Node.

    Esta é uma chamada recursiva. Dependendo do seu labirinto e consulta, isso pode transbordar a pilha de chamadas.

    A solução mais fácil é deixar solve()gerenciar a propriedade de todos os Nodes; cada nó pode ter apenas um arquivo Node* parent. Essa propriedade pode ser tão fácil quanto std::deque<Node>(mas não std::vector<Node>, porque isso realoca e quebra o Node* parent)

    [editar]

    Usar ponteiros inteligentes em objetos de nó não é necessariamente uma má ideia. Em árvores largas, onde cada pai tinha 10 filhos, você enfrentará problemas de memória com cerca de 9 níveis de profundidade (um bilhão de nós), mas a pilha pode lidar facilmente com esse nível de aninhamento. Mesmo uma árvore binária balanceada com um bilhão de nós não é tão ruim (profundidade 32). Nestes casos de árvore, cada nó é o único proprietário de alguns filhos. Isso pode ser implementado com um arquivo std::vector<std::unique_ptr>> children.

    • 6
  2. pptaszni
    2024-08-08T22:29:25+08:002024-08-08T22:29:25+08:00

    No seu, Nodeo std::shared_ptr<Node> parent;não é realmente necessário e está sujeito a dependências cíclicas, o que aparentemente aconteceu no seu labirinto (embora eu tenha preguiça de confirmar isso). Remova shared_ptre apenas forneça as informações sobre o Node pai steps:

    class Node {
    public:
        uint16_t x, y;
        uint32_t steps;
        uint32_t value;
    
        Node(uint16_t x, uint16_t y, uint16_t goal_x, uint16_t goal_y, int parentSteps = -1) : x(x), y(y) {
            steps = parentSteps + 1;
            value = abs(static_cast<int32_t>(goal_x) - static_cast<int32_t>(x)) +
                    abs(static_cast<int32_t>(goal_y) - static_cast<int32_t>(y)) + steps;
        }
    
        [[nodiscard]] bool solved(const uint16_t goal_x, const uint16_t goal_y) const { return x == goal_x && y == goal_y; }
    
        // Overload the < operator for the priority queue
        bool operator>(const Node &rhs) const {
            if (value == rhs.value) {
                return steps < rhs.steps;
            }
            return value > rhs.value;
        }
    };
    

    Então você pode criar novos nós como este:

    queue.emplace(x + 1, y, goal_x, goal_y, node.steps);
    
    • 3
  3. Lajos Arpad
    2024-08-08T23:10:52+08:002024-08-08T23:10:52+08:00

    Este é o erro que você recebe:

    Estouro de pilha no thread nº 1: não é possível aumentar a pilha para 0x1ffe801000

    Estouro de pilha significa que a pilha estourou, então vamos entender o que é a pilha.

    Stack é uma estrutura de dados do tipo LIFO, ou seja, você empilha elementos uns sobre os outros e sempre consegue colocar o elemento no topo primeiro.

    Na Ciência da Computação, existe uma seção de memória chamada pilha de chamadas .

    Esta seção é onde as chamadas de sub-rotina estão sendo chamadas. Portanto, se você chamar uma função que chama uma função que chama uma função, essas funções serão empilhadas na pilha de chamadas. Isso funciona até um certo limite, mas se você tiver muitas chamadas de função sem que as funções sejam executadas (e retiradas da pilha), então suas chamadas de função inacabadas se acumulam e preenchem o tamanho da pilha de chamadas. Neste ponto, se houver outra chamada de função, ela será tentada a ser colocada na pilha de chamadas, mas falhará por não ter espaço de armazenamento suficiente. Isto é o que aconteceu.

    Como você provavelmente já percebeu, você pode aumentar o tamanho da pilha de chamadas, o que removerá os sintomas, mas o problema subjacente ainda estaria lá, apenas o limite da pilha seria mais difícil de alcançar.

    Portanto, a solução é garantir que seu algoritmo seja iterativo e não recursivo. Uma maneira de tentar resolvê-lo é tentar criar uma pilha iterativa (na verdade, você usa explicitamente uma pilha em vez de depender da pilha de chamadas) e se quiser liberar a Node, coloque todas as suas referências em sua pilha, defina-as como null, então nada de suspeito acontece, libere o Nodeque agora não tem mais nenhuma referência para acionar um destruidor, retire seu nó do topo da pilha, comporte-se de forma semelhante, ou seja, empurre suas referências para a pilha, remova as referências deste objeto, libere-o e prossiga com o topo da pilha e assim por diante até que não haja mais ponteiro para liberar.

    • 2

relate perguntas

  • Por que os compiladores perdem a vetorização aqui?

  • Erro de compilação usando CMake com biblioteca [fechada]

  • Erro lançado toda vez que tento executar o premake

  • Como criar um tipo de octeto semelhante a std::byte em C++?

  • Somente operações bit a bit para std::byte em C++ 17?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve