2010-07-09 6 views
1

J'ai eu quelques difficultés à diagnostiquer un défaut de segmentation résultant, ou du moins je pense, résultant d'une classe statique template'd (voir l'article original ici Help understanding segfault with std::map/boost::unordered_map).Comportement étrange avec la classe template'd statique

Depuis la publication que j'ai trouvé un autre comportement bizarre dans mon programme. Je n'ai jamais rencontré quoi que ce soit de pareil, et je pense que je ne dois pas être conscient de certains détails subtils sur le fonctionnement des modèles, ce qui me fait faire quelque chose de mal quelque part.

J'ai essayé de refactoriser des choses sans réfléchir pour essayer de faire disparaître le problème mais ça persiste.

Le symptôme le plus déconcertant est la suivante: dans un fichier source (Menu.cpp) Je les trois appels suivants:

Font::init(); 
// later 
g_font = Font::get("Mono.ttf"); 
// later 
Font::release(); 

Dans l'autre fichier source (Game.cpp) J'ai presque les trois mêmes lignes. Presque tout le code dans Menu.cpp est exécuté avant que n'importe quoi dans Game.cpp soit exécuté.

Maintenant tout va bien avec cela. Cependant, si je simplement commenter

g_font = Font::get("Arial.ttf"); 

dans Game.cpp le programme rencontre une erreur de segmentation à g_font = ... en Menu.cpp (note: tout Menu.cpp et Game.cpp ont leur propre espace de noms). Mais le code NO a même été exécuté dans Game.cpp à ce stade!

Maintenant, si je lie Menu.o avant Game.o le problème disparaît (mais d'autres problèmes existent ailleurs). Qu'est-ce que je ne comprends pas ici?

J'ai utilisé un débogueur pour parcourir le programme pour voir ce qui se passe. Lorsque la ligne g_font est mise en commentaire dans Game.cpp, un conteneur boost (unordered_map) dans la classe de base Font de la ressource n'est pas initialisé correctement. Plus précisément, buckets_ et size_ ne sont pas initialisés et entraînent un comportement erratique. J'ai essayé d'utiliser std :: map et des problèmes similaires existent.

Voici la liste complète des Font.hpp

#ifndef __Font_hpp__ 
#define __Font_hpp__ 

#include <string> 
#include "Vector.hpp" 
#include "Resource.hpp" 

class FTPixmapFont; 

/*! 
* Adapter class for FTGL's FTPixmapFont 
*/ 
class Font : public Resource<Font> 
{ 
public: 
    Font(const std::string& fileName); 
~Font(); 

void render(const std::string& str, const Vector& pos, int ptSize=14) const; 

private: 
FTPixmapFont *m_font; 
}; 

#endif // __Font_hpp__ 

Voici la liste complète des Resource.hpp (il potentiellement fuites de mémoire en ce moment sur release(), j'utilisais à l'origine boost::shared_ptr dans le conteneur, mais commuté aux pointeurs bruts en pensant que cela réparerait tout, doh).

#ifndef __Resource_hpp__ 
#define __Resource_hpp__ 

#include <string> 
#include <map> 
#include <boost/unordered_map.hpp> 
#include <boost/utility.hpp> 
#include "debug.hpp" 
#include "assert.hpp" 

#define MAP_TYPE boost::unordered_map 

/*! 
* Resource base class. 
*/ 
template <class T> 
class Resource : public boost::noncopyable 
{ 
public: 
static void init(const std::string& dir="data") 
{ 
    ASSERT(!c_init); 
    if (*dir.rbegin() == '/') { 
    c_dataDirectory = dir; 
    } else { 
    c_dataDirectory = dir + '/'; 
    } 
    c_init = true; 
} 

static void release() 
{ 
    ASSERT(c_init); 
    c_dataDirectory.clear(); 
    c_resources.clear(); 
    c_init = false; 
} 

static const T *get(const std::string& fileName) 
{ 
    T *resource = NULL; 

    typename MAP_TYPE<std::string, T*>::const_iterator itr = c_resources.find(fileName); 
    if (itr == c_resources.end()) { 
    resource = new T(c_dataDirectory + fileName); 
    c_resources.insert(std::pair<std::string, T*>(fileName, resource)); 
    } else { 
    resource = itr->second; 
    } 

    return resource; 
} 

private: 
static bool c_init; 
static std::string c_dataDirectory; 
static typename MAP_TYPE<std::string, T*> c_resources; 
}; 

template <class T> bool Resource<T>::c_init = false; 
template <class T> std::string Resource<T>::c_dataDirectory; 
template <class T> MAP_TYPE<std::string, T*> Resource<T>::c_resources; 

#endif // __Resource_hpp__ 

Voici une sortie (pile trace) de gdb avec MAP_TYPE = boost::unordered_map:

Reading symbols from /home/tim/Projects/gameproj/app/game...done. 
(gdb) r 
Starting program: /home/tim/Projects/gameproj/app/game 
[Thread debugging using libthread_db enabled] 

Program received signal SIGSEGV, Segmentation fault. 
0x0000000000499aca in boost::unordered_detail::hash_table<boost::unordered_detail::map<std::string, boost::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, Font*> > > >::find_iterator (this=0x79bd80, 
    bucket=0x20, k=...) at /usr/local/include/boost/unordered/detail/table.hpp:55 
55   node_ptr it = bucket->next_; 
(gdb) bt 
#0 0x0000000000499aca in boost::unordered_detail::hash_table<boost::unordered_detail::map<std::string, boost::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, Font*> > > >::find_iterator (this=0x79bd80, 
    bucket=0x20, k=...) at /usr/local/include/boost/unordered/detail/table.hpp:55 
