2015-02-22 16 views
21

Zauważyłem, że niektóre osoby i odniesienia, takie jak książki, stwierdzają, że jeśli p != NULL i p pochodzą z poprzedniej alokacji (np. Przez malloc), to realloc(p, 0) jest równoważne free(p) na GNU/Linux. Aby wesprzeć tę tezę man realloc stany dokładnie w ten sposób (Kopalnia nacisk idzie do przodu):Czy realloc (p, 0) naprawdę obejmuje darmowe (p) w glibc?

Funkcja realloc() zmienia rozmiar bloku pamięci wskazywanego przez ptr do size bajtów. Zawartość pozostanie niezmieniona w zakresie od początku regionu do minimum starych i nowych rozmiarów. Jeśli nowy rozmiar jest większy niż stary rozmiar, dodana pamięć nie zostanie zainicjowana. Jeśli ptr ma wartość NULL, wywołanie jest równoważne malloc (rozmiar) dla wszystkich wartości wielkości; jeśli rozmiar jest równy zeru, a ptr nie ma wartości NULL, wówczas wywołanie jest równoważne bezpłatnemu (ptr). Jeśli ptr nie ma wartości NULL, musi zostać zwrócone przez wcześniejsze wywołanie metody malloc(), calloc() lub realloc(). Jeśli wskazany obszar został przeniesiony, wykonywany jest bezpłatny (ptr) .

Jak można znaleźć w this question, norma C nie określa dokładnie, co powinno się stać, a rzeczywiste zachowanie jest zdefiniowane w ramach implementacji. Dokładniej:

C11 §7.22.3/P1 funkcje zarządzania pamięcią mówi:

Jeśli wielkość przestrzeni wymagane jest zero, zachowanie jest wdrożenie zdefiniowane: albo null wskaźnik jest zwracany, lub zachowanie zachowuje się tak, jakby wielkość miała pewną wartość różną od zera, z tym wyjątkiem, że zwracany wskaźnik nie będzie używany do uzyskania dostępu do obiektu.

i C11 §7.22.3.5 Funkcja realloc zawiera:

3) (...) Jeśli pamięć dla nowego obiektu nie można przyporządkować, stary obiekt jest nierozdzielone i jego wartość jest niezmieniona.

4) Funkcja realloc zwraca wskaźnik do nowego obiektu (co może mają taką samą wartość jak wskaźnik do starego obiektu), lub null pointer jeśli nowy obiekt nie może być przydzielona.

pisałem kilka podstawowych kod, aby dowiedzieć się rzeczywiste zachowanie z pomocą mcheck, sprawdzający pamięci, który jest dostarczany wraz glibc:

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

int main(void) 
{ 
    int a = 5; 
    int *p, *q; 

    mtrace(); 

    p = malloc(sizeof(int)); 
    q = &a; 

    printf("%p\n", (void *) p); 
    printf("%p\n", (void *) q); 

    q = realloc(p, 0); 

    printf("%p\n", (void *) p); 
    printf("%p\n", (void *) q); 

    return 0; 
} 

i wyniki są następujące:

$ gcc -g check.c 
$ export MALLOC_TRACE=report 
$ ./a.out 
0xfd3460 
0x7ffffbc955cc 
0xfd3460 
(nil) 
[[email protected] workspace]$ mtrace a.out report 

Memory not freed: 
----------------- 
      Address  Size  Caller 
0x0000000000fd3460  0x4 at /home/grzegorz/workspace/check.c:12 

jak ty może zobaczyć q został ustawiony na NULL. Wygląda na to, że free() nie zostało tak naprawdę wywołane. W rzeczywistości to nie może być chyba moja interpretacja jest błędna: od realloc powrócił NULL wskaźnik, nowy obiekt może nie zostały przydzielone, co oznacza, że:

stary obiekt nie jest zwalniane, a jej wartość nie ulega zmianie

Czy to prawda?

+11

