2013-06-03 5 views
5

Mówi się, że macierz o zerowej długości jest dla zmiennej długości struktury, co mogę zrozumieć. Ale co mnie łamie, to dlatego, że nie używamy po prostu wskaźnika, możemy wyłuskać i przydzielić inną strukturę wielkości w ten sam sposób.Dlaczego używamy tablicy o zerowej długości zamiast wskaźników?

EDIT - Dodano przykład z uwagi

przyjmując:

struct p 
{ 
    char ch; 
    int *arr; 
}; 

Możemy to wykorzystać:

struct p *p = malloc(sizeof(*p) + (sizeof(int) * n)); 

p->arr = (struct p*)(p + 1); 

Aby uzyskać ciągły kawałek pamięci. Jednak wydawało mi się, że zapominam zajmować przestrzeń p->arr i wydaje się, że jest to rzecz zupełnie odmienna od metody tablicy o zerowym rozmiarze.

+2

Proszę podać przykład. – Arafangion

+1

Wyjaśnienie dotyczące struktur. Istnieją trzy przypadki: 'lastMemberOfArray []' w C90, 'lastMemberOfArray []' w C99 i 'lastMemberOfArray [0]' w niestandardowym GNU Goo. W przypadku C90 jest to brudny hack polegający na nieokreślonym zachowaniu. Może pojawić się problem, jeśli na końcu struktury znajdują się bajty o strukturze struct padding. C99 naprawił to i stworzył typ _flexible array member_, który działa w ten sam sposób, ale z dobrze zdefiniowanym zachowaniem. I na koniec, niestandardowe GNU pozwala na tworzenie macierzy o zerowym rozmiarze w tym samym celu. Kompilacja w standardzie z '-std = c99 -pedantic-errors' i' [0] 'nie będzie się kompilować. – Lundin

+1

@ Pierwszy przykład Lundina powinien być "' lastMemberOfArray [1] 'w C90". Prosta literówka, dodając ją tylko dla tych, którzy nie pamiętają. –

Odpowiedz

5

Są to różne formy tak zwanego "strukturalnego hacka", omówione w pytaniu 2.6 z comp.lang.c FAQ.

Definiowanie tablicy wielkości 0 jest w rzeczywistości niezgodne z literą C i obowiązuje od 1989 r. Standard ANSI. Niektóre kompilatory zezwalają na to jako rozszerzenie, ale poleganie na tym prowadzi do nieprzenośności kodu.

Bardziej przenośne sposobem realizacji tego celu jest użyć tablicę o długości 1, na przykład:

struct foo { 
    size_t len; 
    char str[1]; 
}; 

Można przydzielić więcej niż sizeof (struct foo) bajtów, przy użyciu len śledzić przydzielonego rozmiaru, a następnie dostęp do str[N], aby uzyskać N-ty element tablicy. Ponieważ kompilatory języka C zwykle nie sprawdzają granic, to na ogół "działają". Ale, ściśle rzecz biorąc, zachowanie jest niezdefiniowane.

Norma ISO 1999 dodano funkcję o nazwie „elastyczne członkowie array”, mającą na celu zastąpienie tego wykorzystania:

struct foo { 
    size_t len; 
    char str[]; 
}; 

można sobie z nich w ten sam sposób, jak w starszym struct siekać, ale zachowanie jest dobrze zdefiniowane. Ale sam musisz zrobić całą księgowość; sizeof (struct foo) nadal nie zawiera wielkości tablicy, na przykład.

Można oczywiście użyć wskaźnika zamiast:

struct bar { 
    size_t len; 
    char *ptr; 
}; 

I to jest doskonale dobre podejście, ale ma różne semantykę. Główną zaletą "struct hack" lub elastycznych elementów macierzowych jest to, że tablica jest alokowana w sposób przylegający do reszty struktury, i możesz skopiować tablicę wraz ze strukturą przy użyciu memcpy (o ile cel został odpowiednio przydzielone). Tablica jest przydzielana osobno - co może, ale nie musi być dokładnie tym, czego potrzebujesz.

8

Wskaźnik nie jest tak naprawdę potrzebny, więc kosztuje miejsca bez żadnych korzyści. Może to również oznaczać inny poziom pośrednictwa, który również nie jest naprawdę potrzebny.

Porównaj te deklaracje przykład dla dynamicznej tablicy liczb całkowitych:

typedef struct { 
    size_t length; 
    int data[0]; 
} IntArray1; 

oraz:

typedef struct { 
    size_t length; 
    int *data; 
} IntArray2; 

Zasadniczo wskaźnik wyraża „pierwszy element tablicy jest pod tym adresem, który może być czymkolwiek ", co jest bardziej ogólne niż zwykle potrzebne. Pożądany model to "pierwszy element tablicy jest tutaj, ale nie wiem, jak duża jest ta tablica".

Oczywiście druga forma umożliwia rozwijanie macierzy bez ryzyka, że ​​zmieni się "bazowy" adres (adres samej struktury IntArray2), co może być naprawdę zadbane. Nie można tego zrobić z IntArray1, ponieważ trzeba przydzielić strukturę bazową i elementy danych liczbowych łącznie. Kompromisy, kompromisy ...

+0

Tablica nie może mieć rozmiaru zerowego C11 6.7.6.2 – Lundin

12

Jeśli użyjesz wskaźnika, struktura nie będzie już miała zmiennej długości: będzie miała stałą długość, ale jej dane będą przechowywane w innym miejscu.

Ideą macierzy zerowej długości * jest przechowywanie danych tablicy „w linii” z resztą danych w strukturze, dzięki czemu danych tablicy następujące dane struktury w pamięci. Wskaźnik do osobno przydzielonego obszaru pamięci nie pozwala ci tego zrobić.


* takich układów są znane również jako elastycznych tablic; w C99 deklarujesz je jako element_type flexArray[] zamiast element_type flexArray[0], tj. zrzutu zero.

1

Dzieje się tak, ponieważ za pomocą wskaźnika potrzebujesz osobnej alokacji i przypisania.

struct WithPointer 
{ 
    int someOtherField; 
    ... 
    int* array; 
}; 

struct WithArray 
{ 
    int someOtherField; 
    ... 
    int array[1]; 
}; 

Aby uzyskać 'obiekt' z WithPointer trzeba zrobić:

struct WithPointer* withPointer = malloc(sizeof(struct WithPointer)); 
withPointer.array = malloc(ARRAY_SIZE * sizeof(int)); 

aby uzyskać 'obiekt' z WithArray:

struct WithArray* withArray = malloc(sizeof(struct WithArray) + 
              (ARRAY_SIZE - 1) * sizeof(int)); 

to wszystko.

W niektórych przypadkach jest to bardzo przydatne, a nawet konieczne, aby tablica znajdowała się w kolejnej pamięci; na przykład w pakietach protokołu sieciowego.

+2

Tablica nie może mieć zerowego rozmiaru C11 6.7.6.2 – Lundin

+0

@ Lundin Masz rację i poprawiłeś powyższy kod. –