Przekazywanie przez odniesienie w C

204

Jeśli C nie obsługuje przekazywania zmiennej przez referencję, dlaczego to działa?

#include <stdio.h>

void f(int *j) {
  (*j)++;
}

int main() {
  int i = 20;
  int *p = &i;
  f(p);
  printf("i = %d\n", i);

  return 0;
}

Wynik:

$ gcc -std=c99 test.c
$ a.exe
i = 21 
aks
źródło
22
Gdzie w tym kodzie przekazujesz odniesienie ?
Atul
15
Należy zauważyć, że C nie ma przejścia przez referencję, można go emulować tylko za pomocą wskaźników.
Jakiś programista koleś
6
Poprawna instrukcja brzmi: „C nie obsługuje niejawnego przekazywania zmiennej przez referencję” - musisz jawnie utworzyć referencję (z &) przed wywołaniem funkcji i jawnie wyrejestrować ją (z *) w funkcji.
Chris Dodd
2
Twój kod wyjściowy jest dokładnie równy, gdy wywołanie f(&i);jest implementacją przekazywania przez referencję, która nie istnieje wyłącznie w C.
Przekazywanie
@Someprogrammerdude Przekazywanie wskaźnika jest przekazywane przez odniesienie. To wydaje się być jednym z tych faktów, z których szczycą się „doświadczeni” programiści C. Jakby się z tego wywaliły. „Och, może MYŚLISZ C ma odniesienie przez odniesienie, ale nie, w rzeczywistości jest to tylko wartość przekazywanego adresu pamięci harharhar”. Przekazywanie przez referencję dosłownie oznacza tylko przekazanie adresu pamięci, w którym przechowywana jest zmienna, a nie samej wartości zmiennej. Właśnie na to pozwala C i jest przekazywany przez odniesienie za każdym razem, gdy mijasz wskaźnik, ponieważ wskaźnik jest odwołaniem do lokalizacji pamięci zmiennych.
YungGun,

Odpowiedzi:

315

Ponieważ przekazujesz wartość wskaźnika do metody, a następnie odsuwasz ją, aby uzyskać wskazaną liczbę całkowitą.

tvanfosson
źródło
4
f (p); -> czy to oznacza przekazywanie wartości? następnie wyłuskuj go, aby uzyskać wskazaną liczbę całkowitą. -> czy możesz podać więcej wyjaśnień.
bapi
4
@bapi, dereferencja wskaźnika oznacza „uzyskać wartość, do której odnosi się ten wskaźnik”.
Rauni Lillemets,
To, co nazywamy metodą wywołania funkcji, która pobiera adres zmiennej zamiast przekazywać wskaźnik. Przykład: func1 (int & a). Czy to nie jest połączenie referencyjne? W tym przypadku referencja jest naprawdę pobierana, a w przypadku wskaźnika nadal przekazujemy wartość, ponieważ przekazujemy wskaźnik tylko przez wartość.
Jon Wheelock,
1
Podczas używania wskaźników kluczowym faktem jest to, że kopia wskaźnika jest przekazywana do funkcji. Następnie funkcja używa tego wskaźnika, a nie oryginalnego. Nadal jest to wartość przekazywana, ale działa.
Danijel,
1
@Danijel Możliwe jest przekazanie wskaźnika, który nie jest kopią niczego do wywołania funkcji. Na przykład wywołanie funkcji func: func(&A);przekazałoby wskaźnik do funkcji A bez kopiowania czegokolwiek. Jest to wartość przekazywana, ale ta wartość jest referencją, więc „przechodzisz przez referencję” zmiennej A. Kopiowanie nie jest wymagane. Można powiedzieć, że jest to odnośnik.
YungGun
124

To nie jest przekazywanie przez odniesienie, to jest przekazywanie przez wartość, jak twierdzili inni.

Język C jest przekazywany przez wartość bez wyjątku. Przekazywanie wskaźnika jako parametru nie oznacza przekazywania przez odniesienie.

Reguła jest następująca:

Funkcja nie może zmienić rzeczywistej wartości parametrów.


Spróbujmy zobaczyć różnice między parametrami skalarnymi i wskaźnikowymi funkcji.

Zmienne skalarne

Ten krótki program pokazuje wartość przekazywaną za pomocą zmiennej skalarnej. paramjest nazywany parametrem formalnym, a variableprzy wywołaniu funkcji nazywany jest parametrem faktycznym. Uwaga: zwiększenie paramfunkcji nie zmienia się variable.

#include <stdio.h>

void function(int param) {
    printf("I've received value %d\n", param);
    param++;
}

int main(void) {
    int variable = 111;

    function(variable);
    printf("variable %d\m", variable);
    return 0;
}

Wynik to

I've received value 111
variable=111

Złudzenie przejścia przez odniesienie

Nieznacznie zmieniamy fragment kodu. paramjest teraz wskaźnikiem.

#include <stdio.h>

void function2(int *param) {
    printf("I've received value %d\n", *param);
    (*param)++;
}

int main(void) {
    int variable = 111;

    function2(&variable);
    printf("variable %d\n", variable);
    return 0;
}

Wynik to

I've received value 111
variable=112

To sprawia, że ​​uważasz, że parametr został przekazany przez referencję. Nie było. Został przekazany przez wartość, przy czym wartość parametru jest adresem. Zwiększono wartość typu int i jest to efekt uboczny, który każe nam myśleć, że było to wywołanie funkcji pass-by-reference.

Wskaźniki - przekazywane przez wartość

Jak możemy pokazać / udowodnić ten fakt? Cóż, może możemy wypróbować pierwszy przykład zmiennych skalarnych, ale zamiast skalara używamy adresów (wskaźników). Zobaczmy, czy to może pomóc.

#include <stdio.h>

void function2(int *param) {
    printf("param's address %d\n", param);
    param = NULL;
}

int main(void) {
    int variable = 111;
    int *ptr = &variable;

    function2(ptr);
    printf("ptr's address %d\n", ptr);
    return 0;
}

W rezultacie oba adresy będą sobie równe (nie martw się o dokładną wartość).

Przykładowy wynik:

param's address -1846583468
ptr's address -1846583468

Moim zdaniem dowodzi to wyraźnie, że wskaźniki są przekazywane według wartości. W przeciwnym razie ptrbyłoby NULLpo wywołaniu funkcji.

Ely
źródło
69

W C przekazywanie przez odwołanie jest symulowane przez przekazanie adresu zmiennej (wskaźnika) i wyrejestrowanie tego adresu w funkcji, aby odczytać lub zapisać aktualną zmienną. Będzie to określane jako „pass-by-reference w stylu C.”

Źródło: www-cs-students.stanford.edu

Tamas Czinege
źródło
50

Ponieważ w powyższym kodzie nie ma przekazu referencyjnego. Używanie wskaźników (takich jak void func(int* p)) to przekazywanie według adresu. To jest przejście przez odniesienie w C ++ (nie będzie działać w C):

void func(int& ref) {ref = 4;}

...
int a;
func(a);
// a is 4 now
Alexander Gessler
źródło
1
Podoba mi się odpowiedź typu pass-by-address . Ma więcej sensu.
Afzaal Ahmad Zeeshan
Adres i odniesienie są w tym kontekście synonimami. Ale możesz użyć tych terminów do rozróżnienia tych dwóch, nie jest to po prostu uczciwe w stosunku do ich pierwotnego znaczenia.
YungGun,
27

Twój przykład działa, ponieważ przekazujesz adres swojej zmiennej do funkcji, która manipuluje jej wartością za pomocą operatora dereferencji .

Chociaż C nie obsługuje referencyjnych typów danych , nadal możesz symulować przekazywanie przez referencję, jawnie przekazując wartości wskaźnika, jak w twoim przykładzie.

Referencyjny typ danych C ++ jest mniej wydajny, ale uważany za bezpieczniejszy niż typ wskaźnika odziedziczony z C. To byłby twój przykład przystosowany do użycia referencji C ++ :

