resueltos - Devolver memoria asignada dinámicamente de C++ a C
memoria malloc (10)
Tengo un dll que debe ser utilizable desde C, etc., así que no puedo usar objetos de cadena, etc. como lo haría normalmente, pero no estoy seguro de cómo hacerlo de forma segura ...
const char *GetString()
{
std::stringstream ss;
ss << "The random number is: " << rand();
return ss.str().c_str();
}
podría la cadena c ser destruida cuando ss se cae de la pila? Asumo que sí ...
Otra opción puede ser crear una nueva cadena en el montón, pero ¿qué va a desasignar?
const char *GetString()
{
std::stringstream ss;
ss << "The random number is: " << rand();
char *out = new char[ss.str().size()];
strcpy(ss.str().c_str(), out);
return out;//is out ever deleted?
}
Lo mismo ocurre con los punteros a otras cosas, así como a las cadenas.
Bien, obviamente, cada vez que devuelve punteros a la memoria asignada dentro de una función, la desasignación debe venir externamente, a menos que esté utilizando la recolección de basura. Si no desea hacer esto, asigne un búfer de caracteres antes de llamar a GetString () y cambie el prototipo a
int get_string (const char * buffer);
Luego llene el buffer. Pero devolver un punto a los datos mal clasificados está bien.
Con los años, C redujo esto a 2 métodos estándar:
- La persona que llama pasa en el búfer.
Hay tres versiones de esto.
Versión 1: pase un buffer y una longitud.
Versión 2: la documentación especifica un tamaño de búfer mínimo esperado.
Versión 3: Pre-Vuelo. La función devuelve el búfer mínimo requerido. llamador llama dos veces la primera vez con un búfer NULL.- Ejemplo: leer ()
- Use un buffer estático que sea válido hasta la próxima llamada.
- Ejemplo: tmpname ()
Unos pocos no estándar devolvieron la memoria que tenía que liberar explícitamente
- strdup () aparece en tu mente.
Extensión común pero no realmente en el estándar.
Debe asignar la cadena en el montón si desea devolverla de manera segura, también asignar con malloc () iso new () al escribir funciones C.
Cuando devuelve punteros (y, a diferencia de C ++, en C no tiene muchas opciones), la desasignación siempre es una preocupación. Realmente no hay una solución definitiva.
Una forma de manejar esto que he visto en bastante API es llamar a todas las funciones ya sea
CreateString()
Cuando la memoria necesita ser desasignada por la persona que llama, y
GetString()
cuando eso no es un problema
Esto es todo menos infalible, por supuesto, pero con la disciplina suficiente es el mejor método que he visto para ser honesto ...
Después de llamar a la función, querrá que la persona que llama sea responsable de la memoria de la cadena (y especialmente para desasignarla). A menos que quiera usar variables estáticas, ¡pero habrá dragones! La mejor manera de hacer esto limpiamente es hacer que la persona que llama haga la asignación de la memoria en primer lugar:
void foo() {
char result[64];
GetString(result, sizeof(result));
puts(result);
}
y luego GetString debería verse así:
int GetString(char * dst, size_t len) {
std::stringstream ss;
ss << "The random number is: " << rand();
strncpy(ss.str().c_str(), dst, len);
}
Pasar la longitud máxima de la memoria intermedia y usar strncpy () evitará sobrescribir accidentalmente la memoria intermedia.
El primero en realidad no funcionaría porque el stringstream lo desasigna de la destrucción. Por lo tanto, si intenta desviar el puntero, es muy probable que su programa falle.
La segunda opción que menciona es cómo se hace normalmente y se requiere que el usuario de la función desasigne el espacio. Si este es un programa C que usa la función, asegúrese de asignar con malloc () y libre con free ()
Otra opción es devolver una dirección de una matriz de caracteres estática. Esto es relevante si conoce de antemano un buen límite superior a la longitud. Lo que es más importante, esto debe usarse ÚNICAMENTE si no hay posibilidad de que la función vaya a ser llamada desde dos hilos diferentes al mismo tiempo porque el uso de una matriz estática básicamente hace que su función no sea reentrante .
La primera variante no funciona porque devuelve un puntero a un objeto de pila, que se destruirá. (Más presurosamente, devuelve un puntero a una memoria de montón, que se habrá eliminado ().) Peor aún, puede que funcione durante un tiempo, si nadie sobrescribe la memoria, lo que dificulta la depuración.
A continuación, no puede devolver un const char * a menos que devuelva un puntero a una cadena estática como esta:
const char *GetString()
{
return "a static string in DATA segment - no need to delete";
}
Su segunda variante tiene el problema de devolver la memoria asignada con new () en un programa C que llamará a free (). Esos pueden no ser compatibles.
Si devuelve una cadena a C, hay dos formas de hacerlo:
char *GetString()
{
std::stringstream ss;
ss << "The random number is: " << rand();
return strdup( ss.str().c_str() ); // allocated in C style with malloc()
}
void foo()
{
char *p = GetString();
printf("string: %s", p));
free( p ); // must not forget to free(), must not use delete()
}
o:
char *GetString(char *buffer, size_t len)
{
std::stringstream ss;
ss << "The random number is: " << rand();
return strncpy(buffer, ss.str().c_str(), len); // caller allocates memory
}
void foo()
{
char buffer[ 100 ];
printf("string: %s", GetString(buffer, sizeof( buffer ))); // no memory leaks
}
dependiendo de su política de manejo de memoria.
Como regla general, nunca se puede devolver un puntero o una referencia a un objeto automático en C ++. Este es uno de los errores comunes que se analizan en muchos libros de C ++.
Si declaras ss como estática, puedes evitar el problema. Esta podría ser una buena solución si su programa se ejecuta en un entorno de subproceso único.
Si la seguridad del hilo no es importante,
const char *GetString()
{
static char *out;
std::stringstream ss;
ss << "The random number is: " << rand();
delete[] out;
char *out = new char[ss.str().size()];
strcpy(ss.str().c_str(), out);
return out;//is out ever deleted?
}
Entonces la función puede asumir la responsabilidad de desasignar la cadena.
Si la seguridad de hilo es importante,
Entonces, el mejor método es pasarlo como argumento, como en
void GetString(char *out, int maxlen);
Observo que esto es lo que sucede cuando las antiguas API que no son seguras para subprocesos se cambian a thread-safe.
Las respuestas hasta ahora no abordan un tema muy importante, a saber, qué hacer si se desconoce la longitud del búfer requerido para el resultado y puede cambiar entre llamadas, incluso con los mismos argumentos (como leer un valor de una base de datos) , entonces estoy brindando lo que considero la mejor forma de manejar esta situación.
Si el tamaño no se conoce de antemano, considere pasar una función de devolución de llamada a su función, que recibe la const char*
como parámetro:
typedef void (*ResultCallback)( void* context, const char* result );
void Foo( ResultCallback resultCallback, void* context )
{
std::string s = "....";
resultCallback( context, s.c_str() );
}
La implementación de ResultCallback
puede asignar la memoria necesaria y copiar el búfer al que apunta el result
. Asumo C, así que no estoy lanzando a / desde void*
explícitamente.
void UserCallback( void* context, const char* result )
{
char** copied = context;
*copied = malloc( strlen(result)+1 );
strcpy( *copied, result );
}
void User()
{
char* result = NULL;
Foo( UserCallback, &result );
// Use result...
if( result != NULL )
printf("%s", result);
free( result );
}
Esta es la solución más portátil y maneja incluso los casos más difíciles donde el tamaño de la cadena devuelta no se puede conocer de antemano.
Existen varios métodos, desarrollados a lo largo del tiempo, para devolver una cantidad variable de datos de una función.
- La persona que llama pasa en el búfer.
- El tamaño necesario está documentado y no se pasa, los búferes demasiado cortos son Comportamiento no
strcpy()
:strcpy()
- El tamaño necesario se documenta y se pasa, los errores se señalan por el valor de retorno:
strcpy_s()
- El tamaño necesario es desconocido, pero se puede consultar llamando a la función con buffer-length 0:
snprintf
- El tamaño necesario es desconocido y no se puede consultar, se devuelve todo lo que cabe en un búfer de tamaño pasado. Si es necesario, se deben realizar llamadas adicionales para obtener el resto:
fread
- ⚠ El tamaño necesario es desconocido, no puede ser consultado, y pasar un buffer demasiado pequeño es un Comportamiento no definido . Este es un defecto de diseño, por lo tanto, la función está obsoleta / eliminada en las versiones más recientes, y se menciona aquí para completar:
gets
.
- El tamaño necesario está documentado y no se pasa, los búferes demasiado cortos son Comportamiento no
- La persona que llama pasa una devolución de llamada:
- La función de devolución de llamada obtiene un parámetro de contexto:
qsort_s
- La función de devolución de llamada no tiene parámetro de contexto. Obtener el contexto requiere magia:
qsort
- La función de devolución de llamada obtiene un parámetro de contexto:
- La persona que llama pasa un asignador: no se encuentra en la biblioteca estándar de C. Sin embargo, todos los contenedores C ++ compatibles con el asignador lo admiten.
- El contrato de Callee especifica el desasignador. Llamar a la incorrecta es Comportamiento Indefinido :
fopen
->fclose
strdup
->free
- Callee devuelve un objeto que contiene el
std::shared_ptr
: COM-Objectsstd::shared_ptr
- Callee usa un buffer interno compartido:
asctime
En general, cada vez que el usuario tiene que adivinar el tamaño o buscarlo en el manual, a veces se equivoca. Si no se equivoca, una revisión posterior puede invalidar su trabajo cuidadoso, por lo que no importa que haya tenido una vez la razón. De todos modos, de esta manera se encuentra la locura (UB) .
Por lo demás, elija el más cómodo y eficiente que pueda.