Napisałem funkcję zawierającą tablicę jako argument i wywołałem ją, przekazując wartość tablicy w następujący sposób.
void arraytest(int a[])
{
// changed the array a
a[0]=a[0]+a[1];
a[1]=a[0]-a[1];
a[0]=a[0]-a[1];
}
void main()
{
int arr[]={1,2};
printf("%d \t %d",arr[0],arr[1]);
arraytest(arr);
printf("\n After calling fun arr contains: %d\t %d",arr[0],arr[1]);
}
Odkryłem, że chociaż wywołuję arraytest()
funkcję, przekazując wartości, oryginalna kopia int arr[]
została zmieniona.
Czy możesz wyjaśnić dlaczego?
c
arrays
function
parameters
parameter-passing
Mohan Mahajan
źródło
źródło
main()
musi wrócićint
.Odpowiedzi:
Podczas przekazywania tablicy jako parametru this
void arraytest(int a[])
oznacza dokładnie to samo co
void arraytest(int *a)
więc są modyfikując wartości w głównym.
Z powodów historycznych tablice nie są obywatelami pierwszej kategorii i nie można ich przekazywać według wartości.
źródło
struct
zmieniło dopiero po dodaniu do języka. A potem uznano, że jest za późno na zmianę reguł dla tablic. Było już 10 użytkowników. :-)void arraytest(int a[1000])
itd. itp. Rozszerzona odpowiedź tutaj: stackoverflow.com/a/51527502/4561887 .Jeśli chcesz przekazać jednowymiarową tablicę jako argument w funkcji , musisz zadeklarować parametr formalny na jeden z następujących trzech sposobów, a wszystkie trzy metody deklaracji dają podobne wyniki, ponieważ każda z nich mówi kompilatorowi, że zmierza wskaźnik liczby całkowitej do odbioru .
int func(int arr[], ...){ . . . } int func(int arr[SIZE], ...){ . . . } int func(int* arr, ...){ . . . }
Więc modyfikujesz oryginalne wartości.
Dzięki !!!
źródło
Nie przekazujesz tablicy jako kopii. Jest to tylko wskaźnik wskazujący na adres, pod którym w pamięci znajduje się pierwszy element tablicy.
źródło
Przekazujesz adres pierwszego elementu tablicy
źródło
Tablice w języku C są w większości przypadków konwertowane na wskaźnik do pierwszego elementu samej tablicy. Bardziej szczegółowo, tablice przekazywane do funkcji są zawsze konwertowane na wskaźniki.
Oto cytat z K & R2nd :
Pisanie:
void arraytest(int a[])
ma takie samo znaczenie jak pisanie:
void arraytest(int *a)
Więc pomimo tego, że nie piszesz tego wprost, dzieje się tak, gdy przekazujesz wskaźnik, a więc modyfikujesz wartości w main.
Po więcej, naprawdę polecam to przeczytać .
Ponadto możesz znaleźć inne odpowiedzi na SO tutaj
źródło
Przekazujesz wartość lokalizacji pamięci pierwszego elementu tablicy.
Dlatego kiedy zaczynasz modyfikować tablicę wewnątrz funkcji, modyfikujesz oryginalną tablicę.
Pamiętaj, że
a[1]
jest*(a+1)
.źródło
W C, z wyjątkiem kilku specjalnych przypadków, odwołanie do tablicy zawsze „rozpada się” na wskaźnik do pierwszego elementu tablicy. Dlatego nie jest możliwe przekazanie tablicy „według wartości”. Tablica w wywołaniu funkcji zostanie przekazana do funkcji jako wskaźnik, co jest analogiczne do przekazania tablicy przez referencję.
EDYCJA: Istnieją trzy takie specjalne przypadki, w których tablica nie rozpada się na wskaźnik do jej pierwszego elementu:
sizeof a
to nie to samo cosizeof (&a[0])
.&a
to nie to samo co&(&a[0])
(i nie to samo co&a[0]
).char b[] = "foo"
to nie to samo cochar b[] = &("foo")
.źródło
int a[10]
i przypisałem losową wartość każdemu elementowi. Teraz, jeśli przekażę tę tablicę do funkcji za pomocąint y[]
lubint y[10]
lub,int *y
a następnie w tej funkcji, której używam,sizeof(y)
będzie odpowiedź, że wskaźnik bajtów został przydzielony. Tak więc w tym przypadku zaniknie jako wskaźnik, byłoby pomocne, gdybyś to również dołączył. Zobacz ten postimg.org/image/prhleuezdsizeof
operacji w funkcji w pierwotnie zdefiniowanej przez nas tablicy, to rozpadnie się jako tablica, ale jeśli przekażę inną funkcję, wtedysizeof
operator use rozpadnie się jako wskaźnik.&a
to nie to samo, co&a[0]
kiedya
jest tablicą. Jak to? W moim programie testowym oba pokazują, że są takie same, zarówno w funkcji, w której zadeklarowano tablicę, jak i po przekazaniu do innej funkcji. 2. Autor pisze, że „char b[] = "foo"
to nie to samo cochar b[] = &("foo")
”. Dla mnie ta ostatnia nawet się nie kompiluje. Czy to tylko ja?Przekazywanie tablicy wielowymiarowej jako argumentu do funkcji. Przekazanie jednej tablicy dim jako argumentu jest mniej lub bardziej trywialne. Przyjrzyjmy się bardziej interesującemu przypadkowi przekazywania tablicy 2 dim. W C nie możesz użyć wskaźnika do konstrukcji wskaźnika (
int **
) zamiast 2 dim array. Zróbmy przykład:void assignZeros(int(*arr)[5], const int rows) { for (int i = 0; i < rows; i++) { for (int j = 0; j < 5; j++) { *(*(arr + i) + j) = 0; // or equivalent assignment arr[i][j] = 0; } }
Tutaj określiłem funkcję, która jako pierwszy argument przyjmuje wskaźnik do tablicy 5 liczb całkowitych. Jako argument mogę podać dowolną 2 dim tablicę, która ma 5 kolumn:
int arr1[1][5] int arr1[2][5] ... int arr1[20][5] ...
Możesz wpaść na pomysł zdefiniowania bardziej ogólnej funkcji, która może akceptować dowolną 2 tablice dim i zmienić sygnaturę funkcji w następujący sposób:
void assignZeros(int ** arr, const int rows, const int cols) { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { *(*(arr + i) + j) = 0; } } }
Ten kod zostałby skompilowany, ale podczas próby przypisania wartości w taki sam sposób, jak w przypadku pierwszej funkcji wystąpi błąd w czasie wykonywania. Więc w C wielowymiarowe tablice nie są tym samym, co wskaźniki do wskaźników ... do wskaźników. An
int(*arr)[5]
jest wskaźnikiem do tablicy składającej się z 5 elementów, anint(*arr)[6]
jest wskaźnikiem do tablicy składającej się z 6 elementów, a są one wskaźnikami do różnych typów!Jak zdefiniować argumenty funkcji dla wyższych wymiarów? Proste, po prostu podążamy za wzorem! Oto ta sama funkcja dostosowana do przyjęcia tablicy 3 wymiarów:
void assignZeros2(int(*arr)[4][5], const int dim1, const int dim2, const int dim3) { for (int i = 0; i < dim1; i++) { for (int j = 0; j < dim2; j++) { for (int k = 0; k < dim3; k++) { *(*(*(arr + i) + j) + k) = 0; // or equivalent assignment arr[i][j][k] = 0; } } } }
Jak można się spodziewać, może przyjąć jako argument dowolne 3 ciemne tablice, które mają w drugim wymiarze 4 elementy, aw trzecim wymiarze 5 elementów. Wszystko takie byłoby w porządku:
arr[1][4][5] arr[2][4][5] ... arr[10][4][5] ...
Ale musimy określić wszystkie wymiary, aż do pierwszego.
źródło
Standardowe użycie tablicy w C z naturalnym zanikaniem typu z tablicy na ptr
@Bo Persson słusznie stwierdza w swej wielkiej odpowiedź tutaj :
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 an
int *
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; // works fine because `array1` automatically decays from an array type // to `int *` arraytest(array1); // works fine because `array2` is already an `int *` 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):
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ę naint *
. JEDNAK, jeśli zamiast tego weźmiesz adresarray1
i zadzwoniszarraytest(&array1)
, uzyskasz zupełnie inne zachowanie! Teraz NIE rozpada się naint *
! Zamiast tego typ&array1
isint (*)[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]) { // my function here }
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 2int
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 doint
).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]; // ok, since the type of `array1` is `int (*)[2]` (ptr to array of // 2 ints) arraytest(&array1); // you must use the & operator here to prevent // `array1` from otherwise automatically decaying // into `int *`, which is the WRONG input type here!
To jednak spowoduje ostrzeżenie:
int array1[2]; // WARNING! Wrong type since the type of `array1` decays to `int *`: // main.c:32:15: warning: passing argument 1 of ‘arraytest’ from // incompatible pointer type [-Wincompatible-pointer-types] // main.c:22:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’ arraytest(array1); // (missing & operator)
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:zamieni się w ten błąd kompilacji:
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]; // "type safe" ptr to array of size 2 of int: 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ż:
źródło
void arraytest(int (*a)[1000])
jest lepsze, ponieważ kompilator wyświetli błąd, jeśli rozmiar jest nieprawidłowy.Forcing type safety on arrays in C
, obejmującą twoje punkt.Tablice są zawsze przekazywane przez odwołanie, jeśli używasz
a[]
lub*a
:int* printSquares(int a[], int size, int e[]) { for(int i = 0; i < size; i++) { e[i] = i * i; } return e; } int* printSquares(int *a, int size, int e[]) { for(int i = 0; i < size; i++) { e[i] = i * i; } return e; }
źródło