Quando usare malloc e gratis?

2

Valgrind non riporta una perdita di memoria durante il mio utilizzo effettivo, solo durante il mio test con script che ho scriptato con uno script di shell per testare la mia shell . Ho scoperto che non dovevo usare malloc ogni volta che l'ho fatto. Ad esempio, strdup potrebbe farlo per me. Ora mi chiedo se sia possibile dimostrare formalmente che ho davvero bisogno di malloc dove lo uso o posso fare un'analisi e provare o confutare che malloc sia effettivamente necessario?

Se riesco a riscrivere il programma in modo che non usi malloc, sarei felice. Sarei ancora più felice se riuscissi a dimostrare per alcuni casi che non ho bisogno di malloc, dal momento che ci sono stati casi in cui ho dovuto solo free() e la dimensione di malloc è stata eseguita automaticamente. Un messaggio di errore che appare solo durante il test è il seguente.

. 
==29846== Memcheck, a memory error detector
==29846== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==29846== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==29846== Command: ./shell
==29846== 
'PATH' is set to /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin.
stdin is a file or a pipe
==29846== 
==29846== HEAP SUMMARY:
==29846==     in use at exit: 83,052 bytes in 171 blocks
==29846==   total heap usage: 240 allocs, 69 frees, 101,754 bytes allocated
==29846== 
==29846== 12 bytes in 1 blocks are definitely lost in loss record 5 of 93
==29846==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29846==    by 0x50FCA59: strdup (strdup.c:42)
==29846==    by 0x4E57624: readline (in /usr/lib/x86_64-linux-gnu/libedit.so.2.0.53)
==29846==    by 0x40193B: main (main.c:599)
==29846== 
==29846== LEAK SUMMARY:
==29846==    definitely lost: 12 bytes in 1 blocks
==29846==    indirectly lost: 0 bytes in 0 blocks
==29846==      possibly lost: 0 bytes in 0 blocks
==29846==    still reachable: 83,040 bytes in 170 blocks
==29846==         suppressed: 0 bytes in 0 blocks
==29846== Reachable blocks (those to which a pointer was found) are not shown.
==29846== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==29846== 
==29846== For counts of detected and suppressed errors, rerun with: -v
==29846== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Il test che ho copiato è:

#!/bin/sh
echo "-- Testing our implementation of OpenShell --"
echo ""
echo "- If you have any problem in passing a test read the corresponding"
echo "- source file to understand what the test is checking"
echo ""
printf "********************* PRESS ENTER TO RUN TESTS  ... "
read _

# Key pressed, do something
printf "********************* TEST WILDCARDS \n***** Press any key to listing all files in current directory... "
read _
valgrind --leak-check=full --leak-kinds=all./shell << EOF
ls -al *.*
EOF
printf "********************* TEST ALGORITHMS ... "
read _
echo "top -b -n1|head -8|tail -1" | ./shell

printf "********************* TEST ALGORITHMS Part II.  ... "
read _
valgrind --leak-check=full --leak-kinds=all ./shell << EOF
who|awk '{print \ ; print \}'|sort -n|wc -l
EOF

printf "********************* TEST CHECKENV.  ... "
read _
valgrind --leak-check=full --leak-kinds=all ./shell << EOF
checkenv
EOF
printf "********************* TEST DONE. YOU SHOULD SEE OUTPUT FROM TEST ABOVE ... "
read _

Il mio codice offensivo è qui:

 while (1) {
        buf = "> ";
        if (!isatty(fileno(stdin))) {
            printf("stdin is a file or a pipe\n");
            if (buf)
                command(readline(buf));
            exit(0);
        }
        cwd = malloc(sizeof(char *) * 100);
        if (cwd != NULL && getcwd(cwd, 99) == cwd) {
            printf("%s: ", cwd);
            char *input = readline(buf);
            add_history(input);
            command(input);
            free(input);
            free(cwd);

        } else {
            printf("%s: ", getenv("USER"));
            char *input = readline(buf);
            add_history(input);
            if (input) {
                command(input);
                free(input);
            }

        }
    }
    
posta Niklas Rosencrantz 02.05.2016 - 06:17
fonte

1 risposta

6

Non ho intenzione di eseguire il debug del tuo codice, non c'è abbastanza contesto per farlo comunque, ma ti mostrerò un idioma che probabilmente troverai più facile da usare correttamente. Come bonus, sarà anche più veloce.

Dai un'occhiata al tuo corpo in loop. Assegni memoria durante ciascuna iterazione e free in determinate circostanze, a seconda del flusso di controllo generale. Ciò pone un carico mentale molto elevato su chiunque (incluso te stesso) che legge il codice e sta tentando di verificare che ogni malloc sia associato a un free corrispondente su ogni possibile percorso di esecuzione.

Che cosa succede se invece hai preso l'allocazione della memoria e la deallocazione fuori dal ciclo?

const size_t maxcwd = PATH_MAX;
char * cwd = malloc(maxcwd);
if (cwd == NULL)
  {
    fprintf(stderr, "error: cannot start shell: out of memory\n");
    return EXIT_FAILURE;
  }
while (true)
  {
    if (getcwd(cwd, maxcwd))
      {
        // Good, use it…
      }
    else
      {
        // Bad, do something else…
      }
  }
free(cwd);

Nota come c'è solo un malloc e un singolo free ed è abbastanza facile vedere che si accoppiano correttamente. Il codice sarà anche più veloce perché ci sono molte meno allocazioni e deallocazioni eseguite.

Tuttavia, il codice non è l'ideale. Perché allocare un vasto array in anticipo che potrebbe non essere mai necessario e perché fallire se è ancora troppo piccolo? Sarebbe meglio ridimensionare dinamicamente la matrice, se necessario.

size_t cwdsize = 0;
char * cwd = NULL;
while (true)
  {
    while ((cwd == NULL) || (getcwd(cwd, cwdsize) == NULL))
      {
        size_t newsize = (cwdsize > 0) ? (cwdsize << 1) : 128;
        char * temp = realloc(cwd, newsize);
        if (temp == NULL)
          {
            fprintf(stderr, "error: out of memory\n");
            break;
          }
        cwdsize = newsize;
        cwd = temp;
      }
    // cwd is now always good to use…
  }
free(cwd);

Ovviamente, il codice è un po 'prolisso e potresti decidere di includerlo in una funzione. Questo è anche lo schema utilizzato dalla funzione POSIX getline che trovo molto elegante.

È un peccato che GNU readline non supporti lo stesso idioma. Alloca sempre nuova memoria e devi ricordarti di free dopo ogni chiamata. Ancora peggio, non c'è modo di segnalare errori nell'allocazione della memoria.

bool keep_going = true;
while (keep_going)
  {
    char * line = NULL;
    // Other stuff that needs to be done…
    line = readline("$ ");
    if (line == NULL)
      {
        // Probably EOF
        keep_going = false;
        goto finally;
      }
  finally:
    free(line);
  }

Se disponi anche di altri percorsi di errore, puoi tranquillamente dire goto finally; più volte nel tuo ciclo. Questo perché free(NULL) è un no-op sicuro. Se hai a che fare con risorse in cui la funzione di deallocazione deve essere chiamata solo con risorse valide, il tuo codice di pulizia dovrebbe verificare se ha effettivamente una risorsa da liberare. Questo uso disciplinato di goto sostanzialmente imita la distruzione deterministica in C ++ ed è un idioma molto utile in C.

    
risposta data 02.05.2016 - 16:42
fonte

Leggi altre domande sui tag