c++ - smart - unique pointer
¿Cuál es el recuento máximo de referencias en std:: shared_ptr? ¿Qué pasa si intentas superarlo? (5)
Si asumimos que std::shared_ptr
almacena un recuento de referencias (lo cual me std::shared_ptr
que el estándar no requiere, pero no conozco ninguna implementación que no lo haga), ese recuento de referencias tiene un número limitado de bits, y eso significa que hay un número máximo de referencias que son compatibles. Eso lleva a dos preguntas:
- ¿Cuál es este valor máximo?
- ¿Qué sucede si intenta superarlo (p. Ej., Copiando un std :: shared_ptr que hace referencia a un objeto con el recuento de referencia máximo)? Tenga en cuenta que el constructor de copia de
std::shared_ptr
se declaranoexcept
.
¿El estándar arroja alguna luz sobre cualquiera de estas preguntas? ¿Qué hay de las implementaciones comunes, por ejemplo, gcc, MSVC, Boost?
El estándar de C ++ 11 especifica long
que el tipo de retorno de la función de observador use_count()
, pero no especifica explícitamente si una implementación debe admitir hasta 2^(sizeof(long)*8-1)-1
propiedades compartidas.
Tampoco especifica qué sucede cuando se desborda el contador de referencia.
La implementación boost::shared_ptr
(por ejemplo, 1.58 en Fedora 23, x86-64) utiliza internamente el contador de 32 bits y no comprueba los desbordamientos.
Eso significa:
- el recuento máximo de referencia es
2^31-1
. - Si tiene una propiedad de desbordamiento y liberación, puede terminar con algunos problemas de uso después de la liberación.
Ya que boost utiliza diferentes especializaciones de bajo nivel para diferentes plataformas, puede verificar los detalles estableciendo un punto de interrupción en *add_ref_lock
- en Fedora 23 / x86-64 parará aquí:
/usr/include/boost/smart_ptr/detail/sp_counted_base_gcc_x86.hpp
[..]
int use_count_; // #shared
int weak_count_; // #weak + (#shared != 0)
[..]
bool add_ref_lock() // true on success
{
return atomic_conditional_increment( &use_count_ ) != 0;
}
Ver también:
- Desbordamiento de enteros en uso contador de punteros compartidos - Boost Ticket 12335
- El doble de bits, el doble de problemas: las vulnerabilidades inducidas por la migración a plataformas de 64 bits
La implementación del punto compartido de GNU STL (libstdc ++) se basa en Boost 1.32 one y también tiene este problema (en Fedora 23 / x86-64): allí se _Atomic_word
tipo _Atomic_word
para el conteo de referencias. También es ''solo'' de 32 bits y no se verifica el desbordamiento.
En contraste, la implementación de LLVM libc ++ shared_ptr
utiliza un contador de referencia long
, es decir, en plataformas LP64 como x86-64 puede compartir un objeto entre hasta 2^63-1
propietarios.
La norma no dice; Como dices, ni siquiera requiere el recuento de referencias. Por otro lado, hay (o hubo) una declaración en el estándar (o al menos en el estándar C) de que exceder los límites de implementación es un comportamiento indefinido. Así que esa es casi seguramente la respuesta oficial.
En la práctica, espero que la mayoría de las implementaciones mantengan el recuento como size_t
o ptrdiff_t
. En máquinas con direccionamiento plano, esto significa que no puede crear suficientes referencias para provocar un desbordamiento. (En tales máquinas, un solo objeto podría ocupar toda la memoria, y size_t
o ptrdiff_t
tienen el mismo tamaño que un puntero. Como cada puntero de referencia tiene una dirección distinta, nunca puede haber más de lo que cabría en un puntero). En máquinas con arquitecturas segmentadas, sin embargo, el desbordamiento es bastante concebible.
Como señala Jon, el estándar también requiere que std::shared_ptr::use_count()
devuelva un long
. No estoy seguro de cuál es la razón aquí: ya sea size_t
o ptrdiff_t
tendría más sentido aquí. Pero si la implementación usa un tipo diferente para el recuento de referencia, presumiblemente, las reglas para la conversión a long
se aplicarían: "el valor no cambia si puede representarse en el tipo de destino (y el ancho del campo de bits); de lo contrario, el valor es la implementación definida ". (El estándar C lo hace más claro: el "valor definido por la implementación" puede ser una señal).
No estoy seguro de lo que sugiere la norma, pero míralo prácticamente:
La cuenta de referencia es probablemente una especie de variable std::size_t
. Esta variable puede contener valores de hasta -1+2^32
en entornos de 32 bits y hasta -1+2^64
en entornos de 64 bits.
Ahora imagina lo que tendría que pasar para que esta variable alcance este valor: necesitarías 2 ^ 32 o 2 ^ 64 shared_ptr
. Eso es mucho. De hecho, son tantos que toda la memoria se agotará mucho antes de que alcance este número, ya que un shared_ptr
tiene un shared_ptr
aproximadamente 8/16 bytes.
Por lo tanto, es muy poco probable que pueda alcanzar el límite del conteo de referencia si el tamaño de la variable refcount es lo suficientemente grande.
Podemos obtener cierta información de la función shared_ptr::use_count()
. §20.7.2.2.5 dice:
long use_count() const noexcept;
Devuelve: el número de objetos
shared_ptr
,*this
incluido, que comparte la propiedad con*this
, o0
cuando*this
está vacío.[Nota:
use_count()
no es necesariamente eficiente. — nota final]
A primera vista, el tipo de retorno long
parece responder a la primera pregunta. Sin embargo, la nota parece implicar que shared_ptr
es libre de usar cualquier tipo de referencia que desee, incluyendo cosas como una lista de referencias. Si este fuera el caso, en teoría no habría un recuento máximo de referencias (aunque sin duda habría un límite práctico ).
No hay otra referencia a los límites en el número de referencias al mismo objeto que pude encontrar.
Es interesante notar que use_count
está documentado para no lanzar y (obviamente) para reportar el conteo correctamente; a menos que la implementación use un miembro long
para el conteo, no veo cómo se pueden garantizar teóricamente ambos en todo momento.
Puede averiguar qué sucederá si crea una instancia de los punteros compartidos utilizando la ubicación nueva y nunca los borra. A continuación, puede golpear el límite de 32 bits fácilmente.