Click to See Complete Forum and Search --> : std::vector::push_back issue


Mybowlcut
June 21st, 2008, 03:20 AM
Hey. I've got a weird problem... I've been building my program in release mode just recently and I've come across a problem where a string is inserted into a vector... the highlighted code shows the line where the problem is.

The string is fine until it's pushed into the vector... after it's pushed in, the vector still has 0 elements... I stepped into the push_back definition and the parameter _Val was <Bad Ptr>...

I have no clue why it's happening... here is the relevant code:/*
Universal class. Edits affect multiple projects.
*/

#ifndef XSCREEN_H
#define XSCREEN_H

//testing
#include <iostream>

#include <algorithm>
#include <functional>
#include <string>
#include <exception>

#include "Base_XImage.h"
#include "Clone_Back_Insert_Iterator.h"
#include "Cloneable.h"
#include "Drawable.h"
#include "IO.h"
#include "Uncopyable.h"

// Type used to mark the end of a typelist.
struct Null_Type
{
};

// The typelist. For nested typelists type U is a typelist again, so one can
// build typelists of any length.
template<typename T, typename U>
struct Type_List
{
typedef T head;
typedef U tail;
};

// Convenience macros for building typelists.
#define TL1(T1) Type_List<T1, Null_Type >
#define TL2(T1,T2) Type_List<T1, TL1(T2) >
#define TL3(T1,T2,T3) Type_List<T1, TL2(T2,T3) >
#define TL4(T1,T2,T3,T4) Type_List<T1, TL3(T2,T3,T4) >
#define TL5(T1,T2,T3,T4,T5) Type_List<T1, TL4(T2,T3,T4,T5) >
#define TL6(T1,T2,T3,T4,T5,T6) Type_List<T1, TL5(T2,T3,T4,T5,T6) >
#define TL7(T1,T2,T3,T4,T5,T6,T7) Type_List<T1, TL6(T2,T3,T4,T5,T6,T7) >
#define TL8(T1,T2,T3,T4,T5,T6,T7,T8) Type_List<T1, TL7(T2,T3,T4,T5,T6,T7,T8) >
#define TL9(T1,T2,T3,T4,T5,T6,T7,T8,T9) Type_List<T1, TL8(T2,T3,T4,T5,T6,T7,T8,T9) >
#define TL10(T1,T2,T3,T4,T5,T6,T7,T8,T9,T10) Type_List<T1, TL9(T2,T3,T4,T5,T6,T7,T8,T9,T10) >

/*
To use this class, pass in a typelist (e.g):
#define MY_TYPE_LIST TL3(XImage, XButton, Class_Derived_From_Base_XImage)

Order matters... in this case XImage then XButton then Class_Der[..] will be
updated and drawn in sequential order.
*/

