Standardowe użycie tablicy w C z naturalnym zanikaniem typu z tablicy na ptr
@Bo Persson słusznie stwierdza w swej wielkiej odpowiedź tutaj :
Podczas przekazywania tablicy jako parametru this
void arraytest(int a[])
oznacza dokładnie to samo co
void arraytest(int *a)
Dodam jednak również, że powyższe dwie formy również:
znaczy dokładnie to samo co
void arraytest(int a[0])
co oznacza dokładnie to samo co
void arraytest(int a[1])
co oznacza dokładnie to samo co
void arraytest(int a[2])
co oznacza dokładnie to samo co
void arraytest(int a[1000])
itp.
W każdym z powyższych przykładów tablic typ parametru wejściowego rozpada się na anint *
i można go wywołać bez ostrzeżeń i błędów, nawet przy -Wall -Wextra -Werror
włączonych opcjach kompilacji (zobacz moje repozytorium, aby uzyskać szczegółowe informacje na temat tych 3 opcji kompilacji), na przykład to:
int array1[2];
int * array2 = array1;
arraytest(array1);
arraytest(array2);
W rzeczywistości, „wielkość” wartość ( [0]
, [1]
, [2]
, [1000]
, itd.) Wewnątrz parametru tablicy tu widocznie tylko w celach estetycznych / self-dokumentacyjne, a może być dowolną liczbą całkowitą dodatnią ( size_t
typ I myślę) chcesz!
W praktyce jednak należy go użyć do określenia minimalnego rozmiaru tablicy, jaką ma otrzymać funkcja, aby podczas pisania kodu można było łatwo śledzić i weryfikować. MISRA-C-2012 Standard ( kupić / pobrać 236-PG 2012 wersja PDF standardem dla £ 15.00 tutaj ) idzie tak daleko, aby państwa (podkreślenie dodane):
Reguła 17.5 Argument funkcji odpowiadający parametrowi zadeklarowanemu jako typ tablicowy powinien mieć odpowiednią liczbę elementów.
...
Jeśli parametr jest zadeklarowany jako tablica o określonym rozmiarze, odpowiedni argument w każdym wywołaniu funkcji powinien wskazywać na obiekt, który ma co najmniej tyle elementów, co tablica.
...
Użycie deklaratora tablicy dla parametru funkcji określa interfejs funkcji wyraźniej niż użycie wskaźnika. Minimalna liczba elementów oczekiwana przez funkcję jest wyraźnie określona, podczas gdy nie jest to możliwe w przypadku wskaźnika.
Innymi słowy, zalecają użycie jawnego formatu rozmiaru, mimo że standard C technicznie go nie wymusza - pomaga to przynajmniej wyjaśnić Tobie jako programistom i innym korzystającym z kodu, jakiego rozmiaru tablica oczekuje funkcja do przejścia.
Wymuszenie bezpieczeństwa typu na tablicach w C
Jak @Winger Sendon zwraca uwagę w komentarzu pod moją odpowiedź, możemy zmusić C traktować tablicę typ być różny w zależności od matrycy wielkości !
Po pierwsze, musisz rozpoznać, że w moim przykładzie powyżej, używając int array1[2];
następującego: arraytest(array1);
powoduje, array1
że automatycznie rozpada się na int *
. JEDNAK, jeśli zamiast tego weźmiesz adres array1
i zadzwonisz arraytest(&array1)
, uzyskasz zupełnie inne zachowanie! Teraz NIE rozpada się na int *
! Zamiast tego typ &array1
is int (*)[2]
, co oznacza „wskaźnik do tablicy o rozmiarze 2 typu int” lub „wskaźnik do tablicy o rozmiarze 2 typu int” . Możesz więc WYMUSIĆ C, aby sprawdzić bezpieczeństwo typów w tablicy, na przykład:
void arraytest(int (*a)[2])
{
}
Ta składnia jest trudna do odczytania, ale podobna do tej ze wskaźnika funkcji . Narzędzie online, cdecl , mówi nam, że int (*a)[2]
oznacza to: „zadeklaruj jako wskaźnik do tablicy 2 int” (wskaźnik do tablicy 2 int
s). NIE myl tego z wersją bez nawiasów OUT:, int * a[2]
co oznacza: „zadeklaruj jako tablicę 2 wskaźnika do int” (tablica 2 wskaźników do int
).
Teraz ta funkcja WYMAGA wywołania jej za pomocą operatora adresu ( &
) w ten sposób, używając jako parametru wejściowego WSKAŹNIKA NA TABLICĘ O PRAWIDŁOWYM ROZMIARZE !:
int array1[2];
arraytest(&array1);
To jednak spowoduje ostrzeżenie:
int array1[2];
arraytest(array1);
Możesz przetestować ten kod tutaj .
Aby zmusić kompilator C do zmiany tego ostrzeżenia w błąd, tak aby zawsze MUSISZ wywoływać arraytest(&array1);
używając tylko tablicy wejściowej o odpowiednim rozmiarze i typie ( int array1[2];
w tym przypadku), dodaj -Werror
do opcji kompilacji. Jeśli uruchamiasz powyższy kod testowy na onlinegdb.com, zrób to, klikając ikonę koła zębatego w prawym górnym rogu i kliknij „Dodatkowe flagi kompilatora”, aby wpisać tę opcję. Teraz to ostrzeżenie:
main.c:34:15: warning: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Wincompatible-pointer-types]
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
zamieni się w ten błąd kompilacji:
main.c: In function ‘main’:
main.c:34:15: error: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Werror=incompatible-pointer-types]
arraytest(array1);
^~~~~~
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
void arraytest(int (*a)[2])
^~~~~~~~~
cc1: all warnings being treated as errors
Zwróć uwagę, że możesz również utworzyć wskaźniki „bezpieczne dla typów” do tablic o danym rozmiarze, na przykład:
int array[2];
int (*array_p)[2] = &array;
... ale niekoniecznie to polecam, ponieważ przypomina mi to wiele wybryków C ++ używanych do wymuszania wszędzie bezpieczeństwa typów, przy wyjątkowo wysokich kosztach złożoności składni języka, szczegółowości i trudności w projektowaniu kodu, a których nie lubię i narzekałem już wiele razy (np .: zobacz „Moje myśli o C ++” tutaj ).
Dodatkowe testy i eksperymenty znajdują się w linku poniżej.
Bibliografia
Zobacz linki powyżej. Również:
- Moje eksperymenty z kodem online: https://onlinegdb.com/B1RsrBDFD