Domanda Problema con malloc e array di char*.

iitip

Utente Iron
13 Marzo 2023
1
1
0
2
Ultima modifica da un moderatore:
Ciao,
sono abbastanza novizio sul C. Sto cercando di fare un programma che cerca una stringa in una directory linux e restituisce dove l'ha trovata.
Il codice è il seguente (dict è una specie di hashmap, find_str una funzione che trova una stringa restituendo il file e la riga in cui si trova, mentre enum_dirs dà la lista delle sottocartelle di primo livello della cartella in input):

C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ftw.h>
#include "dict.c"

int f_cp(char *dir_name, char *s) {

    DIR *dir;
    struct dirent *ent;
    //struct stat st;
    if((dir = opendir(dir_name)) != NULL) { // && (ftwbuf -> level < tflag)) {
        while((ent = readdir(dir)) != NULL) {
            char filename[sizeof dir_name + strlen(ent -> d_name)];
            //lstat(ent -> d_name, &st);
            sprintf(filename, "%s%s", dir_name, ent -> d_name);
            find_str(filename, s);
        }
    } else {
        perror("Could not open dir.\n");
        return -1;
    }
    closedir(dir);

    return 0;
}

char **enum_dirs(char *path) {
    int dir_count = 0;
    struct dirent *dent;
    DIR *srcdir = opendir(path);
    char *dd = (char*) malloc(150 * sizeof(char*));
    char **res = (char**) malloc(300 * sizeof(char**));
    for(int a = 0; a < 300; ++a)
        res[a] = malloc(130 * sizeof(char));
    if(srcdir == NULL) {
        perror("Could not open dir.\n");
        return -1;
    }

    while((dent = readdir(srcdir)) != NULL) {
        struct stat st;
        if(strcmp(dent -> d_name, ".") == 0 || strcmp(dent -> d_name, "..") == 0) continue;
        if(fstatat(dirfd(srcdir), dent -> d_name, &st, 0) < 0) {
            perror(dent -> d_name);
            continue;
        }
        if(S_ISDIR(st.st_mode)) {
            sprintf(dd, "%s%s", path, dent -> d_name);
            strcpy(res[dir_count], dd);
            dir_count++;
        }
    }
    closedir(srcdir);
    free(dd);
    //free(res);
    return res;
}


int main(int argc, char *argv[]) {
    if(argc < 1) {
        perror("Not enough arguments. \n");
    }
    char dirname[200];
    char s[50];
    printf("What is the string you want to find?\n");
    scanf("%50[^\n]", s);
    printf("Where do you want to look for the string?\n");
    scanf("%s", dirname);
    //nftw(dirname, f_cp, 6, FTW_D);
  
    //////////////////////////
    char **dirs = (char**) malloc(500 * sizeof(char**));
    for(int b = 0; b < 500; ++b)
        dirs = malloc(130 * sizeof(char));
    dirs = enum_dirs(dirname);
    for(int k = 0; k < sizeof(dirs); k++) {
        f_cp(dirs[k], s);
        printf("DIRS[K]%s\n", dirs[k]);
    }
    //free(dirs);
    return 0;
}

Al momentp del richiamo della funzione enum_dirs, mi accorgo che su main non vengono elencate tutte le sottocartelle, ad esempio di /etc/, ma solo le prime 8, mentre su enum_dirs le trova tutte. è sbagliato il malloc dell'array di char** dirs? Come faccio a far stare tutte le sottocartelle?
 
Ultima modifica:
Utilizza il tasto per il codice quando lo incolli, così mantiene la spaziatura, questa volta lo aggiorno io. Manca anche il file dict.c e la funzione find_str. In sostanza tutte le malloc sono sbagliate e ci sono memory leak. Ti faccio un esempio: nella main dichiari char** dirs e allochi 500 puntatori a char* di 130 caratteri ciascuno ma immediatamente dopo assegni a dirs ciò che ritorna enum_dirs, avendo un leak della memoria appena allocata, enum_dirs poi farà più o meno la stessa cosa ma stavolta con 300 stringhe invece che 500. Non solo, l'ultimo ciclo for del main usa sizeof su dirs, questo non rappresenta il numero di cartelle ma la dimensione in memoria di un char**, cioè 4 o 8 in base alla target CPU di compilazione.

Avrai capito che il problema fondamentale è di design, non è così che si usa malloc: se le dimensioni sono note a priori allora usa un array statico, ma visto che vuoi enumerare cartelle ci potrebbe essere 1, 5000 come 100 mila cartelle, puoi seguire diversi approcci: usare una linked list, usare realloc per espandere di volta in volta (non consiglio) oppure fai prima un ciclo per contare quante sono, poi fai malloc, poi rifai il ciclo per leggere i nomi. La signature della funzione dovrebbe cambiare in:

C:
// Linked list (NodoLinkedList è la struct della tua implementazione di linked list)
NodoLinkedList* enum_dirs(const char* path)

// Altri, ritorna -1 per parametri non validi o fallimento di opendir, se è <= 0 dirs sarà NULL, se no è il numero di elementi contenuto in dirs.
int enum_dirs(const char* path, char** dirs)

In entrambi i casi la responsabilità di chiamare free nel modo giusto è lasciata al caller, quindi ti consiglio di fare una funzione a parte tipo free_linked_list o free_string_array in base all'approccio che scegli.

PS: la soluzione ottimale è comunque non tenere queste informazioni in memoria: nella maggior parte dei casi puoi sapere quello che ti interessa da dentro il loop di readdir, quindi magari potresti mettere dei filtri o una callback evitando quindi di tenere in memoria tutto il resto delle cartelle che al momento non ti interessano.