To pytanie jest skierowane do guru C:
W C można zadeklarować wskaźnik w następujący sposób:
char (* p)[10];
.. który zasadniczo stwierdza, że ten wskaźnik wskazuje na tablicę 10 znaków. Zaletą deklarowania takiego wskaźnika jest to, że jeśli spróbujesz przypisać wskaźnik tablicy o różnym rozmiarze do p, otrzymasz błąd czasu kompilacji. Wystąpi również błąd czasu kompilacji, jeśli spróbujesz przypisać wartość prostego wskaźnika znaku do p. Próbowałem tego z gcc i wydaje się, że działa z ANSI, C89 i C99.
Wydaje mi się, że zadeklarowanie takiego wskaźnika byłoby bardzo przydatne - szczególnie podczas przekazywania wskaźnika do funkcji. Zwykle ludzie pisaliby prototyp takiej funkcji:
void foo(char * p, int plen);
Jeśli spodziewasz się bufora o określonej wielkości, po prostu przetestujesz wartość plen. Nie możesz jednak zagwarantować, że osoba, która przekaże ci p, naprawdę da ci pełne prawidłowe miejsce w pamięci w tym buforze. Musisz ufać, że osoba, która nazwała tę funkcję, postępuje właściwie. Z drugiej strony:
void foo(char (*p)[10]);
.. zmusiłoby wywołującego do podania buforu o określonym rozmiarze.
Wydaje się to bardzo przydatne, ale nigdy nie widziałem wskaźnika zadeklarowanego w ten sposób w żadnym kodzie, z którym się spotkałem.
Moje pytanie brzmi: czy jest jakiś powód, dla którego ludzie nie deklarują takich wskazówek? Czy nie widzę jakiejś oczywistej pułapki?
10
można ją zastąpić dowolną zmienną w zakresieOdpowiedzi:
To, co mówisz w swoim poście, jest całkowicie poprawne. Powiedziałbym, że każdy programista C dochodzi do dokładnie tego samego odkrycia i do dokładnie tego samego wniosku, gdy (jeśli) osiągnie pewien poziom biegłości w języku C.
Gdy specyfika obszaru aplikacji wywołuje tablicę o określonym stałym rozmiarze (rozmiar tablicy jest stałą czasu kompilacji), jedynym właściwym sposobem przekazania takiej tablicy do funkcji jest użycie parametru wskaźnika do tablicy
(w języku C ++ odbywa się to również z odwołaniami
).
Umożliwi to sprawdzanie typu na poziomie języka, co zapewni, że tablica o dokładnie poprawnym rozmiarze zostanie dostarczona jako argument. W rzeczywistości w wielu przypadkach ludzie używają tej techniki niejawnie, nawet nie zdając sobie z tego sprawy, ukrywając typ tablicy za nazwą typedef
Zauważ dodatkowo, że powyższy kod jest niezmienny w stosunku do
Vector3d
typu będącego tablicą lubstruct
. MożeszVector3d
w dowolnym momencie przełączyć definicję z tablicy na astruct
iz powrotem, bez konieczności zmiany deklaracji funkcji. W obu przypadkach funkcje otrzymają obiekt zagregowany „przez odniesienie” (są od tego wyjątki, ale w kontekście tej dyskusji jest to prawdą).Jednak nie zobaczysz tej metody przekazywania tablic używanej jawnie zbyt często, po prostu dlatego, że zbyt wielu ludzi jest zdezorientowanych dość zawiłą składnią i po prostu nie czuje się wystarczająco dobrze z takimi funkcjami języka C, aby używać ich poprawnie. Z tego powodu w przeciętnym prawdziwym życiu przekazywanie tablicy jako wskaźnika do jej pierwszego elementu jest bardziej popularnym podejściem. Po prostu wygląda na „prostsze”.
Ale w rzeczywistości użycie wskaźnika do pierwszego elementu do przekazywania tablic jest bardzo niszową techniką, sztuczką, która służy bardzo konkretnemu celowi: jego jedynym celem jest ułatwienie przekazywania tablic o różnej wielkości (tj. Rozmiar w czasie wykonywania) . Jeśli naprawdę potrzebujesz mieć możliwość przetwarzania tablic o rozmiarze w czasie wykonywania, to właściwym sposobem przekazania takiej tablicy jest użycie wskaźnika do jej pierwszego elementu z konkretnym rozmiarem dostarczonym przez dodatkowy parametr
W rzeczywistości w wielu przypadkach bardzo przydatna jest możliwość przetwarzania tablic o rozmiarze w czasie wykonywania, co również przyczynia się do popularności tej metody. Wielu programistów C po prostu nigdy nie napotyka (lub nigdy nie rozpoznaje) potrzeby przetwarzania tablicy o stałym rozmiarze, pozostając w ten sposób nieświadomymi właściwej techniki stałego rozmiaru.
Niemniej jednak, jeśli rozmiar tablicy jest stały, przekazanie jej jako wskaźnika do elementu
jest głównym błędem technicznym, który niestety jest obecnie dość powszechny. W takich przypadkach znacznie lepszym podejściem jest technika typu wskaźnik do tablicy.
Innym powodem, który może utrudniać przyjęcie techniki przekazywania tablic o stałym rozmiarze, jest dominacja naiwnego podejścia do typowania tablic alokowanych dynamicznie. Na przykład, jeśli program wymaga ustalonych tablic typu
char[10]
(jak w twoim przykładzie), przeciętny programista użyjemalloc
takich tablic, jakTej tablicy nie można przekazać do funkcji zadeklarowanej jako
co dezorientuje przeciętnego programistę i sprawia, że rezygnują z deklaracji parametru o stałym rozmiarze bez zastanawiania się nad tym. W rzeczywistości jednak źródłem problemu jest naiwne
malloc
podejście.malloc
Format pokazano powyżej powinny być zarezerwowane dla tablic o rozmiarze run-time. Jeśli typ tablicy ma rozmiar w czasie kompilacji, lepszy sposóbmalloc
będzie wyglądał następującoMożna to oczywiście łatwo przenieść na powyższe zadeklarowane
foo
a kompilator przeprowadzi odpowiednie sprawdzenie typu. Ale znowu, jest to zbyt zagmatwane dla nieprzygotowanego programisty C, dlatego nie zobaczysz tego zbyt często w „typowym”, przeciętnym, codziennym kodzie.
źródło
const
z nieruchomościami za pomocą tej techniki.const char (*p)[N]
Argument nie wydaje się zgodne ze wskaźnikiem dochar table[N];
Natomiast prostychar*
PTR pozostają zgodne zconst char*
argumentem.(*p)[i]
a nie*p[i]
. Ten ostatni przeskoczy o rozmiar tablicy, co prawie na pewno nie jest tym, czego chcesz. Przynajmniej dla mnie nauczenie się tej składni spowodowało, a nie zapobiegło, błąd; Otrzymałbym poprawny kod szybciej, po prostu przekazując float *.const
wskaźnik do tablicy zmiennych elementów. I tak, to zupełnie co innego niż wskaźnik do tablicy niezmiennych elementów.Chciałbym dodać do odpowiedzi AndreyTa (na wypadek, gdyby ktoś natknął się na tę stronę, szukając więcej informacji na ten temat):
Kiedy zaczynam bardziej bawić się tymi deklaracjami, zdaję sobie sprawę, że istnieje z nimi poważny ułomność w C (najwyraźniej nie w C ++). Dość często mamy do czynienia z sytuacją, w której chciałbyś dać wywołującemu wskaźnik const do bufora, do którego zapisałeś. Niestety nie jest to możliwe, gdy deklarujemy taki wskaźnik w C. Innymi słowy, standard C (6.7.3 - Paragraf 8) jest sprzeczny z czymś takim:
Wydaje się, że to ograniczenie nie występuje w C ++, co czyni tego typu deklaracje znacznie bardziej użytecznymi. Ale w przypadku C konieczne jest cofnięcie się do zwykłej deklaracji wskaźnika, gdy chcesz mieć stały wskaźnik do bufora o stałym rozmiarze (chyba że sam bufor został zadeklarowany jako const). Więcej informacji znajdziesz w tym wątku poczty: tekst linku
Moim zdaniem jest to poważne ograniczenie i może to być jeden z głównych powodów, dla których ludzie zwykle nie deklarują takich wskaźników w C. Drugim jest fakt, że większość ludzi nawet nie wie, że można zadeklarować taki wskaźnik jako Zwrócił uwagę AndreyT.
źródło
Oczywistym powodem jest to, że ten kod nie kompiluje się:
Domyślną promocją tablicy jest niekwalifikowany wskaźnik.
Zobacz także to pytanie , używanie
foo(&p)
powinno działać.źródło
foo(&p)
.Chcę również użyć tej składni, aby umożliwić więcej sprawdzania typów.
Ale zgadzam się również, że składnia i model mentalny używania wskaźników jest prostszy i łatwiejszy do zapamiętania.
Oto kilka innych przeszkód, na które się natknąłem.
Dostęp do tablicy wymaga użycia
(*p)[]
:Zamiast tego kuszące jest użycie lokalnego wskaźnika do znaku:
Ale to częściowo zniweczyłoby cel używania właściwego typu.
Należy pamiętać o używaniu operatora address-of podczas przypisywania adresu tablicy do wskaźnika do tablicy:
Operator address-of pobiera adres całej tablicy
&a
wraz z poprawnym typem do przypisaniap
. Bez operatoraa
jest automatycznie konwertowany na adres pierwszego elementu tablicy, tak samo jak in&a[0]
, który ma inny typ.Ponieważ ta automatyczna konwersja już ma miejsce, zawsze zastanawiam się, czy
&
jest to konieczne. Jest to zgodne ze stosowaniem&
zmiennych on innych typów, ale muszę pamiętać, że tablica jest specjalna i potrzebuję jej,&
aby uzyskać właściwy typ adresu, mimo że wartość adresu jest taka sama.Jednym z powodów mojego problemu może być to, że nauczyłem się K&R C jeszcze w latach 80., co nie pozwalało jeszcze na używanie
&
operatora na całych tablicach (chociaż niektóre kompilatory ignorowały to lub tolerowały składnię). Co, nawiasem mówiąc, może być kolejnym powodem, dla którego wskaźniki-tablice mają trudności z przyjęciem: działają one poprawnie tylko od czasu ANSI C, a&
ograniczenie operatora mogło być kolejnym powodem, aby uznać je za zbyt niewygodne.Gdy nie
typedef
jest używany do tworzenia typu dla wskaźnika do tablicy (we wspólnym pliku nagłówkowym), wówczas globalny wskaźnik do tablicy wymaga bardziej skomplikowanej deklaracji, aby udostępnić go między plikami:extern
źródło
Cóż, po prostu C nie robi rzeczy w ten sposób. Tablica typu
T
jest przekazywana jako wskaźnik do pierwszegoT
w tablicy i to wszystko, co otrzymujesz.Pozwala to na kilka fajnych i eleganckich algorytmów, takich jak zapętlanie tablicy z wyrażeniami takimi jak
Wadą jest to, że zarządzanie rozmiarem zależy od Ciebie. Niestety, niepowodzenie w zrobieniu tego świadomie doprowadziło również do milionów błędów w kodowaniu C i / lub możliwości złowrogiej eksploatacji.
To, co zbliża się do tego, o co prosisz w C, to przekazanie a
struct
(według wartości) lub wskaźnika do jednego (przez odniesienie). Dopóki ten sam typ struktury jest używany po obu stronach tej operacji, zarówno kod, który przekazuje odwołanie, jak i kod, który go używa, są zgodne co do rozmiaru obsługiwanych danych.Twoja struktura może zawierać dowolne dane; może zawierać twoją tablicę o dobrze określonym rozmiarze.
Mimo to nic nie stoi na przeszkodzie, abyś ty lub niekompetentny lub złośliwy programista używał rzutowań, aby oszukać kompilator i potraktować twoją strukturę jako inną o innym rozmiarze. Niemal nieskrępowana umiejętność robienia tego typu rzeczy jest częścią projektu C.
źródło
Tablicę znaków można zadeklarować na kilka sposobów:
Prototyp funkcji, która przyjmuje tablicę według wartości, to:
lub przez odniesienie:
lub według składni tablicy:
źródło
char (*p)[10] = malloc(sizeof *p)
.Nie polecam tego rozwiązania
ponieważ przesłania fakt, że Vector3D ma typ, o którym musisz wiedzieć. Programiści zwykle nie oczekują, że zmienne tego samego typu będą miały różne rozmiary. Rozważ:
gdzie sizeof a! = sizeof b
źródło
sizeof(a)
nie jest taki sam jaksizeof(b)
?Może czegoś mi brakuje, ale ... ponieważ tablice są stałymi wskaźnikami, zasadniczo oznacza to, że nie ma sensu przekazywać do nich wskaźników.
Nie mógłbyś po prostu użyć
void foo(char p[10], int plen);
?źródło
Na moim kompilatorze (vs2008) traktuje to
char (*p)[10]
jako tablicę wskaźników znaków, tak jakby nie było nawiasów, nawet jeśli kompiluję jako plik C. Czy kompilator obsługuje tę „zmienną”? Jeśli tak, jest to główny powód, aby go nie używać.źródło