¿Cómo distinguir entre cadenas en montón o literales?

Tengo un caso de uso en el que puedo obtener punteros de cadenas asignadas en memoria o en literales. Ahora este último no puede ser liberado, así que es un problema si paso el incorrecto. ¿Hay alguna manera de saber cuál está asignado y cuál no?

char *b = "dont free me!"; if(!IS_LITERAL(b)) { free(b); } 

Me imagino algo así.

Mi ejemplo

Escenario 1: literal

 char *b = "dont free me!"; scruct elem* my_element = mylib_create_element(b); // do smth int result = mylib_destroy_element(my_element); // free literal, very bad 

Escenario 2: en el montón

 char *b = malloc(sizeof(char)*17); // example strncpy(b, "you can free me!",17); scruct elem* my_element = mylib_create_element(b); // do smth int result = mylib_destroy_element(my_element); // free heap, nice 

Cómo llama el usuario mylib_create_element(b); no está bajo mi control Si se libera antes de mylib_destroy_element se puede bloquear. Por lo tanto, tiene que ser mylib_destroy_element que limpia.

He tenido un caso similar recientemente. Esto es lo que hice:

Si está creando una API que acepta un puntero de cadena y luego la usa para crear un objeto ( mylib_create_element ), una buena idea sería copiar la cadena en un búfer de stack separado y luego liberarla a su discreción. De esta manera, el usuario es responsable de liberar la cadena que utilizó en la llamada a su API, lo que tiene sentido. Es su cadena, después de todo.

Tenga en cuenta que esto no funcionará si su API depende de que el usuario cambie la cadena después de crear el objeto.

En la mayoría de los Unixes, hay valores ‘etext’ y ‘edata’. Si su puntero está entre ‘etext’ y ‘edata’, entonces se inicializará estáticamente. Esos valores no se mencionan en ningún estándar, por lo que el uso no es portátil y bajo su propio riesgo.

Ejemplo:

 #include #include extern char edata; extern char etext; #define IS_LITERAL(b) ((b) >= &etext && (b) < &edata) int main() { char *p1 = "static"; char *p2 = malloc(10); printf("%d, %d\n", IS_LITERAL(p1), IS_LITERAL(p2)); } 

Solo puede solicitar al usuario que marque explícitamente su entrada como literal o cadena asignada.

Sin embargo, como @ Mints97 menciona en su respuesta, básicamente este enfoque es incorrecto desde el punto de vista arquitectónico: obliga al usuario de su biblioteca a realizar algunas acciones explícitas y, si se olvida de ello, es muy probable que se produzca una pérdida de memoria (o incluso un locking de la aplicación). . Así que úsalo solo si :

  1. Desea reducir drásticamente la cantidad de asignaciones. En mi caso, fueron los nombres de nodo JSON, que nunca cambian durante la vida útil de un progtwig.
  2. Tienes buen control del código de consumidores de tu biblioteca. En mi caso, las bibliotecas se envían con binarios y están estrechamente vinculadas a ellas.

Ejemplo de implementación

 #define AAS_DYNAMIC 'D' #define AAS_STATIC 'S' #define AAS_STATIC_PREFIX "S" #define AAS_CONST_STR(str) ((AAS_STATIC_PREFIX str) + 1) char* aas_allocate(size_t count) { char* buffer = malloc(count + 2); if(buffer == NULL) return NULL; *buffer = AAS_DYNAMIC; return buffer + 1; } void aas_free(char* aas) { if(aas != NULL) { if(*(aas - 1) == AAS_DYNAMIC) { free(aas - 1); } } } ... char* s1 = AAS_CONST_STR("test1"); char* s2 = aas_allocate(10); strcpy(s2, "test2"); aas_free(s1); aas_free(s2); 

