Duda strings, punteros y errores "string alloc: out of memory"

Started by Arcontus, January 19, 2018, 12:26:54 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

SplinterGU

con malloc puedes hacer cualquier puntero salvo los de strings... si usas punteros a string que alocas con malloc, los puedes usar, pero nunca se liberan de la memoria, con lo que la cantidad de strings en memoria crecen y crecen y crecen hasta agotar toda la memoria.

estoy intentando ver si le encuentro alguna solucion a esto...
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

SplinterGU

tengo una solucion al tema de los punteros a strings, no encontra otra opcion que agregar 3 nuevas funciones, 2 para el tema de allocar una string (que se usa solo para string * que no sean string_pointer = &string) y otra para liberar la string cuando ya no se usa. Y agregue una mas para obtener un puntero directo al buffer de la string en memoria (para poder usarla en un memmove o memcopy a un array de char o bytes, o a disco o lo que sea, aunque esta funcion debe ser usada con cuidado porque permite modificar las strings base)

las tengo hace 2 dias, pero las estuve probando a fondo y pensando a ver si podia evitarlas, pero no es posible evitarlas.
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

Arcontus

Esto suenga genial!

cuando encuentres un rato molaría que nos ilustraras sobre esas nuevas funciones. :)

Un saludo!
5Leaps, el primer juego comercial desarrollado para BennuGD. http://www.5leaps.com

SplinterGU

Download Lastest BennuGD Release: http://www.bennugd.org/node/2

Drumpi

Quote from: SplinterGU on January 20, 2018, 07:37:03 AM
Quote from: Drumpi on January 20, 2018, 01:31:13 AM
Yo en su día tuve millones de problemas con los array de strings.
No conocía exactamente el funcionamiento interno de las strings, suponía que eran un array de memoria dinámica que se agrandaba y encogía en función del número de caracteres, y que por la alineación de memoria de los propios array, provocaba que los espacios de memoria se empezaran a pisar unos a otros.

Así que me definí un tipo "string_node" tal como:
type string_node
    string cadena;
    string_node pointer sig
end

Y luego me creé la tipica lista enlazada de nodos, con funciones para añadir, eliminar nodos, buscar, recorrer, vaciar, etc, y la llamé "string_class". Sí, básicamente emulaba la clase String de Java que estaba estudiando en aquel momento.
Se resolvieron TODOS los problemas. Splinter insiste que eso debería fallar en algún momento, pero debe andar ya por los 7 años sin excepciones de ningún tipo.

El código lo subí al foro. http://forum.bennugd.org/index.php/topic,2208
Creo que tuve que arreglar un par de cosillas desde entonces, pero más fallos míos que "malas prácticas" :D
Mi principal uso: crear un fichero de texto con frases y cargarlas todas en memoria, ideal para almacenar diálogos o los textos en el idioma que se elija para el programa.

drumpi, ahi no estas usando punteros a strings... igual creo que tampoco es correcto, aunque depende de otras cosas...

Ya lo sé, esa era la idea: como no podía tener un array de strings ni usar alloc para crear un array dinámico para almacenarlas, hice lo que se debe hacer: una lista enlazada con variables sencillas. Al ser nodos enlazados por punteros, es el sistema el que se encarga de reservarle memoria, y mover los bloques si es necesario más espacio para alguna de ellos.
Hala, como con 1001 procesos sólo va a 9 FPS, vamos a meterle 32 veces más, a ver si revienta.
(Drumpi epic moment)

SplinterGU

Quote from: Drumpi on January 25, 2018, 12:23:07 AM
Quote from: SplinterGU on January 20, 2018, 07:37:03 AM
Quote from: Drumpi on January 20, 2018, 01:31:13 AM
Yo en su día tuve millones de problemas con los array de strings.
No conocía exactamente el funcionamiento interno de las strings, suponía que eran un array de memoria dinámica que se agrandaba y encogía en función del número de caracteres, y que por la alineación de memoria de los propios array, provocaba que los espacios de memoria se empezaran a pisar unos a otros.

Así que me definí un tipo "string_node" tal como:
type string_node
    string cadena;
    string_node pointer sig
end

Y luego me creé la tipica lista enlazada de nodos, con funciones para añadir, eliminar nodos, buscar, recorrer, vaciar, etc, y la llamé "string_class". Sí, básicamente emulaba la clase String de Java que estaba estudiando en aquel momento.
Se resolvieron TODOS los problemas. Splinter insiste que eso debería fallar en algún momento, pero debe andar ya por los 7 años sin excepciones de ningún tipo.

El código lo subí al foro. http://forum.bennugd.org/index.php/topic,2208
Creo que tuve que arreglar un par de cosillas desde entonces, pero más fallos míos que "malas prácticas" :D
Mi principal uso: crear un fichero de texto con frases y cargarlas todas en memoria, ideal para almacenar diálogos o los textos en el idioma que se elija para el programa.

