Autor Wątek: [C++] Tajemnicze segfaulty w mapie wskaźników  (Przeczytany 437 razy)

Offline mawpa

  • Użytkownik

# Marzec 03, 2017, 16:28:26
Witam.

Piszę, bo nie do końca ogarniam, skąd bierze mi się segfault w momencie, kiedy naprawdę mam wrażenie, że wszystko powinno być okej. Ale może najpierw trochę kodu...

Czytam swoje mapy z domyślnego formatu Tileda, bo w zupełności wystarcza do moich potrzeb. Mam klasę Level, w której mam klasę Cell, która dziedziczy po Drawable (kod Drawable jest na końcu postu, bo nie sądzę, żeby był szczególnie istotny, ale kto wie):class Cell : public Drawable
{
public:
  Cell(int x, int y, int offset, Level* parent) : Drawable(x, y), offset(offset), parent(parent)
  {
    w = parent->getTileWidth();
    h = parent->getTileHeight();
  }

  virtual void update(SDL_Event& event) override {}
  virtual void move(float delta) override {}
  virtual void draw() override;

private:
  int offset; // id w tilesecie
  Level* parent;
};

Następnie w Level mam klasę TileLayer, która reprezentuje warstwę kafelków:class TileLayer
{
public:
  TileLayer(std::ifstream& fin, Tag& info, Level* parent);

  std::vector<Cell*> getVisibleObjects(Drawable* collider); // tak sobie uproscilem szukanie kolizji

private:
  typedef std::pair<int, int> pos;
  std::map<pos, Cell*> cells;
};

W konstruktorze TileLayer kafelki robię tak:for(size_t y = 0; y < parent->getLogicalHeight(); y += yStep) // logicalHeight to wysokosc w pikselach, a nie w kafelkach
{
  // czytam caly wiersz

  for(size_t x = 0; x < parent->getLogicalWidth(); x += xStep)
  {
    // czytam offset (id kafelka) z wiersza

    Cell* c = new Cell(x, y, offset, parent);
    c->setCollidable(offset != 0);

    cells[pos(x, y)] = c;
  }
}

I wszystko jest pięknie. Na koniec konstruktora dla pewności wypisuję sobie do pliku ich współrzędne i adresy:string file = "layer_" + layerName + ".txt";
std::ofstream fout(file);
for(auto it : cells)
  fout << std::setbase(10) << "(" << it.second->left() << "," << it.second->top() << ")@" << std::setbase(16) << it.second <<' ';

I jak do tej pory wszystko hula i tańczy, współrzędne i adresy są takie, jakich się spodziewałem (pogrubione są te adresy, które później pojawiają się w pliku testowym):
Cytuj
(0,0)@0x326aa30 (0,32)@0x326e328 (0,64)@0x3270ee8 (0,96)@0x3273aa8 (0,128)@0x3276668
(0,160)@0x3279228 (0,192)@0x327bde8 (0,224)@0x327e9c0 (0,256)@0x3281598
(0,288)@0x3284170 (0,320)@0x3286d40 (0,352)@0x3289918 (0,384)@0x328c4f0
(0,416)@0x328f0c8 (0,448)@0x3291c98 (0,480)@0x3294870 (0,512)@0x3297448
(0,544)@0x329a020 (0,576)@0x329cbf0 (0,608)@0x329f7c8 (0,640)@0x32a23a0
(0,672)@0x32a4f70 (0,704)@0x32a7b48 (0,736)@0x32aa720 (0,768)@0x32ad2f8
(0,800)@0x32afec8 (0,832)@0x32b2aa0 (0,864)@0x32b5678 (0,896)@0x32b8250
(0,928)@0x32bae20 (0,960)@0x32bd9f8 (0,992)@0x32c05d0 (0,1024)@0x32c31a8
[...]

Niestety, problem pojawia się w momencie, kiedy próbuję z tych samych kafelków korzystać w getVisibleObjects:std::vector<Level::Cell*> Level::TileLayer::getVisibleObjects(Drawable* collider)
{
  int objX = collider->left();
  int objY = collider->top();
  int lastX = collider->right();
  int lastY = collider->bottom();

  int xStep = parent->getTileWidth();
  int yStep = parent->getTileHeight();

  // cofam sobie poczatkowe x i y do wspolrzednych pierwszego kafelka, który koliduje z colliderem
  if(objX % xStep) objX -= objX % xStep;
  if(objY % yStep) objY -= objY % yStep;

  std::vector<Cell*> objects;
  std::ofstream sout("test.txt");

  for(int x = objX; x < lastX; x += xStep)
  {
    for(int y = objY; y < lastY; y += yStep)
    {
      auto it = cells.find(pos(x, y)); // w teorii powinno znalezc, jesli x i y sa w porzadku
      if(it != cells.end() && it->second) // no i znajduje, ALE...
      {
        // ponizsza linijka przechodzi tylko w debuggerze
        // przy normalnym uruchomieniu gra sypie sie tutaj
        // a konkretnie przy it->second->top(), bo adres jest prawidlowy
        // i tez zgadza sie z plikiem z konstruktora
        // tak jakby obiekt byl uszkodzony, albo cos...
        sout << "obj @ " << std::setbase(16) << it->second << std::setbase(10) << " ("
             << it->second->left() << ',' << it->second->top() <<")\n";

        objects.push_back(it->second);
      }
    }
  }

  std::ostringstream x;
  x << "Returning " << objects.size() << " objects<br/>Cell count: " << cells.size();
  game->log(x.str());

  return objects;
}

