Foro de elhacker.net

Programación => Programación C/C++ => Mensaje iniciado por: AlbertoBSD en 29 Noviembre 2018, 18:47 pm



Título: problema de memoria con readdir (Solucionado)
Publicado por: AlbertoBSD en 29 Noviembre 2018, 18:47 pm
Muy buen dia.

Problema: La memoria utilizada por el programa aumenta y no disminuye

Solución: No es un problema, es la memoria que se genera por el paradigma de la recursividad, esto es: que al llamar a la misma función de forma recursiva, el Stack Frame de la memoria que utiliza cada una de las llamadas a la función se acumule.

Intente aplicar una solucion de forma Iterativa, guardando el Path de los directorios que faltan por leer en una Queue, pero utiliza mas memoria  que la solucion recursiva.

readdir devuelve un apuntador de la estructura (struct dirent)
Mas info: https://linux.die.net/man/3/readdir

El cual creo que es el causante de este POST.

Versión Corta

¿Quien controla el Apuntador entregado por readdir?

Es memoria estatica y no se debe de liberar

Código
  1. directorio_archivo_temporal = readdir(directorio)

Al momento de tratar de liberar dicho apuntador, el programa se cuelga...

Código
  1. while(directorio_archivo_temporal = readdir(directorio) ) {
  2. //etc..
  3. free(directorio_archivo_temporal);
  4. }
  5.  

El problema radica en que la memoria del programa solo aumenta y no disminuye.

Codigo de la version corta funcionando
Código
  1. #include<sys/stat.h>
  2. #include<sys/types.h>
  3. #include<dirent.h>
  4. #include<stdio.h>
  5. #include<stdlib.h>
  6. #include<string.h>
  7.  
  8. int is_regular_file(const char *path);
  9.  
  10. int procesar_directorio(char *path);
  11.  
  12. int main(int argc,char **argv) {
  13. switch(argc){
  14. case 2: //Se espera directorio
  15. procesar_directorio(argv[1]);
  16. break;
  17. default:
  18. printf("Numero de argumentos no esperado\n");
  19. break;
  20. }
  21.  
  22. }
  23.  
  24. int is_regular_file(const char *path) {
  25. struct stat path_stat;
  26. stat(path, &path_stat);
  27. return S_ISREG(path_stat.st_mode);
  28. }
  29.  
  30. int procesar_directorio(char *path) {
  31. DIR *directorio;
  32. struct dirent *directorio_archivo_temporal;
  33. char *path_copy;
  34. int len_path;
  35. char *path_temporal;
  36. int len_path_temporal;
  37. directorio = opendir(path);
  38. if(directorio != NULL) {
  39. len_path = strlen(path);
  40. path_copy = malloc(len_path+2);
  41. memcpy(path_copy,path,len_path);
  42. if(path_copy[len_path - 1] == '\\' ){
  43. path_copy[len_path] = '\0';
  44. }
  45. else {
  46. path_copy[len_path] = '\\';
  47. path_copy[len_path+1] = '\0';
  48. len_path++;
  49. }
  50. while(directorio_archivo_temporal = readdir(directorio) ) {
  51. len_path_temporal = strlen(directorio_archivo_temporal->d_name);
  52. path_temporal = malloc(len_path+len_path_temporal+1);
  53. path_temporal[len_path+len_path_temporal] = '\0';
  54. sprintf(path_temporal,"%s%s",path_copy,directorio_archivo_temporal->d_name);
  55. if(is_regular_file(path_temporal)) {
  56. printf("%s\n",path_temporal);
  57. }
  58. else { //Directorio?
  59. if( len_path_temporal > 2 ) { //not .. or .
  60. procesar_directorio(path_temporal); //recursivamente
  61. }
  62. }
  63. free(path_temporal);
  64. }
  65. free(path_copy);
  66. closedir(directorio);
  67. }
  68. }
  69.  








Versión extensa

Aquí esta toda la Letanía

Estoy realizando un programa que lea de forma Recursiva un directorio dado y liste los archivos en pantalla. Nada del otro mundo. Asi mismo estoy haciendo que el formato de salida sea el formato tipo UNIX /bla/bla/bla en lugar del formato de windows C:\lalala\lalalala.txt

Esto con el objetivo de virtualizar el path de subdirectorios específicos para otro programa pero eso ya es harina de otro costal.

El problema es que cuando listo Cualquier carpeta con muchos subdirectorios como la unidad C:\  el programa en empieza usando lo clasico unos 400 KB y termina utilizando 800 KB o mas dependiendo de la carpeta que este listada.