drumpi, ahi no estas usando punteros a strings... igual creo que tampoco es correcto, aunque depende de otras cosas...

Ya lo sé, esa era la idea: como no podía tener un array de strings ni usar alloc para crear un array dinámico para almacenarlas, hice lo que se debe hacer: una lista enlazada con variables sencillas. Al ser nodos enlazados por punteros, es el sistema el que se encarga de reservarle memoria, y mover los bloques si es necesario más espacio para alguna de ellos.

por favor, pasame un miniprograma de ejemplo usando esa estructura, asi lo pruebo, pero temo que es incorrecto.
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

SplinterGU

drumpi, imagino que tenes algo como esto:


import "mod_mem";
import "mod_say";
import "mod_string";

type string_node
    string cadena;
    string_node pointer sig;
end

global
    string_node * nodes = NULL;
end

function add_node( string s )
private
    string_node pointer n;
begin
    n = calloc(1,sizeof(string_node));
    n.cadena = s;
    n.sig = nodes;
    nodes = n;
end

function a()
private
    s = "lala";
    string_node pointer p, pointer p1;
begin
    say("# inicial [ a() entry ]");
    string_dump();

    s = "*" + s + "*"; // evitamos la cadena estatica
    string_dump();

    say("## agrego primer nodo");

    add_node(s);
    string_dump();

    say("## agrego segundo nodo");
    add_node(s + "_1");
    string_dump();

    say("## agrego tercer nodo");
    add_node(s + "_2");
    string_dump();

    say("#### free");
    p = nodes;
    while ( p )
        nodes = p.sig;
        free( p );
        p = nodes;
    end
    string_dump();

    say( "# a() exit");
end

begin
    a();
    string_dump();
end


vas a ver que hay 1 funcion nueva string_dump(), esta funcion lo que hace es mostrar las strings en uso
por ejemplo, [STRING]    6 [   1] STATIC: {lala}
id [ cantidad de usos ] {string}

actualmente ese codigo produce este resultado:

Quote
# inicial [ a() entry ]
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]    6 [   1] STATIC: {lala}
[STRING]   32 [   1]: {string_struct}
[STRING] ---- Dumping Used=2 End ----
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]   32 [   1]: {string_struct}
[STRING]   34 [   1]: {*lala*}
[STRING] ---- Dumping Used=2 End ----
## agrego primer nodo
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]   32 [   1]: {string_struct}
[STRING]   34 [   2]: {*lala*}
[STRING] ---- Dumping Used=2 End ----
## agrego segundo nodo
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]   32 [   1]: {string_struct}
[STRING]   34 [   2]: {*lala*}
[STRING]   35 [   1]: {*lala*_1}
[STRING] ---- Dumping Used=3 End ----
## agrego tercer nodo
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]   32 [   1]: {string_struct}
[STRING]   34 [   2]: {*lala*}
[STRING]   35 [   1]: {*lala*_1}
[STRING]   36 [   1]: {*lala*_2}
[STRING] ---- Dumping Used=4 End ----
#### free
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]   32 [   1]: {string_struct}
[STRING]   34 [   2]: {*lala*}
[STRING]   35 [   1]: {*lala*_1}
[STRING]   36 [   1]: {*lala*_2}
[STRING] ---- Dumping Used=4 End ----
# a() exit
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]   32 [   1]: {string_struct}
[STRING]   34 [   1]: {*lala*}
[STRING]   35 [   1]: {*lala*_1}
[STRING]   36 [   1]: {*lala*_2}
[STRING] ---- Dumping Used=4 End ----

aca veremos el problema...

cuando a() termina, todas las strings usadas en su codigo deberian tener uso 0, ya que no son retornadas, pero al usar punteros, estas no se liberan... que si prestas atencion si libera 1 uso de "*lala*".

pero en el ultimo dump (en el main) se ven que quedaron strings sin liberar....

el codigo correcto con las nuevas funcionens seria:


import "mod_mem";
import "mod_say";
import "mod_string";

type string_node
    string * cadena;
    string_node pointer sig;
end

global
    string_node * nodes = NULL;
end

function add_node( string s )
private
    string_node pointer n;
begin
    n = calloc(1,sizeof(string_node));
    n.cadena = string_alloc(s);
    n.sig = nodes;
    nodes = n;
end

function a()
private
    s = "lala";
    string_node pointer p;
begin
    say("# inicial [ a() entry ]");
    string_dump();

    s = "*" + s + "*"; // evitamos la cadena estatica
    string_dump();

    say("## agrego primer nodo");

    add_node(s);
    string_dump();

    say("## agrego segundo nodo");
    add_node(s + "_1");
    string_dump();

    say("## agrego tercer nodo");
    add_node(s + "_2");
    string_dump();

    say("#### free");
    p = nodes;
    while ( p )
        nodes = p.sig;
        string_release(&p.cadena);
        free( p );
        p = nodes;
    end
    string_dump();

    say( "# a() exit");
end

begin
    a();
    string_dump();
end


salida

Quote
# inicial [ a() entry ]
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]    6 [   1] STATIC: {lala}
[STRING]   32 [   1]: {string_struct_new}
[STRING] ---- Dumping Used=2 End ----
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]   32 [   1]: {string_struct_new}
[STRING]   34 [   1]: {*lala*}
[STRING] ---- Dumping Used=2 End ----
## agrego primer nodo
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]    0 [   1] STATIC: {}
[STRING]   32 [   1]: {string_struct_new}
[STRING]   34 [   2]: {*lala*}
[STRING] ---- Dumping Used=3 End ----
## agrego segundo nodo
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]    0 [   2] STATIC: {}
[STRING]   32 [   1]: {string_struct_new}
[STRING]   34 [   2]: {*lala*}
[STRING]   35 [   1]: {*lala*_1}
[STRING] ---- Dumping Used=4 End ----
## agrego tercer nodo
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]    0 [   3] STATIC: {}
[STRING]   32 [   1]: {string_struct_new}
[STRING]   34 [   2]: {*lala*}
[STRING]   35 [   1]: {*lala*_1}
[STRING]   36 [   1]: {*lala*_2}
[STRING] ---- Dumping Used=5 End ----
#### free
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]    0 [   3] STATIC: {}
[STRING]   32 [   1]: {string_struct_new}
[STRING]   34 [   1]: {*lala*}
[STRING] ---- Dumping Used=3 End ----
# a() exit
[STRING] ---- Dumping MaxID=1056 strings ----
[STRING]    0 [   3] STATIC: {}
[STRING]   32 [   1]: {string_struct_new}
[STRING] ---- Dumping Used=2 End ----

como veras, al salir de a() todas las strings que no se retornan son liberadas...

que pasa actualmente con el uso de punteros a strings? que se generan y generan strings que no se liberan nunca de memoria, con lo que incrementa la memoria e incrementa e incrementa, hasta dar un "no hay mas memoria para allocar" o algo parecido.

los crash con respecto a strings *, no se debe a este problema, sino, seguramente a un uso desprolijo de los punteros, quizas no se setean a null luego de liberarlos y cuando se vuelven a acceder se piensa que hay valor, o usarlos como *pointer cuando valen NULL.

las nuevas funciones son

string * string_alloc()              - retorna un puntero a string conteniendo una string vacia
string * string_alloc(string)      - retorna un puntero a string asignando un valor a la string
string_release(string **)          - libera la string usada (contador de usos), libera de memoria el puntero a string, y setea el puntero a NULL (para evitar el mal uso y el olvido de setear el puntero a NULL se exige que el parametro sea string **)
string_dump()                        - funcion de debug, muestra las strings en uso, su id, contador de uso y string
any * string_buffer(string)                - retorna el char * directo al contenido de la string (esta funcion debe ser usada con cuidado, porque permite modificar las cadenas que son fijas del sistema/dcb)

por otro lado, la forma correcta de meter actualmente strings a punteros en estructuras es usando arrays de chars, o si no son a estructuras usar <string *> = &<string>

uso correcto actual


import "mod_say";
import "mod_mem";
import "mod_string";


function char * string2buffer(string in, char *buf)
private
    int sz, i;
begin
    sz = strlen(in);
    if (!buf) buf = mem_alloc(sz+1); end
    for( i = 0; i < sz ; i++ ) buf[i] = in[i]; end
    buf[sz] = 0;
    return buf;
end

#define buffer2string(buf) ((string)(buf))

function split_string(string input, char **part1, char **part2)
private
    int i;
begin
    i = find(input," ");
    *part1 = string2buffer( substr( input, 0, i ), null );
    *part2 = string2buffer( substr( input, i + 1), null );
end

private
    char *pchar1, *pchar2;
    string string1 = "hello world", string2;
    char array1[128], array2[128];
begin

    say("--------------------------");
    say("- direct string <-> char[]");
    say("--------------------------");
    array1  = string1;                              say("array1  (from string)                              > '" + array1 + "'");
    string2 = array1;                               say("string2 (from array)                               > '" + string2 + "'");
    say("");

    say("------------------------");
    say("- safe string <-> char *");
    say("------------------------");
    pchar1  = string2buffer(string1,null);          say("pchar1  (from string2buffer with allocated return) > '" + (string) pchar1 + "' (" + pchar1 + ")");
    string2 = (string) pchar1;                      say("string2 (from cast char *)                         > '" + string2 + "'");
    string2 = buffer2string(pchar1); free(pchar1);  say("string2 (from buffer2string) (and free)            > '" + string2 + "'");
    say("");

    say("--------------------------------------------------------");
    say("- string <-> char[] with string2buffer and buffer2string");
    say("--------------------------------------------------------");
    string2buffer(string1,&array2);                 say("array2  (from string2buffer using array address)   > '" + array2 + "'");
    string2 = (string) &array2;                     say("string2 (from cast array address)                  > '" + string2 + "'");
    string2 = buffer2string(&array2);               say("string2 (from buffer2string)                       > '" + string2 + "'");
    say("");


    say("-----------------------------------");
    say("sample function safe string returns");
    say("-----------------------------------");
    split_string(string1, &pchar1, &pchar2 );

    say( "part1 > " + buffer2string(pchar1) );
    say( "part2 > " + buffer2string(pchar2) );

    free( pchar1 );
    free( pchar2 );

end




import "mod_say";
import "mod_mem";
import "mod_string";


function split_string(string input, string *parts)
private
    int i;
begin
    i = find(input," ");
    parts[0] = substr( input, 0, i );
    parts[1] = substr( input, i + 1 );
end


function a()
private
    string string1 = "hello world";
    string parts[1];
begin
    string_dump();

    say("-----------------------------------");
    say("sample function safe string returns");
    say("-----------------------------------");
    split_string( string1, &parts );

    say( "part1 > " + parts[0] );
    say( "part2 > " + parts[1] );

    string_dump();

end

begin
    a();
    string_dump();
end
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

SplinterGU

tengo mas ejemplos, pero tengo que limpiarlos un poquito.
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

Drumpi

Es un poco tarde para leer el código, pero aunque se parece a lo que hago, no es exactamente eso. En el enlace que puse tienes la "librería" (tiene un par de fallos en la ordenación de strings y algún caso que no me había dado cuenta de puntero a null, tengo que subir la versión corregida) y un código de ejemplo.
La "librería" es algo más complicada que eso que has escrito, porque tiene constructores, y destructores (destructores que vacían la string antes de hacer free, porque ya comentaste en su día algo similar de que no se liberaba la memoria), así como funciones de concatenación, filtrado, búsqueda, ordenación, etc, etc, que me han sido tremendamente útiles (hasta el punto de que reutilicé el código para almacenar una lista de carpetas y ficheros para crear navegadores de directorios).

Además, nunca uso CALLOC, siempre tiro de ALLOC, me parece más adecuado a lo que estoy acostumbrado a usar.

Respecto a los arrays de strings, el problema más común que he tenido siempre es cuando reservo espacio para varias cadenas, y relleno los datos para la primera, para la segunda, para la tercera... y en un momento dado, modifico la segunda, haciéndola más larga, y se me produce un segmentation fault, que yo he achacado siempre a que la memoria de la segunda cadena me ha pisado los datos de la tercera, y en ocasiones (raras, pero me ha pasado) memorias de variables de algún proceso, como la coordenada X, o una variable privada.
No me pidas ejemplos, porque no los tengo. Los corregí en su momento y nunca más se supo, y aislarlo me hubiera llevado horas, para evitar que al eliminar procesos, variables y demás, el error dejase de reproducirse por cambiar los espacios de memoria.
Hala, como con 1001 procesos sólo va a 9 FPS, vamos a meterle 32 veces más, a ver si revienta.
(Drumpi epic moment)

SplinterGU

por el contrario, calloc es mas adecuado, porque aloca con la memoria limpia, lo que equivale a 0, null, y en el caso de strings, equivale a la string 0, que es "" y es del sistema, esa no importa que tenga contador de usos a cero, porque esa string nunca se descarga de memoria... y cuando le asignes un valor, el core no intentara descargar basura (u otra string) de memoria, el usar malloc en lugar de calloc, puede provocar (aunque no siempre) crashes/petes...

no se como descargas la string desde bennugd, porque no hay funcion para descargar strings a nivel de usuario... a menos que hagas magia... quizas una forma de que no ocupe memoria, seria hacer un *puntero_string = "" antes de hacer el free del puntero. Si bien incrementa el contador de usos de "" (string fija del sistema) puede hacer que se descargue el contador de usos de la string que apuntaba el puntero, y la librere de memoria cuando ya no la use... tendria que probarlo.

cuando lo tengas el codigo corregido, avisame y lo pruebo.
Download Lastest BennuGD Release: http://www.bennugd.org/node/2

Drumpi

Hombre, free no hago, pero asignarle la cadena "" sí :D

¡Ah! ¿Estabas esperando la versión corregida? perdona ^^U Voy a buscarla y te la subo en cuanto pueda, que me has pillado de celebraciones cumpleañeras :P
Hala, como con 1001 procesos sólo va a 9 FPS, vamos a meterle 32 veces más, a ver si revienta.
(Drumpi epic moment)