Mémoires partagée et placement new

Résolu
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012 - 8 mars 2012 à 20:29
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012 - 11 mars 2012 à 10:01
Bonsoir,

je me demandais s'il était possible, lors d'un placement new sur une class non POD (je suppose que std::string n'est pas plate, mais je me trompe peut être), de spécifier un placement pour les pointeurs internes.
Par exemple imaginons que je souhaite mettre un tableaux de std::string dans une mémoire partagée de manière à ce qu'il soit accessible par d'autres process ?

Je pense que la réponse va être évidente : fait toi une class POD, mais dès fois que, je suis loin de tout connaître en C++...


  Qui ne tente rien...
  Ne risque pas d'avoir grand chose !!!

4 réponses

cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
10 mars 2012 à 17:00
Effectivement, tu as raison. J'aurais du relire le code de std::basic_string plutôt que juste le commentaire, que j'avais mal compris.

Les std::string dépendent d'un allocateur choisi via un paramètre de template. Par défaut c'est celui dans /usr/include/bits/allocator qui est en fait le __glibcxx_base_allocator. Lui même utilise celui donné par le système (chez moi i686-linux-gnu/bits/c++allocator.h).

Dans celui-ci l'allocateur peux soit utiliser un allocateur standard:
      pointer
      allocate(size_type __n, const void* = 0)
      { 
if (__n > this->max_size())
  std::__throw_bad_alloc();

return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
      }



Soit un placement new.
      void 
      construct(pointer __p, const _Tp& __val) 
      { ::new((void *)__p) _Tp(__val); }


Par défaut, c'est un allocateur standard qui est utilisé dans le code du std::basic_string. Tu peux donc soit utiliser un char* soit un std::string mais en réécrivant un allocateur mémoire pour celui-ci. Je te conseille donc de convertir tes std::string en char* et inversement.

(Écrire un allocateur n'est franchement pas simple, vu qu'en cas de réallocation, il faut gérer la copie de char* vers une même zone. C'est néanmoins techniquement possible et présente l'avantage de rendre tout les conteneurs STL compatibles avec la mémoire partagée. Un article super intéressant ici: http://drdobbs.com/184401639)

________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
3
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
9 mars 2012 à 10:48
Bonjour.

Les std::string devraient être compatibles avec un placement new. Ça va dépendre de l'implémentation de la STL que tu utilises, mais généralement les std::string sont fait astucieusement: en interne ils utilisent des placement new de manière à ce qu'ils soient quasiment strictement équivalent à un char* (+1 octet pour le header et \0 terminated).

Je te copie la documentation écrite dans le commentaire de la classe std::basic_string (std::string):
/*
_Rep: string representation
Invariants:
1. String really contains _M_length + 1 characters: due to 21.3.4
must be kept null-terminated.
2. _M_capacity >= _M_length
Allocated memory is always (_M_capacity + 1)  sizeof(_CharT).
3. _M_refcount has three states:
-1: leaked, one reference, no ref-copies allowed, non-const.
0: one reference, non-const.
n>0: n + 1 references, operations require a lock, const.
4. All fields==0 is an empty string, given the extra storage
beyond-the-end for a null terminator; thus, the shared
empty string representation needs no constructor.

A string looks like this:
@code
[_Rep]
_M_length
[basic_string<char_type>]            _M_capacity
_M_dataplus                          _M_refcount
_M_p ---------------->               unnamed array of char_type
@endcode

Where the _M_p points to the first character in the string, and
you cast it to a pointer-to-_Rep and subtract 1 to get a
pointer to the header.

This approach has the enormous advantage that a string object
requires only one allocation.  All the ugliness is confined
within a single pair of inline functions, which each compile to
a single "add" instruction: _Rep::_M_data(), and
string::_M_rep(); and the allocation function which gets a
block of raw bytes and with room enough and constructs a _Rep
object at the front.

The reason you want _M_data pointing to the character array and
not the _Rep is so that the debugger can see the string
contents. (Probably we should add a non-inline member to get
                    the _Rep for the debugger to use, so users can check the actual
                    string length.)

Note that the _Rep object is a POD so that you can have a
static "empty string" _Rep object already "constructed" before
static constructors have run.  The reference-count encoding is
chosen so that a 0 indicates one reference, so you never try to
destroy the empty-string _Rep object.
*/


________________________________________________________________________
Historique de mes créations, et quelques articles:
[ http://0217021.free.fr/portfolio http://0217021.free.fr/portfolio]
Merci d'utiliser Réponse acceptée si un post répond à votre question
0
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012
9 mars 2012 à 19:56
En fait std::string n'est pas une structure plate (sinon en cas de realloc, seul celui qui fait le realloc connait le nouveau pointeur, ce qui s'avère un peu génant :))
J'ai vérifié par ailleurs : sizeof(std::string) = 8 quelque soit le contenu (sur ubuntu 64 bits), et sous gdb :

void *ptr = malloc(1024*1204);
std::string *str = new(ptr) std::string;
str = ptr, ce qui correspond bien a un placement new, mais le contenu :

(gdb) p ptr
$1 = (void *) 0x7ffff7ed4010
(gdb) p str
$2 = (std::string *) 0x7ffff7ed4010
(gdb) p *str
$3 {static npos 18446744073709551615, _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, _M_p = 0x7ffff7dca158 ""}}
(gdb) p - 0x7ffff7dca158 + 0x7ffff7ed4010
$5 = 1089208
(gdb) p 1024*1024
$6 = 1048576

qui montre bien que la donnée effective interne est un pointeur différent, qui se trouve en dehors de la zone allouée précédemment.
Ce qui, si j'avais voulu stocker de cette manière str dans ptr, une mémoire partagée entre différents processus, ne marche absolument pas.

Ma question était donc : peut on certifier lors de la construction de std::string par exemple que le data interner va bien se trouver là ou on le voudrait ?
std::string *str = new(ptr) std::string;
nous assure que str ptr, mais peut on assurer que str->_M_p ptr2 d'une manière ou d'une autre par un placement new ?

Merci d'avance.

  Qui ne tente rien...
  Ne risque pas d'avoir grand chose !!!
0
mondrone Messages postés 246 Date d'inscription mercredi 5 janvier 2005 Statut Membre Dernière intervention 11 mars 2012
11 mars 2012 à 10:01
Merci, c'est exactement ce que je voulais savoir :)
En clair, il faut faire un chunk mémoire se basant uniquement sur la mémoire partagée, et l'utiliser comme allocateur.

  Qui ne tente rien...
  Ne risque pas d'avoir grand chose !!!
0
Rejoignez-nous