Sformułowanie o 'realloc (ptr, 0)' będącym odpowiednikiem 'free (ptr)' było używane w POSIX, ale [oficjalne referencje POSIX 'realloc'] (http: //pubs.opengroup. org/onlinepubs/9699919799/functions/realloc.html) używa teraz języka ze standardu C. Standard POSIX mówi jednak "jeśli' realloc() 'zwraca zerowy wskaźnik, spacja wskazywana przez p nie została zwolniona". –

Odpowiedz

9

Edytuj: Twój glibc wydaje się być pre-2.18, w 2.18 naprawiono błąd w mtrace (patrz here). W wersji 2.20 glibc program testowy zgłasza: "Brak wycieków pamięci".

free jest wywoływana w glibc. Od źródeł obecnego glibc 2,21 (here i here):

/* 
    REALLOC_ZERO_BYTES_FREES should be set if a call to 
    realloc with zero bytes should be the same as a call to free. 
    This is required by the C standard. Otherwise, since this malloc 
    returns a unique pointer for malloc(0), so does realloc(p, 0). 
*/ 

#ifndef REALLOC_ZERO_BYTES_FREES 
#define REALLOC_ZERO_BYTES_FREES 1 
#endif 

void * 
__libc_realloc (void *oldmem, size_t bytes) 
{ 
    mstate ar_ptr; 
    INTERNAL_SIZE_T nb;   /* padded request size */ 

    void *newp;    /* chunk to return */ 

    void *(*hook) (void *, size_t, const void *) = 
    atomic_forced_read (__realloc_hook); 
    if (__builtin_expect (hook != NULL, 0)) 
    return (*hook)(oldmem, bytes, RETURN_ADDRESS (0)); 

#if REALLOC_ZERO_BYTES_FREES 
    if (bytes == 0 && oldmem != NULL) 
    { 
     __libc_free (oldmem); return 0; 
    } 
#endif 
+0

Nie wyklucza to niektórych nagłówków (opcjonalnie) definiujących "REALLOC_ZERO_BYTES_FREES" jako zero. Nie robi tego nagłówek, ale myślę, że powinieneś o tym wspomnieć w swojej odpowiedzi. – hvd

+1

@hvd Definiowanie 'REALLOC_ZERO_BYTES_FREES' ma tylko wpływ na * budowanie * glibc. Nie działa, gdy korzystasz tylko z biblioteki. –

+0

@ 一 二三 Well duh. :) Odnoszę się do nagłówków, których glibc używa. To nie jest kompletny plik źródłowy. Kompletny plik źródłowy zawiera dyrektywy # # include'. – hvd

2

Jako zachowania realloc() gdy były przekazywane rozmiar 0 jest implenentation zdefiniowane ...

Jeśli wielkość przestrzeni wymagane jest zero, zachowanie jest realizacja zdefiniowane: albo zerowy wskaźnik jest wrócił, lub zachowanie jest takie, jakby rozmiar był pewną niezerową wartością, z tym że zwracany wskaźnik nie będzie używany do uzyskania dostępu do obiektu.

... przenośny odpowiednik

void * p = malloc(1); 
free(p); 

musi być

void * p = malloc(1); 
p = realloc(p, 0) 
free(p); /* because of the part after the "or" as quoted above. 

Bilans pamięć powinna być nawet później.

Aktualizacja obejmujące realloc() „s "error" przypadek:

void * p = malloc(1); 
{ 
    void * q = realloc(p, 0); 
    p = q ?q :p; 
} 
free(p); /* because of the part after the "or" as quoted above. 
+5

Jeśli 'realloc' zwraca wskaźnik NULL, straciłeś wskaźnik, który musi zostać zwolniony :( –

+0

Masz na myśli przenośny odpowiednik' void * p = malloc (1), realloc (p, 0); 'prawda? – immibis

+2

@BenVoigt, jeśli 'realloc' zwraca wskaźnik NULL, nie wiesz, czy zwolnić stary wskaźnik, czy nie! – immibis

2

Wystarczy popatrzeć na kodzie realloc w glibc tutaj: http://code.woboq.org/userspace/glibc/malloc/memusage.c.html#realloc. W linii 434 zobaczysz, że free jest wywoływane, gdy rozmiar wynosi 0.

+4

Czy jesteś pewien, że jest to * implementacja' realloc' glibc'a? Uważam, że źródła są dość mylące, a ten plik źródłowy zawiera komentarz (w pierwszym line) "Wykorzystanie pamięci stosu i stosu w stosie programu." Istnieje inny plik, "malloc/malloc.c", który zawiera '__libc_realloc', który jest aliasowany do' realloc'. Ta implementacja w rzeczywistości zawiera kompilator swi tch nazywa się 'REALLOC_ZERO_BYTES_FREES', co ma wpływ na zachowanie w tym przypadku. – dyp

+0

Masz rację. W http://osxr.org/glibc/source/malloc/malloc.c patrz wiersze 571-57. Nie wiem, kiedy to nastąpi, lub nie ... –

+0

Zobacz także linie 217-220 w http://www.atnf.csiro.au/computing/software/casacore/casacore-1.2.0/doc/html/ malloc_8h_source.html. Definiowanie tych symboli jest kwestią spójności z zachowaniem 'malloc' przy alokacjach zerowego rozmiaru. –

7

Choć mój intepretation z „zwróciło NULL” przypadek wydaje się być prawidłowe (patrz mój edit poniżej), glibc deweloperzy decided zachować to zgodne z poprzednim C89 Standardowe i odrzucone zgodnie z C99/C11:

Nie ma sposobu, aby to się zmieniło.Tak to zostało zaimplementowane na zawsze. C powinien dokumentować istniejącą praktykę. Zmiana oznaczałoby wprowadzenie wycieków pamięci.

także mcheck wskazanie jest mylące, ponieważ inne testowy przypadku wykazała, że ​​pamięć jest skutecznie uwalnia się przez realloc:

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

int main(void) 
{ 
    int *p, *q; 

    p = malloc(20 * sizeof(int)); 
    malloc_stats(); 

    putchar('\n'); 

    q = realloc(p, 0); 
    malloc_stats(); 

    return 0; 
} 

Tutaj wyjściowy:

$ gcc check.c 
$ ./a.out 
Arena 0: 
system bytes  =  135168 
in use bytes  =   96 
Total (incl. mmap): 
system bytes  =  135168 
in use bytes  =   96 
max mmap regions =   0 
max mmap bytes =   0 

Arena 0: 
system bytes  =  135168 
in use bytes  =   0 
Total (incl. mmap): 
system bytes  =  135168 
in use bytes  =   0 
max mmap regions =   0 
max mmap bytes =   0 

Edycja :

Jak wskazano w hvd w comment, grupa robocza ISO/IEC odbyła pewną dyskusję, zmaterializowaną jako Defect Report #400. Proponowane zmiany mogą prawdopodobnie umożliwić istniejącą praktykę glibc w przyszłym przeglądzie normy C (lub ewentualnie jako spór techniczny 1 dla C11).

Co ja naprawdę jak o DR # 400 to propozycja do:

Dodaj do podrozdziału 7.31.12 nowy ustęp (pkt 2):

Wywoływanie realloc z argumentem równym rozmiarze zero to funkcja przestarzała.

+2

+1 za cytowanie modułu do śledzenia błędów. Ponownie otworzyłem tę kwestię, ponieważ decyzja została podjęta przez byłego dyktatora glibc, który teraz przeniósł się. –

+2

@R ..Czy jest jednak jakiś błąd glibc? C zmieniono, aby ponownie zezwalać na zachowanie C90 i glibc, jeśli czytam poprawnie [DR # 400] (http://open-std.org/JTC1/SC22/WG14/www/docs/dr_400.htm). Czy to nigdy nie było akceptowane? – hvd

+0

@hvd: Przypuszczam, że będzie to następny TC do C11, więc może to wystarczy, aby ludzie byli szczęśliwi. Nadal uważam, że jest to mylące. Pierwsza zmiana w proponowanym TC dla DR sprawia, że ​​brzmi jak NULL jest tylko poprawną wartością zwracaną dla 'realloc', gdy wystąpił błąd. –