#1 0x0000000000499872 in boost::unordered_detail::hash_table<boost::unordered_detail::map<std::string, boost::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, Font*> > > >::find (this=0x79bd80, k=...) 
    at /usr/local/include/boost/unordered/detail/table.hpp:583 
#2 0x00000000004994fb in boost::unordered_map<std::string, Font*, boost::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, Font*> > >::find (this=0x79bd80, k=...) 
    at /usr/local/include/boost/unordered/unordered_map.hpp:423 
#3 0x00000000004992ab in Resource<Font>::get (fileName=...) at /home/tim/Projects/gameproj/app/Resource.hpp:45 
#4 0x0000000000498e23 in Menu::init (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Menu.cpp:57 
#5 0x0000000000498ce1 in Menu::run (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Menu.cpp:23 
#6 0x0000000000481275 in Main::run (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Main.cpp:25 
#7 0x0000000000481135 in main (argc=1, argv=0x7fffffffe398) at /home/tim/Projects/gameproj/app/main.cpp:10 
(gdb) 

Voici une sortie (trace de la pile) de gdb avec MAP_TYPE = std::map:

Reading symbols from /home/tim/Projects/gameproj/app/game...done. 
(gdb) r 
Starting program: /home/tim/Projects/gameproj/app/game 
[Thread debugging using libthread_db enabled] 

Program received signal SIGSEGV, Segmentation fault. 
0x00007ffff701ae4d in std::string::compare(std::string const&) const() from /usr/lib/libstdc++.so.6 
(gdb) bt 
#0 0x00007ffff701ae4d in std::string::compare(std::string const&) const() from /usr/lib/libstdc++.so.6 
#1 0x0000000000480c2d in std::operator< <char, std::char_traits<char>, std::allocator<char> > (__lhs=..., __rhs=...) 
    at /usr/include/c++/4.4/bits/basic_string.h:2320 
#2 0x0000000000480a15 in std::less<std::string>::operator() (this=0x772d60, __x=..., __y=...) 
    at /usr/include/c++/4.4/bits/stl_function.h:230 
#3 0x0000000000480691 in std::_Rb_tree<std::string, std::pair<std::string const, Font*>, std::_Select1st<std::pair<std::string const, Font*> >, std::less<std::string>, std::allocator<std::pair<std::string const, Font*> > >::find (this=0x772d60, 
    __k=...) at /usr/include/c++/4.4/bits/stl_tree.h:1424 
#4 0x0000000000480465 in std::map<std::string, Font*, std::less<std::string>, std::allocator<std::pair<std::string const, Font*> > >::find (this=0x772d60, __x=...) at /usr/include/c++/4.4/bits/stl_map.h:659 
#5 0x000000000048027d in Resource<Font>::get (fileName=...) at /home/tim/Projects/gameproj/app/Resource.hpp:45 
#6 0x000000000047ff97 in Menu::init (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Menu.cpp:57 
#7 0x000000000047fe55 in Menu::run (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Menu.cpp:23 
#8 0x000000000046a725 in Main::run (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Main.cpp:25 
#9 0x000000000046a5e5 in main (argc=1, argv=0x7fffffffe398) at /home/tim/Projects/gameproj/app/main.cpp:10 

J'ai également utilisé Valgrind, il n'y a pas d'autres erreurs que lorsque l'erreur de segmentation se produit. J'utilise CMake pour générer des Makefiles, gcc 4.4.3 et boost 1.43. Je construis sur une machine Ubuntu 64 bits.

Toute aide à ce sujet serait grandement appréciée, je me sens comme si je n'allais nulle part avec elle. En attendant, je vais essayer de construire sur une plate-forme/machine différente pour voir si j'ai le même comportement.

+0

Votre programme est multithread? Vous n'avez aucun verrouillage. – Stephen

+1

Actuellement, il n'y a qu'un seul thread. – Tim

+1

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 – Anycorn

Répondre

0

Essayez de changer

template <class T> std::string Resource<T>::c_dataDirectory; 
template <class T> MAP_TYPE<std::string, T*> Resource<T>::c_resources; 

à utiliser explicitement les constructeurs par défaut? Peut-être que cela est nécessaire pour les modèles - les Annotations C++ utilisent ce style, bien qu'ils ne commentent pas pourquoi.

Si personne ne l'a encore mentionné, il existe une copie du membre de données statiques par classe.

0

Essayez d'utiliser le motif de conception Singleton. Vous pouvez par exemple utiliser SingletonHolder de Loki (http://loki-lib.sourceforge.net/index.php?n=Main.HomePage). Et si vous avez une configuration d'objet Singleton appropriée, vous pouvez ignorer les membres statiques, puisque string et unordered_map conservent quand même tous leurs membres dynamiquement. Ou en faire des pointeurs statiques, et assurez-vous de les supprimer correctement.