void f(int &j) {
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}
Daniel Vassallo
źródło
3
Ten artykuł Wikipedii dotyczy C ++, a nie C. Referencje istniały przed C ++ i nie zależą od specjalnej składni C ++.
1
@Roger: Dobra uwaga ... Usunąłem wyraźne odniesienie do C ++ z mojej odpowiedzi.
Daniel Vassallo
1
I ten nowy artykuł mówi „odniesienie jest często nazywane wskaźnikiem”, co nie jest dokładnie tym, co mówi twoja odpowiedź.
12

Podajesz wskaźnik (lokalizację adresu) według wartości .

To tak, jakby powiedzieć „oto miejsce z danymi, które chcę, abyś zaktualizował”.

antony.trupe
źródło
6

p jest zmienną wskaźnikową. Jego wartością jest adres i. Kiedy wywołujesz f, przekazujesz wartość p, która jest adresem i.

Anssi
źródło
6

Brak referencji w C, ale p „odnosi się” do i, a ty przekazujesz p według wartości.

Pavel Radzivilovsky
źródło
5

W C wszystko jest przekazywane według wartości. Zastosowanie wskaźników daje nam złudzenie, że mijamy przez odniesienie, ponieważ zmienia się wartość zmiennej. Jeśli jednak wydrukujesz adres zmiennej wskaźnikowej, zobaczysz, że nie ma to wpływu. Kopia o wartości adresu jest przekazywana do funkcji. Poniżej znajduje się fragment tego ilustrujący.

void add_number(int *a) {
    *a = *a + 2;
}

int main(int argc, char *argv[]) {
   int a = 2;

   printf("before pass by reference, a == %i\n", a);
   add_number(&a);
   printf("after  pass by reference, a == %i\n", a);

   printf("before pass by reference, a == %p\n", &a);
   add_number(&a);
   printf("after  pass by reference, a == %p\n", &a);

}

before pass by reference, a == 2
after  pass by reference, a == 4
before pass by reference, a == 0x7fff5cf417ec
after  pass by reference, a == 0x7fff5cf417ec
Arun Suresh
źródło
4

Ponieważ przekazujesz wskaźnik (adres pamięci) do zmiennej p do funkcji f. Innymi słowy, przekazujesz wskaźnik, a nie odniesienie.

Dave
źródło
4

Krótka odpowiedź: Tak, C implementuje przekazywanie parametrów przez referencję za pomocą wskaźników.

Podczas wdrażania przekazywania parametrów projektanci języków programowania stosują trzy różne strategie (lub modele semantyczne): przesyłają dane do podprogramu, odbierają dane z podprogramu lub wykonują oba te zadania. Modele te są powszechnie znane jako odpowiednio w trybie, trybie wyjściowym i trybie wejściowym.

Projektanci języków opracowali kilka modeli w celu wdrożenia tych trzech podstawowych strategii przekazywania parametrów:

Przekazywanie przez wartość (semantyka trybu) Przekazywanie przez wynik (semantyka trybu wyjścia) Przekazywanie przez wartość-wynik (semantyka trybu wejścia) Przekazywanie przez odniesienie (semantyka trybu wprowadzania) Przekazywanie według nazwy (tryb wprowadzania semantyka)

Przekazywanie przez referencję to druga technika przekazywania parametrów w trybie wyjściowym. Zamiast kopiować dane tam iz powrotem między główną procedurą a podprogramem, system wykonawczy wysyła ścieżkę bezpośredniego dostępu do danych dla podprogramu. W tej strategii podprogram ma bezpośredni dostęp do danych, skutecznie współdzieląc dane z główną procedurą. Główną zaletą tej techniki jest to, że jest absolutnie wydajna pod względem czasu i przestrzeni, ponieważ nie ma potrzeby powielania przestrzeni i nie ma operacji kopiowania danych.