El dia Ayer publique un post parecido:
Problema de memoria en miniservidor usando Winsock (Solucionado) (https://foro.elhacker.net/programacion_cc/problema_de_memoria_en_miniservidor_usando_winsock_solucionado-t490142.0.html)

En el cual se estaba cerrando el socket de forma incorrecta.

En este caso creo que es problema del apuntador devuelto por readdir.

Código del programa funcionando:

Código
  1. #include<sys/stat.h>
  2. #include<sys/types.h>
  3. #include<dirent.h>
  4. #include<stdio.h>
  5. #include<stdlib.h>
  6. #include<string.h>
  7.  
  8. int fsize(const char *filename);
  9. int is_regular_file(const char *path);
  10.  
  11. int procesar_directorio(char *path,char *virtual_path);
  12.  
  13. int main(int argc,char **argv) {
  14. switch(argc){
  15. case 2: //Se espera directorio
  16. procesar_directorio(argv[1],"/");
  17. break;
  18. case 3: //Se espera archivo + nombre de directorio de destino.
  19. break;
  20. default:
  21. printf("Numero de argumentos no esperado\n");
  22. break;
  23. }
  24.  
  25. }
  26.  
  27. int is_regular_file(const char *path) {
  28. struct stat path_stat;
  29. stat(path, &path_stat);
  30. return S_ISREG(path_stat.st_mode);
  31. }
  32.  
  33. int fsize(const char *filename) {
  34.    struct stat st;
  35.    if (stat(filename, &st) == 0)
  36.        return st.st_size;
  37.    return -1;
  38. }
  39.  
  40. int procesar_directorio(char *path,char *virtual_path) {
  41. DIR *directorio;
  42. struct dirent *directorio_archivo_temporal,*result;
  43.  
  44. char *path_copy;
  45. char *virtual_path_copy;
  46. int len_path,len_virtual_path;
  47.  
  48. char *path_temporal;
  49. char *virtual_path_temporal;
  50. int len_path_temporal;
  51. int len_virtual_path_temporal;
  52.  
  53.  
  54. directorio = opendir(path);
  55. if(directorio != NULL) {
  56.  
  57. len_path = strlen(path);
  58. len_virtual_path = strlen(virtual_path);
  59.  
  60. virtual_path_copy = malloc(len_virtual_path+2);
  61. path_copy = malloc(len_path+2);
  62. memcpy(path_copy,path,len_path);
  63. memcpy(virtual_path_copy,virtual_path,len_virtual_path);
  64.  
  65. if(path_copy[len_path - 1] == '\\' ){
  66. path_copy[len_path] = '\0';
  67. }
  68. else {
  69. path_copy[len_path] = '\\';
  70. path_copy[len_path+1] = '\0';
  71. len_path++;
  72. }
  73. if(virtual_path_copy[len_virtual_path - 1] == '/' ){
  74. virtual_path_copy[len_virtual_path] = '\0';
  75. }
  76. else {
  77. virtual_path_copy[len_virtual_path] = '/';
  78. virtual_path_copy[len_virtual_path+1] = '\0';
  79. len_virtual_path++;
  80. }
  81. /*
  82. directorio_archivo_temporal = calloc(1,sizeof(struct dirent));
  83. result == NULL;
  84. */
  85. //while(readdir_r(directorio,directorio_archivo_temporal,&result) == 0 && result != NULL){
  86. while(directorio_archivo_temporal = readdir(directorio) ) {
  87.  
  88. len_path_temporal = strlen(directorio_archivo_temporal->d_name);
  89. //printf("readdir point to %p\n",directorio_archivo_temporal);
  90. //printf("Len %i : %s\n",len_path_temporal,directorio_archivo_temporal->d_name);
  91.  
  92. path_temporal = malloc(len_path+len_path_temporal+1);
  93. virtual_path_temporal = malloc(len_virtual_path + len_path_temporal+1);
  94.  
  95. path_temporal[len_path+len_path_temporal] = '\0';
  96. virtual_path_temporal[len_virtual_path+len_path_temporal] = '\0';
  97.  
  98. sprintf(path_temporal,"%s%s",path_copy,directorio_archivo_temporal->d_name);
  99. sprintf(virtual_path_temporal,"%s%s",virtual_path_copy,directorio_archivo_temporal->d_name);
  100.  
  101. if(is_regular_file(path_temporal)) {
  102. //printf("Archivo :\"%s\"\n",path_temporal);
  103. printf("%s\n",virtual_path_temporal);
  104. }
  105. else { //Directorio?
  106. if( len_path_temporal > 2 ) { //not .. or .
  107. procesar_directorio(path_temporal,virtual_path_temporal); //recursivamente
  108. }
  109. }
  110. free(path_temporal);
  111. free(virtual_path_temporal);
  112. }
  113. //free(directorio_archivo_temporal);
  114.  
  115.  
  116.  
  117. free(path_copy);
  118. free(virtual_path_copy);
  119. closedir(directorio);
  120. }
  121. else {
  122. printf("El directorio \"%s\" no es un directorio valido\n",path);
  123. }
  124. }
  125.  

Funciona bien, recibe parametros de entrada, ejemplo

Ejemplo

Código:
C:\>generar_contenido.exe contenido
/index.html

Le agregue un getchar al final del archivo para poder ver con cuanta memoria termina el programa. Para carpetas sin tanto subdirecorio no hay problema, el detalle es cuando se trata de todo un disco...

Imagen:
(http://i.imgur.com/Wmyb6A0l.png) (https://imgur.com/Wmyb6A0)

Ya he validado que todos los apuntadores que yo genero de forma dinámica son liberados en su totalidad, esto mediante re implementaciones de malloc,calloc,realloc y free como describo en el siguiente post: ¿He liberado todos los apuntadores? (https://foro.elhacker.net/programacion_cc/iquesthe_liberado_todos_los_apuntadores-t489858.0.html)

Imagen:
(http://i.imgur.com/VUeMenKl.png) (https://imgur.com/VUeMenK)

Con lo cual considero que el problema es el apuntador devuelto por readdir.

Intente cambiar el siguiente pedazo de codigo

Código
  1. while(directorio_archivo_temporal = readdir(directorio) ) {
  2. //etc...
  3. }
  4.  

por

Código
  1. while(directorio_archivo_temporal = readdir(directorio) ) {
  2. //etc..
  3. free(directorio_archivo_temporal);
  4. }
  5.  

Pero el programa se queda colgado por un rato y finaliza, seguramente por tratar de escribir en memoria que no esta asignada...

Intente implementarlo con readdir_r utilizando mi propio apuntador previamente asignado..

Código
  1. while(readdir_r(directorio,directorio_archivo_temporal,&result) == 0 && result != NULL){
  2.  

Pero por alguna razon MinGW no lo tiene implementado, y segun he visto ya esta deprecated, entonces ¿Cual puede ser la solución?

Saludos!



Pordon por el Post tan largo, aqui esta su patata

(https://i.imgur.com/zhOapf6l.jpg)


Título: Re: problema de memoria con readdir
Publicado por: CalgaryCorpus en 29 Noviembre 2018, 21:35 pm
Segun la documentacion, readdir usa memoria estatica, de modo que no corresponde usar free() sobre lo que se retorna.



Título: Re: problema de memoria con readdir
Publicado por: CalgaryCorpus en 29 Noviembre 2018, 22:10 pm
Sugiero reemplazar esto de pedir y liberar memoria en cada vuelta del while por solo pedir memoria cuando es necesario pedirla, y no liberarla en cada vuelta, solo liberarla al final del while.

Algo asi como:

Código
  1. buffer = NULL;
  2. memoria_actual = 0;
  3. while( ... ) {
  4.   memoria_requerida = ...  // calcular cuanto se requiere
  5.   if( memoria_requerida > memoria_actual ) {
  6.       // liberar la memoria anterior, pedir tanta como memoria_requerida, o hacer realloc
  7.       ..
  8.       // y actualizar cual es el tamano actual
  9.       memoria_actual = memoria_requerida;
  10.   }
  11.   // copiar hacia buffer
  12.  
  13.   // la logica actual
  14.   if( is_regular_file ....) {
  15.   }
  16.   //etc
  17.  
  18.   // no liberar la memoria en cada vuelta
  19. }
  20.  
  21. free(buffer);  // liberar al final.
  22.  



Título: Re: problema de memoria con readdir (Solucionado)
Publicado por: AlbertoBSD en 30 Noviembre 2018, 04:57 am
Segun la documentacion, readdir usa memoria estatica, de modo que no corresponde usar free() sobre lo que se retorna.

Excelente, segun veo usa parte de la misma estrucutra original del directorio pasado como parametro. o algo asi alcance a ver.

Sugiero reemplazar esto de pedir y liberar memoria en cada vuelta del while por solo pedir memoria cuando es necesario pedirla, y no liberarla en cada vuelta, solo liberarla al final del while.

Voy aplicarlo gracias!, ya que si termina siendo mas eficiente, por que evitaria tantas llamadas a malloc como sea posible.

Creo que ya encontre la solucion, según veo no es problema de memoria por liberar, es problema de la implementación recursiva de la función.

La solución recursiva es la que menos memoria utiliza.

Saludos!