Rendimiento de prueba (nota # 1)

Comparé mi biblioteca libtsjson con el siguiente código (800k iteraciones):

  node = json_new_node(NULL); json_add_integer(node, NODE_NAME("i"), 10); json_add_string(node, NODE_NAME("s1"), json_str_create("test1")); json_add_string(node, NODE_NAME("s2"), json_str_create("test2")); json_node_destroy(node); 

Mi CPU es Intel Core i7 860. Si NODE_NAME es solo una macro, el tiempo por iteración fue 479ns Si NODE_NAME es una asignación de memoria, el tiempo por iteración fue 609ns

Indicación de usuario o comstackdor (nota # 2)

  • Agregue una pista a todos estos punteros, es decir, el analizador de fuente estática de Linux Sparse puede detectar tales problemas

     char __autostring* s1 = aas_copy("test"); /* OK */ char __autostring* s2 = strdup("test"); /* Should be fail? */ char* s3 = s1; /* Confuses sparse */ char* s4 = (char*) s1; /* Explicit conversion - OK */ 

(No estoy completamente seguro de las salidas de Sparse)

  • Use typedef simple para hacer que el comstackdor muestre una advertencia cuando haga algo mal:

     #ifdef AAS_STRICT typedef struct { char a; } *aas_t; #else typedef char *aas_t; #endif 

Este enfoque es un paso más a un mundo de hacks C sucios, es decir, sizeof(*aas_t) ahora es> 1.

La fuente completa con cambios se puede encontrar aquí. Si se comstack con -DAAS_STRICT , boostán toneladas de errores: https://ideone.com/xxmSat Incluso para el código correcto puede quejarse de strcpy() (no reproducido en ideone).

La respuesta simple es que no puede hacer esto ya que el lenguaje C no delimita la sección de stack, montón y datos.

Si desea realizar una conjetura, puede recostackr la dirección de la primera variable en la stack, la dirección de la función de llamada y la dirección de un byte de memoria asignado al montón; y luego compárelo con su puntero, una muy mala práctica sin garantías.

Es mejor que renueve su código de tal manera que no se encuentre con este problema.

Aquí hay una forma práctica:

Aunque el estándar de lenguaje C no dicta esto, para todas las apariciones idénticas de una cadena literal dada en su código, el comstackdor genera una sola copia dentro de la sección de datos de la imagen ejecutable.

En otras palabras, cada aparición de la cadena literal "dont free me!" En su código se traduce a la misma dirección de memoria.

Entonces, en el punto en el que desea desasignar esa cadena, simplemente puede comparar su dirección con la dirección de la cadena literal "dont free me!" :

 if (b != "dont free me!") // address comparison free(b); 

Para enfatizar esto de nuevo, no está impuesto por el estándar de lenguaje C, pero prácticamente es implementado por cualquier comstackdor decente del lenguaje.


Lo anterior es simplemente un truco práctico que se refiere directamente a la pregunta en cuestión (en lugar de a la motivación detrás de esta pregunta).

Estrictamente hablando, si ha alcanzado un punto en su implementación en el que tiene que distinguir entre una cadena asignada estáticamente y una cadena asignada dinámicamente, entonces tenderé a adivinar que su diseño inicial es defectuoso en algún lugar a lo largo de la línea.

Esta es exactamente la razón por la que la regla es que solo la pieza de código o módulo que creó una cadena puede liberarla. En otras palabras, cada cadena o pieza de datos es “propiedad” de la unidad de código que la creó. Sólo el propietario puede liberarlo. Una función nunca debe liberar las estructuras de datos que recibió como argumentos.

Puedes hacer lo siguiente:

  typedef struct { int is_literal; char * array; } elem; 

Cada vez que asigna elem.array en el montón, simplemente establezca is_literal a 0. Cuando configure la matriz para que sea literal, establezca la bandera en un valor distinto de cero, por ejemplo:

 elem foo; foo.array = "literal"; foo.is_literal = 1 ; 

o

 elem bar; bar.array = (char*) (malloc(sizeof(char) * 10)) ; bar.is_literal = 0; 

Luego en el lado del cliente:

 if(!bar.is_literal) { free(bar.array); } 

Simple como eso.

En los primeros días, cuando un 80386 podía tener un máximo de 8 megabytes de RAM, y las ideas de hacer objetos se explicaban en todos los demás artículos de revistas, no me gustaba copiar literales perfectamente buenos en objetos de cadena (asignar y liberar la copia interna). ) y le pregunté a Bjarne sobre eso, ya que una clase de cuerdas en bruto era uno de sus ejemplos de cosas extravagantes de C ++.

Dijo que no te preocupes por eso.

¿Esto tiene que ver con literales vs otros punteros de caracteres? Siempre puedes poseer la memoria. Creo que sí, a partir de sus ideas de buscar diferentes segmentos de memoria.

O es más general que la propiedad se puede o no otorgar, no hay manera de saberlo, y se necesita almacenar una bandera: “hey, este es un objeto de montón, pero alguien más lo está usando y lo cuidará ¿más tarde OK?”

Para el caso manejable en el que está “en el montón” o “no” (literales, globales, basados ​​en stack), puede tener la función free . Si proporcionó un conjunto coincidente de allocate / maybe-free , podría escribirse para saber qué memoria está bajo su control.