Implementacja przekazywania parametrów w C: C implementuje semantykę pass-by-value, a także pass-by-reference (tryb inout), używając wskaźników jako parametrów. Wskaźnik jest wysyłany do podprogramu i żadne rzeczywiste dane w ogóle nie są kopiowane. Ponieważ jednak wskaźnik jest ścieżką dostępu do danych głównej procedury, podprogram może zmieniać dane w głównej procedurze. C przyjął tę metodę z ALGOL68.

Implementacja przekazywania parametrów w C ++: C ++ implementuje również semantykę pass-by-reference (tryb inout) przy użyciu wskaźników, a także specjalnego rodzaju wskaźnika, zwanego typem odniesienia. Wskaźniki typu odniesienia są domyślnie wyłuskiwane w podprogramie, ale ich semantyka jest również przekazywana przez odniesienie.

Zatem kluczową koncepcją jest to, że przekazywanie przez odniesienie implementuje ścieżkę dostępu do danych zamiast kopiowania danych do podprogramu. Ścieżkami dostępu do danych mogą być jawnie wskaźniki dereferencyjne lub wskaźniki auto dereferencyjne (typ odniesienia).

Aby uzyskać więcej informacji, zapoznaj się z książką Koncepcje języków programowania autorstwa Roberta Sebesta, 10 wydanie, rozdział 9.

Francisco Zapata
źródło
3

Nie przekazujesz int przez referencję, przekazujesz wskaźnik do int przez wartość. Inna składnia, to samo znaczenie.

Xan
źródło
1
+1 „Inna składnia, to samo znaczenie.” .. Więc to samo, ponieważ znaczenie ma większe znaczenie niż składnia.
Nie prawda. Wywołując za void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }pomocą int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, drukuje 111 zamiast 500. Jeśli przekazujesz przez referencję, powinien wypisać 500. C nie obsługuje przekazywania parametru przez referencję.
Konfle Dolex
@Konfle, jeśli przekazujesz składniowo przez referencję, ptr = &newvaluezostanie niedozwolony. Bez względu na różnicę, myślę, że wskazujesz, że „to samo znaczenie” nie jest do końca prawdziwe, ponieważ masz również dodatkową funkcjonalność w C (możliwość ponownego przypisania samego „odniesienia”).
xan
Nigdy nie piszemy czegoś takiego, ptr=&newvaluejeśli jest przekazywana przez referencję. Zamiast tego piszemy ptr=newvalueOto przykład w C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }Stanie się wartość parametru przekazanego do func () 500.
Konfle Dolex
W przypadku powyższego komentarza przekazanie param przez odniesienie nie ma sensu. Jeśli jednak parametr jest obiektem zamiast POD, spowoduje to znaczącą różnicę, ponieważ każda zmiana po param = new Class()wewnątrz funkcji nie miałaby wpływu na obiekt wywołujący, jeśli zostanie przekazany przez wartość (wskaźnik). Jeśli paramzostanie przekazany przez odniesienie, zmiany będą widoczne dla dzwoniącego.
Konfle Dolex
3

W C, aby przekazać referencję, używasz operatora adresu, &który powinien być użyty przeciwko zmiennej, ale w twoim przypadku, ponieważ użyłeś zmiennej wskaźnika p, nie musisz poprzedzać jej operatorem adresu. To byłoby prawdziwe, jeśli stosowany &ijako parametr: f(&i).

Możesz również dodać to, aby wyłuskać pi zobaczyć, jak pasuje ta wartość i:

printf("p=%d \n",*p);
t0mm13b
źródło
Dlaczego czułeś potrzebę powtórzenia całego kodu (łącznie z tym blokiem komentarzy ...), aby powiedzieć mu, że powinien dodać printf?
@Neil: Ten błąd został wprowadzony przez edycję @ Williama, teraz go odwrócę. A teraz oczywiste jest, że tommieb ma w większości rację: możesz zastosować i do dowolnego obiektu, nie tylko zmiennych.
2

wskaźniki i referencje to dwie różne kwestie.

Kilka rzeczy, o których nie wspomniałem.