template<typename Type_List>
class XScreen : public Drawable, public Updateable, public Uncopyable
{
public:
XScreen() {}

XScreen(const std::string& directory)
:
directory(directory)
{
Load<Type_List>();
}

virtual ~XScreen()
{
for(std::vector<Base_XImage*>::iterator it = elements.begin();
it != elements.end(); ++it)
{
delete *it;
}
}

virtual void Draw()
{
std::for_each(
elements.begin(),
elements.end(),
std::mem_fun(&Base_XImage::Draw));
}

virtual void Update(const SDL_Event& event_)
{
std::vector<Base_XImage*>::iterator it = elements.begin();
for(; it != elements.end(); ++it)
{
(*it)->Update(event_);
}
}
protected:
template<typename Type_List>
void Load()
{
// Get the name of the type of each element.
Get_Element_Types<Type_List>();
// Recursively load each type of element from respective file.
Load_Elements<Type_List>();
}

template<typename Type_List>
void Get_Element_Types()
{
// element_types.push_back(Remove_Class_Prefix(typeid(Type_List::head).name()));

// testing
std::string s = Remove_Class_Prefix(typeid(Type_List::head).name());
std::cout << s;
element_types.push_back(s);
// testing

// Recursive call when tail is NOT Null_Type.
Get_Element_Types<Type_List::tail>();
}
// Specialized template function to end typelist traversion(recursion).
template<>
void Get_Element_Types<Null_Type>()
{
}

static std::string XScreen::Remove_Class_Prefix(std::string s)
{
const int PREFIX_LENGTH = 6;
if(s.size() <= PREFIX_LENGTH)
{ // Too small.
throw std::runtime_error("Missing class prefix in Remove_Class_Prefix: " + s);
}

s.erase(s.begin(), s.begin() + 6);
return s;
}

template<typename T>
std::string Get_Element_File_Name()
{
return Remove_Class_Prefix(std::string(typeid(T).name()) + "s.txt");
}

template<typename T>
bool Valid_Element_Class()
{
for(std::vector<std::string>::const_iterator it = element_types.begin();
it != element_types.end(); ++it)
{
if(Remove_Class_Prefix(typeid(T).name()) == *it)
{
return true;
}
}
return false;
}

template<typename T>
void Load_Elements_Of_Type()
{
if(!Valid_Element_Class<T>())
{ // Not a valid element.
throw std::runtime_error("Load_Elements_Of_Type: " +
std::string(typeid(T).name()) + "is not a valid element type.");
}
try
{
std::ifstream file;
IO::open_read_file(file, directory + Get_Element_File_Name<T>());
IO::read_data_clone<T, std::vector<Base_XImage*> >(elements, file);
file.close();
}
catch(const std::runtime_error r)
{ // No file; don't want to load element type "T".
}
}

// The load function. Its template parameter is a typelist which is traversed
// from head to tail. A specialized template function for Null_Type ends the
// recursion.
template<typename Type_List>
void Load_Elements()
{
Load_Elements_Of_Type<Type_List::head>();
// Recursive call when tail is NOT Null_Type.
Load_Elements<Type_List::tail>();
}
// Specialized template function to end typelist traversion(recursion).
template<>
void Load_Elements<Null_Type>()
{
}
protected:
std::vector<std::string> element_types;
std::vector<Base_XImage*> elements;
std::string directory;
};

#endif

Cheers.

Mybowlcut
June 21st, 2008, 03:31 AM
Ok... the directory constructor parameter (string) is also a <Bad Ptr>... This is where XScreen is used:
class Checkers_Game : public Runnable
{
public:
Checkers_Game();
virtual ~Checkers_Game() {}
public:
virtual void Run();
private:
void Game_Over();
void Instructions();
void Menu();
void New_Game();
void High_Scores();
void Exit();
void Mute();
private:
typedef void (Checkers_Game::*Mem_Func)(void);

void Test();

void Init_Event_Mappings();
bool Handle_Events();

SDL_Event sdl_event;

// Need this since we can't "switch" on strings.
std::map<std::string, Mem_Func> event_mappings;

XScreen<SCREEN_TYPELIST>* active_screen;
XScreen<SCREEN_TYPELIST> menu;
XScreen<SCREEN_TYPELIST> instructions;
XScreen<SCREEN_TYPELIST> ingame_GUI;
High_Scores_Screen<SCREEN_TYPELIST> high_scores;

Board board;
bool playing;
bool exit_game;
// Saves resetting the board twice the first time program is run.
bool board_reset_necessary;
};
Checkers_Game::Checkers_Game()
:
menu("Screens/Menu_Screen/"),
ingame_GUI("Screens/Checkers_Screen/"),
instructions("Screens/Instructions_Screen/"),
high_scores(HIGH_SCORES_WRITE_X, HIGH_SCORES_WRITE_Y, HIGH_SCORES_ROW_SPACING,
HIGH_SCORES_DETAIL_SPACING, "Screens/High_Scores_Screen/", "High Scores/", MAX_SCORES),
active_screen(&menu),
playing(false),
exit_game(false),
board_reset_necessary(false)
{
Init_Event_Mappings();
}

Paul McKenzie
June 21st, 2008, 07:50 AM
Hey. I've got a weird problem... I've been building my program in release mode just recently and I've come across a problem where a string is inserted into a vector... the Does the program work correctly?

I ask this, since a release build with optimizations turned on will remove, move, and replace code. What you're seeing in the debugger is not what is being executed when debugging an optimized program.
highlighted code shows the line where the problem is.

The string is fine until it's pushed into the vector... after it's pushed in, the vector still has 0 elements... I stepped into the push_back definition and the parameter _Val was <Bad Ptr>...In release mode, things are optimized away. If you want to debug a release version, you have to make sure that optimizations are turned off.

