c++ - tipos - Almacenamiento en caché de un const char*como un tipo de retorno
¿qué pasa si borro los datos almacenados en caché? (12)
Estaba leyendo un poco en mi C ++, y encontré este artículo sobre RTTI (identificación del tipo de tiempo de ejecución): http://msdn.microsoft.com/en-us/library/70ky2y6k(VS.80).aspx . Bueno, ese es otro tema :) - Sin embargo, tropecé con un dicho extraño en la clase type_info
, concretamente sobre el ::name
método. Dice: "La función de miembro type_info::name
devuelve un const char*
a una cadena terminada en nulo que representa el nombre legible por humanos del tipo. La memoria apuntada se almacena en caché y nunca se debe desasignar directamente."
¿Cómo puedes implementar algo como esto tú mismo? He estado luchando un poco con este problema exacto a menudo antes, ya que no quiero hacer un nuevo parche para la persona que llama para eliminar, así que me he quedado con std::string
hasta el momento.
Entonces, en aras de la simplicidad, digamos que quiero hacer un método que devuelva "Hello World!"
, llamémoslo
const char *getHelloString() const;
Personalmente, lo haría de alguna manera como este (Pseudo):
const char *getHelloString() const
{
char *returnVal = new char[13];
strcpy("HelloWorld!", returnVal);
return returnVal
}
.. Pero esto significaría que la persona que llama debería hacer una delete[]
en mi puntero de retorno :(
Thx por adelantado
¿Por qué el tipo de devolución debe ser const
? No piense en el método como un método de obtención , piense en ello como un método de creación . He visto muchas API que requieren que elimines algo que devuelve un operador / método de creación. Solo asegúrate de anotar eso en la documentación.
/* create a hello string
* must be deleted after use
*/
char *createHelloString() const
{
char *returnVal = new char[13];
strcpy("HelloWorld!", returnVal);
return returnVal
}
Algo como esto haría:
const char *myfunction() {
static char *str = NULL; /* this only happens once */
delete [] str; /* delete previous cached version */
str = new char[strlen("whatever") + 1]; /* allocate space for the string and it''s NUL terminator */
strcpy(str, "whatever");
return str;
}
EDITAR: Algo que se me ocurrió es que un buen reemplazo para esto podría ser devolver un boost :: shared_pointer en su lugar. De esta forma, la persona que llama puede conservarlo todo el tiempo que quiera y no tiene que preocuparse por eliminarlo explícitamente. Un compromiso justo IMO.
Creo que como saben que hay un número finito de estos, simplemente los conservan para siempre. Puede ser apropiado que lo haga en algunos casos, pero como regla general, std :: string va a ser mejor.
También pueden buscar nuevas llamadas para ver si ya hicieron esa cadena y devolver el mismo puntero. De nuevo, dependiendo de lo que estés haciendo, esto también puede ser útil para ti.
Lo que he hecho a menudo cuando necesito este tipo de funcionalidad es tener un puntero char * en la clase, inicializado en nulo, y asignarlo cuando sea necesario.
verbigracia:
class CacheNameString
{
private:
char *name;
public:
CacheNameString():name(NULL) { }
const char *make_name(const char *v)
{
if (name != NULL)
free(name);
name = strdup(v);
return name;
}
};
No puedes confiar en GC; esto es C ++ Eso significa que debe mantener la memoria disponible hasta que finalice el programa. Simplemente no sabe cuándo es seguro eliminar []. Entonces, si quiere construir y devolver un const char *, simple new [] it y devolverlo. Acepta la fuga inevitable.
Probablemente se haga usando un buffer estático:
const char* GetHelloString()
{
static char buffer[256] = { 0 };
strcpy( buffer, "Hello World!" );
return buffer;
}
Este búfer es como una variable global a la que solo se puede acceder desde esta función.
Qué tal esto:
const char *getHelloString() const
{
return "HelloWorld!";
}
Devolver un literal directamente significa que el espacio para la cadena se asigna en almacenamiento estático por el compilador y estará disponible durante todo el tiempo que dure el programa.
Tenga cuidado al implementar una función que asigna un trozo de memoria y luego espera que la persona que llama lo desasigne, como lo hace en el PO:
const char *getHelloString() const
{
char *returnVal = new char[13];
strcpy("HelloWorld!", returnVal);
return returnVal
}
Al hacer esto, transfiere la propiedad de la memoria a la persona que llama. Si llama a este código desde alguna otra función:
int main()
{
char * str = getHelloString();
delete str;
return 0;
}
... la semántica de transferir la propiedad de la memoria no está clara, creando una situación en la que es más probable que haya errores y pérdidas de memoria.
Además, al menos en Windows, si las dos funciones están en 2 módulos diferentes, podría dañar el montón. En particular, si main () está en hello.exe, compilado en VC9 y getHelloString () está en utility.dll, compilado en VC6, corromperá el montón cuando elimine la memoria. Esto se debe a que VC6 y VC9 usan su propio montón, y no son el mismo montón, por lo que está asignando de un montón y desasignando de otro.
Me gustan todas las respuestas sobre cómo se puede asignar estáticamente la cadena, pero eso no es necesariamente cierto para todas las implementaciones, especialmente aquella a la que se vinculó el cartel original. En este caso, parece que el nombre del tipo decorado se almacena estáticamente para ahorrar espacio, y el nombre del tipo no decorado se calcula bajo demanda y se almacena en caché en una lista vinculada.
Si tiene curiosidad acerca de cómo la implementación de Visual C ++ type_info::name()
asigna y almacena en caché su memoria, no es difícil de averiguar. Primero, crea un pequeño programa de prueba:
#include <cstdio>
#include <typeinfo>
#include <vector>
int main(int argc, char* argv[]) {
std::vector<int> v;
const type_info& ti = typeid(v);
const char* n = ti.name();
printf("%s/n", n);
return 0;
}
Compilarlo y ejecutarlo bajo un depurador (utilicé WinDbg) y mirar el puntero devuelto por type_info::name()
. ¿Señala a una estructura global? Si es así, el comando ln
de WinDbg le dirá el nombre del símbolo más cercano:
0:000> ?? n
char * 0x00000000`00857290
"class std::vector<int,class std::allocator<int> >"
0:000> ln 0x00000000`00857290
0:000>
No imprimió nada, lo que indica que la cadena no estaba en el rango de direcciones propiedad de ningún módulo específico. Estaría en ese rango si estuviera en el segmento de datos o de solo lectura. Veamos si se asignó en el montón, buscando en todos los montones la dirección devuelta por type_info::name()
:
0:000> !heap -x 0x00000000`00857290
Entry User Heap Segment Size PrevSize Unused Flags
-------------------------------------------------------------------------------------------------------------
0000000000857280 0000000000857290 0000000000850000 0000000000850000 70 40 3e busy extra fill
Sí, fue asignado en el montón. Poner un punto de interrupción al inicio de malloc()
y reiniciar el programa lo confirma.
Ver la declaración en <typeinfo>
da una pista sobre dónde se están almacenando en caché los punteros del montón:
struct __type_info_node {
void *memPtr;
__type_info_node* next;
};
extern __type_info_node __type_info_root_node;
...
_CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;
Si encuentra la dirección de __type_info_root_node
y camina por la lista en el depurador, encontrará rápidamente un nodo que contiene la misma dirección devuelta por type_info::name()
. La lista parece estar relacionada con el esquema de almacenamiento en caché.
La página MSDN vinculada en la pregunta original parece llenar los espacios en blanco: el nombre se almacena en su forma decorada para ahorrar espacio, y este formulario es accesible a través de type_info::raw_name()
. Cuando llama a type_info::name()
por primera vez en un tipo determinado, descifra el nombre, lo almacena en un búfer asignado en el montón, almacena en caché el puntero del búfer y lo devuelve.
La lista vinculada también se puede usar para desasignar las cadenas en caché durante la salida del programa (sin embargo, no verifiqué si ese es el caso). Esto garantizaría que no aparezcan como pérdidas de memoria cuando ejecuta una herramienta de depuración de memoria.
Bueno, si solo estamos hablando de una función, siempre queremos devolver el mismo valor. es bastante simple.
const char * foo()
{
static char[] return_val= "HelloWorld!";
return return_val;
}
El truco es cuando comienzas a hacer cosas donde estás almacenando el resultado en caché, y luego tienes que considerar el enhebrar, o cuando tu caché se invalida, y tratando de almacenar cosas en el almacenamiento local de la secuencia. Pero si se trata de una producción única que se copia de inmediato, esto debería ser el truco.
Alternativamente, si no tiene un tamaño fijo, tiene que hacer algo donde tenga que usar un búfer estático de tamaño arbitrario ... en el que eventualmente podría tener algo demasiado grande, o pasar a una clase administrada, decir std::string
.
const char * foo()
{
static std::string output;
DoCalculation(output);
return output.c_str();
}
también la función de la firma
const char *getHelloString() const;
solo es aplicable para funciones miembro. En ese momento no es necesario tratar con variables locales de funciones estáticas y solo podría usar una variable miembro.
El consejo dado que advierte sobre la vida útil de la cadena devuelta es un consejo sensato. Siempre debe tener cuidado al reconocer sus responsabilidades cuando se trata de administrar la vida útil de los punteros devueltos. Sin embargo, la práctica es bastante segura, siempre que la variable apuntada dure más que la llamada a la función que la devolvió. Considere, por ejemplo, el puntero a const char devuelto por c_str()
como un método de clase std::string
. Esto devuelve un puntero a la memoria administrada por el objeto de cadena que se garantiza que es válida siempre que el objeto de cadena no se elimine o se haga para reasignar su memoria interna.
En el caso de la clase std::type_info
, es una parte del estándar de C ++ como lo implica su espacio de nombres. La memoria devuelta desde name()
apunta a la memoria estática creada por el compilador y el enlazador cuando se compiló la clase y forma parte del sistema de identificación de tipo de tiempo de ejecución (RTTI). Como se refiere a un símbolo en el espacio del código, no debe intentar eliminarlo.
Creo que algo así solo puede implementarse "limpiamente" usando objetos y el modismo RAII. Cuando se llama al destructor de objetos (obj está fuera del alcance), podemos suponer con seguridad que los punteros const char*
no se usarán.
código de ejemplo:
class ICanReturnConstChars
{
std::stack<char*> cached_strings
public:
const char* yeahGiveItToMe(){
char* newmem = new char[something];
//write something to newmem
cached_strings.push_back(newmem);
return newmem;
}
~ICanReturnConstChars(){
while(!cached_strings.empty()){
delete [] cached_strings.back()
cached_strings.pop_back()
}
}
};
La única otra posibilidad que conozco es pasar un smart_ptr ..