Czy powinienem zwolnić zmienne char*, gdy zostały zainicjowane za pomocą literałów ciągu? Według mnie składnia doprowadziłaby mnie do założenia, że ​​są one alokowane tylko na stosie, ale ten przykład pokazał mi, że tak nie jest.

#include <stdlib.h>
#include <stdio.h>

static char* globalBuffer;

typedef struct Container {
    char* buffer;
} Container;

Container* Container_new(char* buffer) {
    Container* container = malloc(sizeof(Container));
    container->buffer    = buffer;
    globalBuffer         = buffer;
    return container;
}

void Container_print(Container* container) {
    if (container->buffer != NULL) {
        printf("%s", container->buffer);
        printf("\n");
    }
    else {
        printf("Container contains a NULL-buffer.");
    }
}

Container* stage() {
    Container* container = Container_new("Test-string.");
    Container_print(container);
    return container;
}

int main() {
    Container* container = stage();
    Container_print(container);

    free(container);
    Container_print(container); // I know, this results in undefined behaviour

    printf(globalBuffer);
    printf("\n");

    return 0;
}

Otrzymuję następujący wynik:

C:\Users\niklas\Desktop>gcc char_test.c

C:\Users\niklas\Desktop>a.exe
Test-string.
Test-string.
­6>
Test-string.

C:\Users\niklas\Desktop>

Tak więc char* zainicjowany za pomocą literałów ciągów nadal istnieje, nawet jeśli wyszedł poza zakres.

Więc moje pytanie, czy powinienem zwolnić takie char* wskaźniki? Czy to byłby właściwy main()?

int main() {
    Container* container = stage();
    Container_print(container);

    free(container->buffer);    // NEW
    free(container);
    Container_print(container);

    printf(globalBuffer);
    printf("\n");

    return 0;
}
23
Niklas R 29 luty 2012, 22:26

2 odpowiedzi

Najlepsza odpowiedź

Literały łańcuchowe są przechowywane w taki sposób, że są dostępne przez cały okres istnienia programu; jeśli piszesz

char *ptr = "This is a test";

Wszystko, co jest zapisywane do ptr, to adres literału ciągu znaków "This is a test". Nawet jeśli zmienna ptr wyjdzie poza zakres, literał ciągu nadal istnieje we własnej sekcji pamięci, która nie jest tą samą sekcją używaną przez malloc (przynajmniej , a nie na poziomie logicznym). Zauważ, że wiele wystąpień tego samego literału łańcuchowego może rozwiązać w tej samej lokalizacji; IOW, biorąc pod uwagę

char *p0 = "This is a test";
char *p1 = "This is a test";

p0 i p1 mogą zawierać ten sam adres (od kompilatora zależy, czy wielokrotne wystąpienia literałów ciągów są mapowane do tej samej lokalizacji, czy nie).

Kiedy dzwonisz Container_new, wszystko co robisz to kopiowanie adresu do container->buffer i globalBuffer; obie kończą się wskazywaniem na tę samą rzecz, która istnieje niezależnie od każdego z nich. free-ing container nie wpływa na literał ciągu, na który wskazuje container->buffer, więc printf(globalBuffer); nadal wyświetla "Test-string.".

Podsumowując, nie dzwonić

free(container->buffer);

Dla tego konkretnego programu, ponieważ nie przypisałeś do niego wyniku wywołania malloc, calloc ani realloc.

Jeśli, OTOH, napisałeś Container_new jako

Container* Container_new(char* buffer) 
{
  Container* container = malloc(sizeof(Container));
  container->buffer    = malloc(strlen(buffer) + 1);  // Allocate memory to 
  if (container->buffer)                              // store a *new* instance
  {                                                   // of the input string. 
    strcpy(container->buffer, buffer);                // This will need to be 
  }                                                   // freed before freeing
  globalBuffer         = buffer;                      // the container
  return container;
}

Wtedy musisz uwolnić container->buffer przed zwolnieniem container.

34
John Bode 29 luty 2012, 23:40

Nigdy nie będziesz free() pamiętał, którego nie malloc()edytowałeś.

Sposób, w jaki kompilator implementuje literały łańcuchowe, nie jest Twoją sprawą: to szczegół implementacji. Możesz free() wskazać pamięć, którą przydzieliłeś za pomocą malloc() i tylko te, w przeciwnym razie ryzykujesz życiem swojego systemu.

Idealnie, wywołania malloc() i wywołania free() powinny pojawić się na tym samym „poziomie projektu” (na przykład w tym samym pliku implementacyjnym dla tego samego modułu) i powinny idealnie pasować: jeden free() dla każdego malloc(). ale nie zawsze jest to możliwe.

(Zauważ, że niektóre biblioteki przydzielają bloki pamięci, zwracają wskaźniki do tych bloków i instruują Cię, abyś je uwolnił. W tym przypadku możesz uwolnić te wskaźniki, ale jest to zła praktyka projektowa ze strony ludzi, którzy stworzyli biblioteka.)

37
nsg 23 lipiec 2013, 04:27