Game crashing on exit

Since a few weeks my game project crashes on exit (clicking ‘Quit’ button of cge gamestatemenu or closing window with clicking on window cross upright.)

I tried removing the code of procedureTStatePlay.Stop but no effect.
What and where could be the cause?

image

Oh, this is a VERY nasty bug. I hate it.

Most often it happens because you try to free an object twice. I.e. once you free it manually and second time it gets freed automatically. Or if it gets freed automatically twice.

One of the very often reasons is adding some TComponent derivatives (like UI elements) to a TObjectList which was created with OwnsChildren = true (as Create(true) which is default, so Create also gets you into this trap). This way both TObjectList and Parent try to free the object and the one who “comes later” gets an already freed object, and thus the crash.

It’s very complicated to debug this thing.

  1. Try make a note when it started happening (and never ignore this bug - it’s very hard to debug it later), see what you did and try to figure out why some object may be freed twice. Having your project at some version control like GIT may help a lot here.
  2. Try commenting out all possible manual freeing of the objects (Free, FreeAndNil, Dispose, etc.). See when it stops crashing (it’ll generate memory leaks, but first we need to pinpoint the culprit).
  3. Try commenting out handling large objects if it’s possible. E.g. do not run main state of the game to see which object fails to manage memory.

There can be other things that cause this. E.g. if you rely on one class in destructor of the other class you need to make sure the first class isn’t freed at that moment.

@eugeneloza Thanks.
I just outcommented all “free” object code but it did not work.
So now I will try the create objects. This is also what I added last time before the crash happened (I think).
Indeed it is a nasty bug.
:slightly_frowning_face:

“Access Violation” means you access a wrong memory place, i.e. you have a pointer (which may also mean: a variable holding an instance of a class) that is not valid.

It means you have an instance of an object that is invalid (e.g. you have copied the pointer, and called Free on another variable containing the same pointer). It may also mean you just access a variable equal to nil (zero) – which is the case here, as your error message says “Acccess violation executing address $0000”.

I would actually

  • Advise against trying to find it by commenting/uncommenting various places. This feels too much like “searching by luck”, you may end up wasting time doing 100 changes unrelated to the bug.
  • Do not trust too much the intuition “this bug often occurs when…”. The root cause is that you have an invalid pointer. There are many many ways how you can get an invalid pointer.

Instead the first thing is to figure out when it occurs. I very advise to just use Lazarus debugger for this, usually this gives me the solution in 5 minutes.

To do this,

  • Open the project in Lazarus, with Lazarus debugger on.
  • Run.
  • Lazarus will break at the exact Pascal line when it happens. This often helps and gives you straightforward place where the error occurs.
  • Seldom, Lazarus cannot find the line in Pascal code – then you will land in Assembler window, which is less useful. But you can then still access “Call Stack” window in Lazarus. This tells you what Pascal routine caused it.
  • If the Lazarus debugger didn’t give any useful results, then add just WritelnLog at the beginning of some larger pieces of code. Keep adding and removing them, and locate the place where the error occurs this way.

Once you find the place where it occurs, analyze what can do wrong.

Example:

X := MyObject.Y;

If the access violation happens there, it’s obvious that MyObject is either nil or a dangling pointer. To check this, you can e.g.

WritelnLog('MyObject <> nil? "%s"', [BoolToStr(MyObject <> nil, true)]);

right before that

X := MyObject.Y;

Or you can set in Lazarus breakpoint (F5) and right before the crashing line executes, check the contents of MyObject. If they are not nil – then you have a dangling pointer, and you need to understand why (something else freed it?) and secure from it (like making sure it is nil when freed, or maybe nothing else should free it). If it is nil – then understand why, and if it can happen, then you can simply add condition like

if MyObject <> nil then // during normal execution of the program, MyObject may have been freed by SomethingSomething
  X := MyObject.Y;

It gets a little more complicated when the instruction is longer, like

X := MyObject.MyAnotherObject.Y;

Maybe MyObject here is nil / invalid. Or maybe MyObject is good, and MyObject.MyAnotherObject is invalid. Again you will need to check it – with Lazarus breakpoint, or a sequence of writes to log.

@michalis The problem in question is that crash happens on game exit, not just during a normal workflow. Such crashes usually do not contain any meaningful stacktrace or any way to even approximately detect what is going wrong.

I ran the program from Lazarus when I got the above error.
When I compile and run from CGE editor and quit the program I get this:
Maybe this tells more about the kind of problem.

Yes, it does, though it’s just a tiny hint:

55d5ff5bb6079eb39fa15f280d2d248768d2bd04

This means the exception happens somewhere around destructor Destroy inside TCastleWindow or similar class. This means you free something manually, but it’s added to TCastleWindow and when it frees itself, it tries to free the same pointer again, and therefore the exception.

This doesn’t mean 100% that TCastleWindow or TCastleApplication is the class that throws an exception, it can happen e.g. inside TUiState derivative, it can also happen in your class’s destructor if it calls something unexpected, or practically anything else that for some obscure reason didn’t make it into the stack trace.

So, as I’ve mentioned, try to look at things that you explicitly or inexplicitly free manually and make sure that everything you create with Create(Application), Create(Self) or Create(FreeAtStop) or something like that doesn’t get freed manually or if you need to just Create(nil). When you add something to lists, make sure it’s never added twice to lists that free their stuff.

Hi!

I finally found the cause of the crashing;
It was because I created a Transform that was already created inside a type (TAvatar).

I am placing it here as a warning for others that might encounter this error.
I have outcommented the line that was causing the crash on exit.
:slight_smile:

constructor TAvatar.Create;
begin
  inherited;
  Personalia := TPersonalia.Create;
  Character :=  TCharacter.Create;
  Transform := TCastleTransform.Create(Application); 
end;
procedure TStatePlay.CreatePlayer; 
begin
  Player := TAvatar.Create;    

//Player.Transform.Create(Application); // this was the baddy!!!
 Player.Transform.Add(Player.StandIdleScene);
 Player.Transform.Add(Player.WalkLeftScene);
 Player.Transform.Add(Player.WalkRightScene);
 Player.Transform.Add(Player.WalkFrontScene);
 Player.Transform.Add(Player.WalkBackScene);
end;
1 Like

Interesting… what bothers me is that this situation shouldn’t cause a crash. Not even a memory leak. Both transforms would just be freed when the Application frees and they shouldn’t cause any crash (creating a hidden memory leak, but this is not a critical issue).

Ah. Wait. I’m too sleepy.

You should never (ever) call anything like Player.Transform.Create(Application). This means you call “constructor” again. Which is in 99.9% shouldn’t be done.

Indeed, most likely Transform allocates some memory inside and calling Create causes this memory to be misassigned as constructor was never supposed to be called this way and can most certainly cause unexpected behavior and crashes.

Unless you are 200% sure what you are doing it should always be

SomeClassVariable := TSomeClass.Create(...);

@eugeneloza Thanks for your reply.
I am really glad that I now can leave this behind and continue with the game.
:slight_smile:

2 Likes

Indeed calling constructor like this is a mistake.

A have a long dream to write a tool like pascal-lint that would capture errors like that, that are for 99% an invalid code, but the compiler doesn’t catch them for whatever reason. A have added this point (" Do not call constructor as a regular method") to the list of things it should detect.

See my plans on GitHub - michaliskambi/pascal-lint: Parse and do some correctness checks (that the compilers don't) on Pascal code .

2 Likes