W debuggerze powyższa funkcja jeszcze przechodzi, więc mam możliwość podejrzenia, co leci do pliku test.txt:obj @ 0x32ad2f8 (-2147483648,-2147483648)
obj @ 0x32afec8 (-2147483648,-2147483648)
obj @ 0x32b2aa0 (-2147483648,-2147483648)
obj @ 0x32b5678 (-2147483648,-2147483648)
obj @ 0x32b8250 (-2147483648,-2147483648)
obj @ 0x32bae20 (-2147483648,-2147483648)
obj @ 0x32bd9f8 (-2147483648,-2147483648)
obj @ 0x32c05d0 (-2147483648,-2147483648)
obj @ 0x32c31a8 (-2147483648,-2147483648)

Wszystkie adresy stąd pojawiają się też w pliku, który tworzę sobie w konstruktorze (te pogrubione), czyli teoretycznie pokazują na te same obiekty, tak? Dlaczego więc współrzędne tych obiektów są wzięte z kosmosu? Dlaczego kod przechodzi tylko w debuggerze, a normalnie już nie, bo kiedy próbuję się dobrać do obiektu, który przecież istnieje (adres zgadza się z adresem Cella utworzonego w konstruktorze, bo niby czemu miałby się nie zgadzać), program rzuca segfault? Teoretycznie wszystko powinno ładnie działać i podejrzewam, że to jakiś bardzo głupi i bardzo prosty błąd z mojej strony, ale walczę z tym dziadostwem już drugi dzień, czyściłem i budowałem od nowa cały projekt i już naprawdę nie wiem, co jest nie tak...

I na koniec, dla pewności, klasa Drawable:class Drawable
{
public:
  Drawable(int x = 0, int y = 0) : x(x), y(y) {}
  virtual ~Drawable() {}

  bool isCollidable() const {return collidable;}
  void setCollidable(bool c) {collidable = c;}

  int left() const {return x;}
  int right() const {return x + w;}
  int top() const {return y;}
  int bottom() const {return y + h;}

  bool collidesWith(Drawable& d);

  virtual void update(SDL_Event& event) = 0;
  virtual void move(float delta) = 0;
  virtual void draw() = 0;

protected:
  float x; // float po to, zeby mnozyc przez delte, wszedzie indziej dziala bez zarzutu
  float y; // - || -
  int w;
  int h;
  bool collidable = false;
};

// Edit: drobna poprawka wyjścia do pliku, która w zasadzie i tak nic nie zmienia
« Ostatnia zmiana: Marzec 03, 2017, 16:47:30 wysłana przez mawpa »

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline lethern

  • Użytkownik

# Marzec 03, 2017, 17:44:37
Dodaj logowanie ctora i destructora tych Cell / drawable z wypisaniem ich adresu

Offline mawpa

  • Użytkownik

# Marzec 03, 2017, 18:26:10
Dodałem. Wygląda na to, że obiekty powstają przy new, a potem są niszczone przy wyjściu z konstruktora. Zdawało mi się, że jeśli coś alokuję przez new, to istnieje dopóki nie będzie wywołane delete na jego adresie... Próbowałem shared_ptr i też nie działało (spróbuję jeszcze raz później, bo może ostatnie zmiany, które wprowadziłem przed napisaniem posta coś wniosły do użyteczności shared_ptr).

Offline lethern

  • Użytkownik

# Marzec 03, 2017, 18:57:34
debugger i szukaj co wywołuje delete, albo masz gdzieś destruktor w instancji którą kopiujesz zamiast używać wskaźnik, albo...
nigzie w powyższym kodzie nie widać użycia delete, więc ciężko wróżyć
mam nadzieję że Ci debugger (slash logger) pomoże
« Ostatnia zmiana: Marzec 03, 2017, 19:00:14 wysłana przez lethern »