0

Edit:

I've implemented copy constructors (both from the suggested answer). I have done this in my controller class, as well as equivalent versions in it's sub classes. However, this has not resolved the issue. Also, a counter and console comment in the removePawn() function (the only place in the program that delete is called) shows that it is only called once.

In more detail, there is one instance of each subclass (not pointers). These are declared in my world class. Both are used as parameters in the same world classes methods via the baseController class pointers. The problem is, while both are going through the same processes in the same order, if one class has removePawn() called on it, the program is fine and will run continually. However if the second class has removePawn() (delete specifically) called on it, it crashes the program at that instruction.

I've also checked the addresses. The address of the pointer just after allocation is identical to the address at the point of deletion.

More info: I will get a segmentation error when closing the program ONLY if the player is killed (deleted then given a new pawn). However, if the program is started then closed without any more than the first new and last delete, then it runs perfectly fine.

Original:

I'm having a bit of trouble with pointers. I understand them and believe my code is fairly robust however I seem to get a complete crash while calling this section of code.

Pawn is a basePawn* initialized to NULL.

if (pawn != NULL)
{
    cout << "Calling delete.\n";
    delete pawn;
    pawn = NULL;
}

This is a university assignment for a PS2 program so my debugging is limited to basic printing to the console.

Removing the delete line allows the main new/delete section to run a few times however it eventually crashes too (I assume this is because the memory limit is reached however I can't be sure)

I have checked all the usual culprits, the pointer is initialized to null and only deleted once (new is always called also).

I may be making a fairly obvious mistake however I have no clue. Any suggestions would be great. (I can post up more code if needed).

Edit:

Here's how the structure of the code works.

basePawn is a class with some fairly basic methods representing the character.

Controller is a class with a pointer to a basePawn (initially set to NULL) used as the brain of the character (AI or player controlled). It contains a removePawn method.

void controller::removePawn()
{
    if (pawn != NULL)
    {
        cout << "Calling delete.\n";
        delete pawn;
        pawn = NULL;
    }
}

This method gets called in the destructor. It also gets called when the pawn is removed from the level.

It also has a respawn method.

if (pawn == NULL)
{
    respawnCounter++;

    if (respawnCounter >= respawnTime)
    {
        //Switch block to change class
        pawn = new basePawn;

        if (pawn !=NULL)
        {
            pawn->boardY = 4; //Will be random
            pawn->boardX = 5; //Will be random

            respawnCounter = 0;

            pawn->setIdle();

            return true;
        }
    }
}

Edit:

baseController header file

#ifndef _BASEPAWNCONTROLLER_H
#define _BASEPAWNCONTROLLER_H

#include "basePawn.h"
#include "textureManager.h"
#include "direction.h"
#include "vector2f.h"

//Used to control pawns
//Allows the same commands to be used to control different pawns
class basePawnController
{
private:
protected:
basePawn *pawn;

int respawnCounter,
    respawnTime;

vector2f    targetDest;

bool        bMoving,
            bTarget;

void        removePawn();

public:

bool    bFirstFrameDead;

basePawnController();
virtual ~basePawnController();

virtual void update();

basePawn *getPawn();
void setPawn(basePawn *p);  
void setTarget(float x, float y);
direction getDir();
bool isMoving();
bool hasTarget();

virtual bool respawn();
virtual void render(textureManager &tManager);

virtual bool wantsPawn();
virtual void giveTargetInfo(direction d, int n);
};

#endif
  • How did you allocate/assign to `pawn`? If it was `pawn = new basePawn();`, it shouldn't crash. `pawn = &some_object;` on the other hand wouldn't make it a suitable operand of `delete`. – Daniel Fischer Apr 29 '13 at 17:14
  • 1
    Could you give us a minimal complete example? – Beta Apr 29 '13 at 17:21
  • are the destructors for pawn and basePawn implemented and doing some cleanup, and if they are, did you make basePawn's destructor virtual? – Kate Gregory Apr 29 '13 at 17:23
  • Pawn is allocated similarly to that. (In a class 'World' class, containing a 'Controller' class which contains a basePawn *pawn) basePawn *temp; `temp = new basePawn();` `controllerInstance.setPawn(temp) // this function does pawn = parameter (which is a basePawn *p)` pawn is just an basePawn pointer, not a different class. – Damien McAlear Apr 29 '13 at 17:28
  • I'm going to take wild guess and say you're not following the 'rule of three'. That seems to be the commonest reason for these kind of crashes. Can you post the `controller` class, just what's in the header should be enough. – john Apr 29 '13 at 17:51
  • I have added the `controller` header. It is a little bloated with other methods but hopefully it helps. – Damien McAlear Apr 29 '13 at 18:06
  • "I'm having a bit of trouble with pointers": Yes, they often cause problems. Smart pointers would be much easier to deal with. – Mike Seymour Apr 29 '13 at 18:50
  • Unfortunately I don't have access to them. As I said in a comment below, I can't even get std::vector's .at() method. – Damien McAlear Apr 29 '13 at 21:23
  • @DamienMcAlear Yep, failure to follow the rule of three. Although he doesn't name it user1309389 has said the same thing in his answer. Here's the official SO take on this topic http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three – john Apr 29 '13 at 22:09
  • @john I have implemented those suggested methods however I have the same issue, more info at the top of the question. – Damien McAlear Apr 30 '13 at 09:10
  • @DamienMcAlear If that didn't help then either you didn't implement them correctly, or you aren't copying these objects and your problem is unrelated to the rule of three. So, hard to tell from this distance. Here's one thing to try however, keep your copy constructor and assignment operators but make them *protected*. If this gives you a compiler error I would still bank on it being a rule of three issue. – john Apr 30 '13 at 17:50
  • @john I will try making them protected in the morning when I get back to the console. As for my implementation, I follow user1309389's example, for the base class as well as the subclasses (even making sure I copy the base class members as well, just on the off chance that the base one doesn't get called either) – Damien McAlear Apr 30 '13 at 19:39
  • @john Just in case this isn't a rule of 3 problem, do you have any other suggestions of what this may be? Thanks for all the help by the way. – Damien McAlear Apr 30 '13 at 19:45
  • @DamienMcAlear A crash during delete could be caused by a few things. It could be a double delete, deleting the same pointer twice (that would be a rule of three error). It could be a delete of a garbage pointer, it could be a corrupt heap, (it could be a bug in ~basePawn as well). You could get some idea which by modifying your cout statement, `cout << "Calling delete " << pawn << ".\n";`. This will give you the address of the pointer you are deleting, a double delete or a garbage pointer should be fairly obvious. Heap corruption is much harder to detect however. – john May 01 '13 at 05:57
  • @john someone in my class did suggest this. I output the address at the time of allocation and just before deletion. The same address is used each time, so the pointer itself isn't changing. ~basePawn is totally empty. basePawn just uses normal variables, no pointers or dynamic ones. However, in my function that does the deleting does not seem to get called except when the player kills the target and when the program is closed. The instance of the class is passed by pointer to functions within the update loop. Shouldn't these instances also call the destructor at the end of their scope? – Damien McAlear May 01 '13 at 11:14
  • @DamienMcAlear No (assuming I'm understanding you correctly). Passed by pointer is the clue, because pointers do not have destructors (even if what they're pointing at does). So nothing happens when a pointer goes out of scope. If you are only seeing "Calling delete" once, and the pointer value is unchanged, and the destructor does nothing, then it seems the only possibility left is heap corruption. Which unfortunately is the hardest to track down. – john May 01 '13 at 18:30
  • @DamienMcAlear If you can, cut down the size of your program so that it is small enough to post in it's entirely to SO. It doesn't matter if the program no longer does anything sensible just as long as the bug is still happening. Then I'm sure that someone will take the code, compile and run it and give you the answer very quickly. Just going through the process of cutting down the program may give you a clue, because at some point the bug might go away. If that happens then the last piece of code you removed is quite likely to be the culprit. – john May 01 '13 at 18:34
  • @john Unfortunately I don't think anyone would be able to compile the code, due to it being compiled on the console (command line + make file). However, I do have a stripped down version (pawn, controller class (+ it's 2 sub classes) as well as the baseWorld class (one that calls the controller class's functions). If anyone would be willing to look at that? As for passed by pointer, I use signatures similar to this, `void setControllerTarget(basePawnController *c);` I don't believe that will cause problems, especially as this works fine with one class but not the other. – Damien McAlear May 01 '13 at 22:19
  • @john here's my stripped down version in the case that you would be willing to have a look. https://www.dropbox.com/s/sc1dn7e6yvgligm/PS2PointerDeletion.zip – Damien McAlear May 01 '13 at 22:27
  • @DamienMcAlear Well I had a look, but that's not really what I meant by a stripped down example, because as you say it's not compilable in that form. Had a very quick look, couldn't see any obvious problems. In general terms though I still suggest it's heap corruption. – john May 02 '13 at 05:27
  • @john I'm using the console just now. I tried to do something like this. ` if counter > 500 removePawn GiveNewPawn reset counter ` Doing this repeatedly for both of my instances of controller caused no problems whatsoever. I also interleaved the calls, called the update function on them etc. These also caused no issues. This leads me to believe my use of new and delete seem to be sound. However that it's something somewhere else that is causing me trouble. – Damien McAlear May 02 '13 at 10:12

2 Answers2

1

Given that it is the PS2, I will assume you have no access to C++11 and with it -- smart pointers (std::unique_ptr and the sort) etc. If Boost or C++11 is not an option, you should really take the time and study how to reimplement basic smart pointer functionality -- this would relax the mental strain considerably. But, let's assume that's not an option.

Do note this is the most likely scenario what's going on, due to a scarce description of the problem, gathered from the question and the comments.

You're trying to utilize RAII by deallocating resources like the pawn when the controller instance goes out of scope, since you call removePawn in the destructor -- that's good. However, there are many ways an instance of a class can lose data / blow up when you don't provide non-trivial copy assignment operator and copy constructor implementations when coupled with pointers and heap allocations.

You should really implement additional functionality which manages copying which can happen in ways you don't expect. If you don't manage it yourself, the code perhaps runs a few times and lets the controller get copied somehow by passing it to a function or the like -- it will construct a new controller with the old one's data. But it's a shallow copy, the address of the basePawn which we assume is valid will be copied, but it will not set the dying instance's one to 0 / NULL.

After the old one runs out of scope, its destructor gets called. And since you didn't invalidate the old pointer, it will delete the same pawn that is now referred by two different objects. And when you state delete pawn; some time later -- boom.

You need these two properly implemented:

basePawnController(basePawnController& that);
basePawnController& operator=(basePawnController& that);

Don't get into the trap of declaring it as const, that is not an option when you're playing around with raw pointers. What needs to happen is along the lines of this, you can implement one in terms of the other:

// Copy constructor
basePawnController::basePawnController(basePawnController& rhs) {

    *this = rhs; // invokes basePawnController::operator=()

}

// Copy assignment operator
basePawnController& basePawnController::operator=(basePawnController& rhs) {

    // copy your stuff and now the basePawn
    pawn = rhs.pawn; // Copy the address...
    rhs.pawn = 0; // ...but make sure the old instance doesn't troll you.

    return *this;

}

My best advice to you would be to investigate ownership transparency through smart pointers by utilizing the RAII facilities. Programming should be a pleasure.

  • Unfortunately I cannot use C++11 or Boost (in fact, the compiler and libraries are so old that it doesn't allow stl::vector's .at() method!) I will add this functionality that you are suggesting. However, the controller classes are never used as pointers, only as actual variables. removePawn also gets called via a branch midway through the update function (if the pawn is dead). This is so I can swap the basePawn type if I wish to do so. – Damien McAlear Apr 29 '13 at 21:21
  • A note, I have 2 sub classes (one for the playerController and one for the AIcontroller) both are implemented as similarly as possible, there are no real changes in the flow of logic. However the AI one repeatedly fails on the delete call specifically. The player one is perfectly fine. – Damien McAlear Apr 29 '13 at 21:22
  • @user1309389 I have implemented these methods in the `controller` class as well as equivalents in it's subclasses however this has not resolved the issue. More info at the top of the question. – Damien McAlear Apr 30 '13 at 09:05
0

Well, you guys were pretty much right.

After adding the copy constructor and assignment operator, I performed more checks to see where my problems were actually occurring. Turns out it wasn't anything to do with my controller classes directly, but my pawn and world classes.

My pawn has a vector of damageNode* which I wasn't deleting. Not a big issue as I had a pointer to them in the world. But I never deleted them in the world class either, hence I was getting seg faults when removing the pawn and the world.