Kiedy tak naprawdę wskaźniki są przydatne?

Witam naszła mnie taka myśli co o tym sądzicie?

Skoro w c++ wskaźnik ma także swój adres w pamięci, to na jakiej zasadzie wartość wskaźnika, czyli adres innej zmiennej jest odczytywany szybciej…

no bo jeśli napiszemy powiedzmy:

int a = 4; int *wsk = &a;

a następnie będę chciał wyłuskać wartość:

cout << *wsk;

to przeciez najpierw trzeba mimo wszystko znaleźć wskaźnik wsk po etykiecie, a nie adresie? więc wykonało by się to wolno.

bo przecież wskaźnika wsk nie mamy podanego po adresie, ale po etykiecie.

czyli szukamy go tak samo wolno jak przy wypisaniu

cout << a;

■■■? ;p

Szybciej od czego? Oraz gdzie tak przeczytałeś? Nie okroiłeś przypadkiem kontekstu? Np. przy przekazywaniu do funkcji dużego obiektu klasy robi się to przez wskaźnik, bo zwyczajnie czas kopiowania wskaźnika jest szybszy niż czas kopiowania dużej struktury.

czyli jedynie szybkość zawdzięczamy temu faktu, że szybciej się kopiuje wskaźnik niżeli dużą strukturę / klasę?

no ale w takim razie

function test(int &wsk);

function test2(int wsk);

przy przesyłaniu zwykłych zmiennych według tego co napisałem w temacie głównym funkcja test w cale nie jest szybsza od test2? bo mimo wszystko trzeba najpierw znaleźć w pamięci komputera przesyłaną zmienną wsk, aby odczytać z niej adres, a dopiero wtedy można skorzystać z adresu ?

kod wyglądający tak:

// my first pointer

#include 

using namespace std;


int main ()

{

  int firstvalue, secondvalue;

  int * mypointer;


  mypointer = &firstvalue;

  *mypointer = 10;

  mypointer = &secondvalue;

  *mypointer = 20;

  cout << "firstvalue is " << firstvalue << endl;

  cout << "secondvalue is " << secondvalue << endl;

  return 0;

}

też w cale nie przyspiesza zmiany wartości? dobrze rozumiem?

Nie jestem może najbardziej kompetentną osobą to wypowiadania się na ten temat, ale z tego co mi się wydaje: w przypadku zmiennych o wielkości mniejszej lub równej wielkości wskaźnika (a przynajmniej dla takich typów wbudowanych) różnice wydajnościowe w przesyłaniu przez wartość/wskaźnik/referencję są marginalne/zerowe.

Właściwie to nawet jest chyba strata?

Oczywiście dereferencja wskaźnika wprowadza narzut. Z drugiej strony może się opłacać wprowadzać ten narzut gdy przesyłane są większe dane, bo zmniejsza się koszt kopiowania. Typy podstawowe przesyłane są raczej przez wartość.

Armon , proszę zapoznaj się z tą stroną oraz tym tematem, a następnie popraw tytuł tematu, używając przycisku ac7a4cd89050aa6e.gif

@Armon: etykieta to też adres. Ogólnie różnorodność trybów adresowania sprawia, że różnic w wydajności między podanymi przez Ciebie przykładami (zarówno z pierwszego jak i trzeciego posta) przeważnie nie ma. Rację ma nr47 pisząc, że w przypadku zmiennych o wielkości mniejszej lub równej wielkości wskaźnika nie ma różnicy przy wywołaniach funkcji*. Nie jest prawdą, że dereferencja wskaźnika wprowadza narzut: x86 i amd64 (a to są zapewne najbardziej dla nas interesujące architektury) radzą sobie z pojedynczą dereferencją w tym samym cyklu, w którym wykonywana jest operacja. Bo to architektury CISC.

Zresztą nawet niewinne “int i = 4;” de facto tworzy wskaźnik: ten wskaźnik to i. Wartość znajduje się gdzieś w pamięci (w tym wypadku na stosie) i w kodzie maszynowym mamy do czynienia z odwołaniem do komórki pamięci, której nadaliśmy etykietę i w kodzie C/C++. Dodanie do tego “int *wsk = &i” w większości przypadków nie powoduje, że wskaźnik spowolni cokolwiek. Większość kompilatorów będzie w stanie zoptymalizować kod tak, żeby różnicy nie było wcale.

Problemy mogą się pojawić w bardziej złożonych konstrukcjach, np. jeśli przez to “i” przemnożysz jakąś wartość i użyjesz do indeksowania tablicy. Jeśli operujesz bezpośrednio na “i”, kompilator może się domyśleć, że nie ulega zmianie (jeśli tak w istocie jest) i potraktować mnożenie jako złożony tryb adresowania pamięci (np. zamienić mnożenie i adresowanie na “mov ecx, [esi + ebx * 4]”). Przy dodatkowym poziomie odwołania może się już tego nie domyśleć (bo analiza kodu do optymalizacji kiedyś musi się zakończyć).

Wszystko zależy od konkretnego kodu oraz używanej architektury i dywagacje teoretyczne nigdy nie dadzą konkretnej odpowiedzi. Takie jak tutaj zastanawianie się na poziomie teoretycznym “co jest lepsze” w kontekście adresowania w ogóle nie ma sensu, bo ignoruje masę oboczności przy dostępie do pamięci (chybienie cache, różnej maści stalle przy pracy na urządzeniu wielordzeniowym albo z rozłącznymi rejestrami różnych jednostek logicznych).

* choć wiele też zależy od sposobu wołania funkcji (czy to stdcall, fastcall czy pascal) i liczby parametrów

@Ryan Dziękuje Ci serdecznie za poświęcony czas na tak wyczerpującą odpowiedź :slight_smile: