[C++] Virtual methods in constructors/destructors + Memory corruption on iOS

Started by Blizzard, January 01, 2011, 06:33:17 am

Previous topic - Next topic

Blizzard

The other day while I was working on Medieval Battlefields, I was trying to solve a nasty sound bug. I won't go into details, but basically for the iOS version we decided to use the internal hardware-accelerated AVPlayer class for the music in order to save CPU time. The problem was that on scene switch where the same music would continue playing, the memory of the streambuffer for the music would get corrupted and the music would stop playing. Even upon restart of the stream (with or without setting the stream at the position where it stopped aka resuming), it would still stop playing after 1-2 seconds again. We figured that the problem had to be somewhere during the scene switch. This was probably the nastiest bug I had encountered in my life. It is simple to reproduce it, but because AVPlayer is a closed source component, we had no way of finding out what was going on and why. We only got a generic error from that component and had no idea what was going on. Funny enough, the same bug did not appear in Frogs vs. Storks that was built on the same scene manager code except that there were different scenes.

After many different attempts to fix the problem. I proposed an interesting idea to alter the scene manager system for MB only. Instead of destroying and creating scenes on scene switch, I would create a clear method that would set the scene into its initial state, clearing and destroying everything in the scene. It would basically be destroying and creating a new one except that there was no need for memory deallocation and reallocation. Since this new "clear" method would reset the scene into its initial state, I figured that I could use it to actually set the scene into its initial state after construction as well. Now here's where a few new problems started appearing. I was sure that the code was correct and that I made no mistake. Even checking the code over and over didn't help me find out what was going on. When I properly started the debugger, I noticed that an integer was uninitialized. I was confused.

Before I continue, here's a quick overview of the class hierarchy for the scenes.

Base
- Generic-Scenes (such as a scene that has everything implemented for data display on several pages)
- - Specialized-Generic-Scenes (such as an ingame help where you can change pages)
- Special-Purpose-Scenes (such as the menu scene, the game scene, the title scene, etc.)

Scene::Display would not initialize the member "pageIndex" to 0 for some reason. So I figured that I should check if the overridden virtual method "clear" (defined as virtual within Scene::Base, not a pure virtual) is actually called. Guess what, it's not. I couldn't believe what was going on. After a couple of minutes of trying to figure it out, I simply googled for exactly what I was doing and didn't work; "calling virtual function constructor". The second link immediately caught my attention.

Basically when constructors are run, first the base class data is constructed and then next subclass data down the hierarchy. The problem with calling overridden virtual methods in constructors (and destructors) was that the code had no idea what to call. At the point of the call (calling "clear" in the constructor of Scene::Base), the code has no idea that the class that is being constructed was actually Scene::Help that was derived from Scene::Display which again was derived from Scene::Base. So only the "clear" method of Scene::Base was called leaving the variables of Scene::Display uninitialized (Scene::Help has no own variables, it only changes some of the the initial values of Scene::Display in its constructor). I had to restructure my code. Even though this behavior seems counterintuitive, there's a good reason for it. The reason is memory allocation. By default the part of Scene::Base that is allocated comes first. Then comes the memory of the subclass Scene::Display, then Scene::Help. If in the constructor of Scene::Base the variables of Scene::Display would be initialized, there is no way to know where to put the data into the memory since Scene::Display wasn't constructed in the memory. Hence the overridden virtual method actually can't be called, the code isn't yet aware of its existence.

Luckily I thought of this being possible and actually googled it. It's good to know details like this when working in C++. This here is the article if you want a detailed explanation of the concept. In the article it is also stated that this actually causes the error "pure virtual function call". Remember, don't call virtual methods in constructors and destructors in C++.

As for the sound bug, it turned out that the restructure worked and the streambuffer didn't get corrupted anymore. Along with the change that scenes don't get destroyed but only cleared, there was another change. Since we work with STL but have created our own High Level wrapper classes for it, we are obviously using those classes. Due to the fact that I had to clear those scenes, all non-pointer members had to be reset to their initial values. This would include clearing all "harray" instances (derived from std::vector called hltypes::Array or harray) from the data inside. I have tried another variant of the code where the scenes would still be destroyed and created again but I left the new code that cleared the "harray" instances. Guess what, it worked again. It looks that iOS has some sort of problem with that kind of memory management and the streambuffer corruption was actually caused by uncleared "harray" instances. Obviously, this is bullshit because the harrays should have been destroyed properly. So an additional lesson is to always manually clear std::vector instances when destroying objects on iOS.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.