Wskaźnik to adres czegoś. Wskaźnik można przechowywać i kopiować jak każdą inną zmienną. Ma więc rozmiar.

Odniesienie należy postrzegać jako ALIAS czegoś. Nie ma rozmiaru i nie można go przechowywać. MUSI odnosić się do czegoś, tj. nie może być zerowy ani zmieniony. Czasami kompilator musi przechowywać referencję jako wskaźnik, ale jest to szczegół implementacji.

W przypadku odniesień nie występują problemy ze wskaźnikami, takie jak obsługa własności, sprawdzanie wartości zerowej, usuwanie odnośników przy użyciu.

użytkownik2187033
źródło
1

„Przekaż przez referencję” (przy użyciu wskaźników) było w C od samego początku. Jak myślisz, dlaczego tak nie jest?

Maurits Rijk
źródło
7
Ponieważ technicznie nie jest to odniesienie.
Mehrdad Afshari,
7
Przekazanie wartości wskaźnika nie jest tym samym, co przekazanie przez odniesienie. Aktualizacja wartości j( nie *j ) w f()nie ma wpływu na iw main().
John Bode,
6
Jest to semantycznie to samo, co przekazywanie przez referencję, i wystarczy powiedzieć, że przechodzi przez referencję. To prawda, że ​​standard C nie używa terminu „odniesienie”, ale to mnie nie zaskakuje ani nie stanowi problemu. Nie mówimy też standardem na temat SO, nawet jeśli możemy odnosić się do standardu, w przeciwnym razie nikt by nie mówił o wartościach (standard C nie używa tego terminu).
4
@Jim: Dziękujemy za poinformowanie nas, że głosowałeś na komentarz Johna.
1

Myślę, że C faktycznie obsługuje przekazywanie przez odniesienie.

Większość języków wymaga cukru syntaktycznego, aby przekazywał się przez odniesienie zamiast wartości. (Na przykład C ++ wymaga & w deklaracji parametru).

C wymaga również cukru syntaktycznego. Jest * w deklaracji typu parametru i argumencie. Więc * i & jest składnia C przekazywana przez referencję.

Można teraz argumentować, że rzeczywiste przekazywanie przez referencję powinno wymagać tylko składni w deklaracji parametru, a nie po stronie argumentu.

Ale teraz jest C #, który robi wsparcia poprzez przepuszczanie odniesienia i wymaga cukier syntaktyczny na obie parametrów i argumentów stron.

Argument, że C nie przepuszcza by-ref powoduje, że elementy składniowe wyrażające go wykazują podstawową implementację techniczną wcale nie jest argumentem, ponieważ dotyczy to mniej więcej wszystkich implementacji.

Jedynym pozostałym argumentem jest to, że przekazywanie przez ref w C nie jest cechą monolityczną, ale łączy dwie istniejące cechy. (Weź argument argumentu przez &, spodziewaj się, że odniesienie zostanie wpisane przez *.) Na przykład C # wymaga dwóch elementów składniowych, ale nie można ich używać bez siebie.

Jest to oczywiście niebezpieczny argument, ponieważ wiele innych funkcji w językach składa się z innych funkcji. (jak obsługa napisów w C ++)

Johannes
źródło
1

To, co robisz, to przekazywanie wartości, a nie przekazywanie referencji. Ponieważ wysyłasz wartość zmiennej „p” do funkcji „f” (głównie jako f (p);)

Będzie wyglądał ten sam program w C z przekazywaniem przez referencję (!!! ten program daje 2 błędy, ponieważ przekazywanie przez odwołanie nie jest obsługiwane w C)

#include <stdio.h>

void f(int &j) {    //j is reference variable to i same as int &j = i
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}

Wynik:-

3:12: błąd: oczekiwany ”;„, ”,„ lub ”) przed tokenem„ & ”
             void f (int & j);
                        ^
9: 3: ostrzeżenie: niejawna deklaracja funkcji „f”
               fa);
               ^
Sunay
źródło