Regards,

Paul McKenzie

TheCPUWizard
June 21st, 2008, 08:59 AM
Hey. I've got a weird problem...

Weird person...weird problem...no suprise... :D :wave:

Seriously, Pauls assesment is most likely correct if:

1) With optimizations turned off, things look as expected
2) With optimizations turned on, the code functions in an identical manner.

Remember, there is no such thing as "Release Mode". The build is controlled by dozens of options (switch settings). Visual studio creates two of these configurations by default, and happens to call then "Release" and "Debug".

You can create your own, or modify the existing ones. Sometimes (I like to torment certain developers), I will turn off all symbolic information, and enable optimization in the debug configuration, while at the same time enabling symbolic information and disabling optimizations in the release configuration.

Obviously this is not "good practice", and my recommendation is to never modify ANYTHING in either of the two default configurations. Rather create custom configurations. Typical ones I create are: Development, UnitTest,CheckIn, QA, and Production.

Even when the code is completely optimized, you can use the debugger to see exactly what is going on. However, you have to use the disassembly window, the registers window, and the raw memory window to understand it.

Your most likely scenario is that the return value of Remove_Class_Prefix is in a register, and directly passed to the push_back, without ever being stored at the locations the debugger (locals window) expects it.

To verify this:

1) Open Disassembly Window.
2) See what register is being used.
3) Open Register Window to see contents of register
4) Open watch window and cast that address to a std::string.

Mybowlcut
June 21st, 2008, 09:14 AM
Cheers Paul. I'll try that.

Weird person...weird problem...no suprise... :D :wave:

Seriously, Pauls assesment is most likely correct if:

1) With optimizations turned off, things look as expected
2) With optimizations turned on, the code functions in an identical manner.

Remember, there is no such thing as "Release Mode". The build is controlled by dozens of options (switch settings). Visual studio creates two of these configurations by default, and happens to call then "Release" and "Debug".

You can create your own, or modify the existing ones. Sometimes (I like to torment certain developers), I will turn off all symbolic information, and enable optimization in the debug configuration, while at the same time enabling symbolic information and disabling optimizations in the release configuration.

Obviously this is not "good practice", and my recommendation is to never modify ANYTHING in either of the two default configurations. Rather create custom configurations. Typical ones I create are: Development, UnitTest,CheckIn, QA, and Production.

Even when the code is completely optimized, you can use the debugger to see exactly what is going on. However, you have to use the disassembly window, the registers window, and the raw memory window to understand it.

Your most likely scenario is that the return value of Remove_Class_Prefix is in a register, and directly passed to the push_back, without ever being stored at the locations the debugger (locals window) expects it.

To verify this:

1) Open Disassembly Window.
2) See what register is being used.
3) Open Register Window to see contents of register
4) Open watch window and cast that address to a std::string.Haha oi!

Well, I tried to follow your steps.. but I'm not familiar with registers and assembly... I got as far as the picture attached when I realised I was lost... :o

TheCPUWizard
June 21st, 2008, 09:22 AM
Look at the disassembly.

00409664 is the actual call to print.
00409663 is pushing the address of cout from ECX (this was loaded at 0040965C)
00409662 is pushing the address of the string from EAX (this was loaded at 00409652 from the EDI register).

Looking at the call to push_back we see

004096FF is pushing the address of the string from the EDX (this was loaded at 0040966D from the EDI register)

So the EDI register is containing the address of the string.

Mybowlcut
June 21st, 2008, 09:29 AM
Look at the disassembly.

00409664 is the actual call to print.
00409663 is pushing the address of cout from ECX (this was loaded at 0040965C)
00409662 is pushing the address of the string from EAX (this was loaded at 00409652 from the EDI register).

Looking at the call to push_back we see

004096FF is pushing the address of the string from the EDX (this was loaded at 0040966D from the EDI register)

So the EDI register is containing the address of the string.Hmm... I stopped debugging to post and I started debugging again and all the addresses have changed... I'm also unsure of how to cast an address to a string? I tried (std::string)(*0040B22B) but I really don't know... haha.

Oh, I think the addresses changed because I turned optimisations off as per Paul's advice. It seemed to have the same problem though.