skip to Main Content

Wariacje na temat zamiany miejscami dwóch wartości

Zamiana dwóch liczb (a=5 b=89) miejscami, że (a=89 b=5), jest prostą rzeczą do zaprogramowania. Ale nawet tak prostą, rzeczy można skomplikować, do takiego stopnia, że wymagane jest wprowadzenie komentarzy.

Algorytm jest prosty:

  1. Utwórz zmienną pomocniczą tmp
  2. Przypisz do tmp wartość a
  3. Przypisz do a wartość b
  4. Przypisz do b wartość tmp
W języku C można wykorzystać funkcję (z koniecznym wykorzystaniem wskaźników):
Jednak powyższe rozwiązanie ma dwie wady:

  1. Funkcja nie działa dla każdego typu danych
  2. Wywołanie funkcji jest spowolnione, ponieważ jest funkcją. (tzw. koszt wywołania funkcji, związany z assemblerową instrukcją call)
Sposób na rozwiązanie tego problemu jest wykorzystanie makr. I to nie byle jakich makr. Kod pochodzi z biblioteki standardowej (dołączanej do kompilatora) glibc, zwianej bezpośrednio z GCC. (a dokładnej pliku funkcji Quicksort – /stdlib/qsort.c)
Przypomnijmy, że makra to dosyć chamskie zastąpienie tekstu dlatego takie użycie makra (wyświetlenie hello world – pod warunkiem że funkcja display_hello zwróci wartość true):
Zostanie rozwinięte do postaci:
Co nie do końca jest o co nam chodziło, ponieważ słowo ‚world’ zawsze zostanie wyświetlone.

Ten boczny wywód na temat makr, jest ważny dla zrozumienia dlaczego występuje pętla do{…]while(0). Taka pętla wykona się zawsze jeden raz ponieważ  warunek sprawdzający jest zawsze fałszywy. Pętla posiada kilka ważnych cech dla działania marka

  1. Pozwala uniknąć błędu z if’em który został wcześniej opisany
  2. Pozwala na zadeklarowanie zmiennych w akurat tym miejscu programu
  3. Dodaje wartość czysto stylistyczną wywołanie makra musi się kończyć średnikiem, więc makro wygląda jakby było normalną funkcją.
Backslashe ” są używane wtedy gdy makro zawiera się w więcej niż jedną linijkę.
Dla przypomnienia słowo kluczowe register przed zmienną służy do powiedzenia kompilatorowi że dana zmienna będzie często wykorzystywana i aby tą zmienną przechowywał w rejestrze procesora. Tak naprawdę to tylko sugestia, kompilator może to słowo kluczowe zupełnie zignorować. Niezależnie od tego czy kompilator słowo register bierze pod uwagę czy nie, z takiej zmiennej nie można uzyskać adresu zmiennej, ponieważ zgodnie z założeniami zmienna nie jest przechowywana w pamięci komputera.

Ciekawym trickiem wykorzystanym w makrze jest utworzenie kolejnych zmiennych o nazwach __a  i __b ponieważ to zapobiega wpadnięciu w pułapkę kiedy do makra poda się na przykład SWAP(pa++,pb++,sizeof(char)) wtedy wskaźnik może zostać przysunięty nie tylko jeden raz.

Makro operuje na wskaźnikach typu char *, dla zmiennych większych niż char jest potrzebne rzutowanie w dół. Przykładowe wykorzystanie makra SWAP dla dwóch zmiennych int:

Druga pętla do{…}while(–__size > 0); zamienia bajt po bajcie. Czyli przykładowo dla zmiennej typu int potrzebne jest wykonanie 4 zamian ponieważ rozmiar int’a w bajtach to 4.

Jak widać stworzenie wydajnej i działającej na wszystkich typach funkcji w języku C jest nieco skomplikowane.

W językach wysokiego poziomu, np Pythonie wystarczy jedna linijka kodu:

Powyższa linijka działa dla każdego typu zmiennych od liczb całkowitych, przez liczby zmiennoprzecinkowych po zmienne typu string

Paweł Sołtysiak

Programista, domowy kucharz i "amator amerykańskiej polityki".
Zbieram informacje z całej sieci, po odrzuceniu chwastów i dodaniu swojej opinii publikuje na blogu.

  • Anonymous

    zamiast używać makr można dodać słowo kluczowe inline przed nazwą funkcji , wtedy funkcja nie jest rozwijana i call nie jest wykonywane, dziala bardzo podobnie do makr, tyle że makrami zajmuje sie preprocesor, a inline funkcjami kompilator.
    poza tym, mozna zrobic zeby funkcja dzialala dla wszystkich typow zmiennych. jest takie cos jak szablony funkcji. oczywiscie nie w gołym ansi c. tylko w c++
    np
    template
    inline void swap(A *a,A *b){
    A tmp=*b;
    *b=*a;
    *a=tmp;
    return;
    }
    pozdro

  • s/hamskie/chamskie

    Bardzo ciekawy wpis, po raz pierwszy widzę tak dokładne i zrozumiałe omówienie makra SWAP. ;]

    @Anonimowy: Inline jest według standardu jedynie sugestią dla kompilatora, to, czy ciało danej funkcji zostanie włączone w miejscu wywołania, czy zapisane jako funkcja zależy tylko od jego wewnętrznych mechanizmów.

Back To Top