2009-10-24 14 views
11

czytałem jakiś kod napisany w C tego wieczoru, a na górze plik był funkcja podobny makro HASH:Kiedy używać makr funkcyjnych, jak w C

#define HASH(fp) (((unsigned long)fp)%NHASH) 

To mnie opuścił zastanawiasz , dlaczego ktoś zdecydowałby się zaimplementować w ten sposób funkcję używając makra funkcyjnego, zamiast implementować jako zwykłą waniliową funkcję C? Jakie są zalety i wady każdej implementacji?

Wielkie dzięki!

+0

Ponieważ funkcje hashowe idą, to jest dość słabe - ale potem wydaje się, że dane, z którymi ma do czynienia, są również bardzo proste. –

+0

W rzeczywistości nie jest zbyt jasny, aby zrobić to "makro". Funkcja [wbudowana funkcja statyczna] (http://stackoverflow.com/questions/7762731/whats-the-difference-between-static-and-static-inline-function) jest prawdopodobnie bardziej odpowiednia. * C * ma wbudowany od 1999 r. I 'gcc' jeszcze wcześniej. To pytanie zostało przytoczone jako powielanie [użycie definicji kfifo] (http://stackoverflow.com/questions/16117304/why-is-kfifo-h-so-full-of-define-statements). Jeśli ** MS-Windows ** jest powodem, dla którego ludzie mówią, że to makro jest uzasadnione, w jaki sposób są one powiązane? –

Odpowiedz

3

Z jednej strony makra są złe, ponieważ zostały wykonane przez preprocesor, który nie rozumie niczego na temat języka i zastępuje tekst. Zwykle mają wiele ograniczeń. Nie widzę żadnego z powyższych, ale zazwyczaj makra są brzydkimi rozwiązaniami.

Z drugiej strony, są one czasami nawet szybsze niż metoda static inline. Intensywnie optymalizowałem krótki program i odkryłem, że wywoływanie metody static inline zajmuje około dwa razy więcej czasu (tylko narzut, a nie rzeczywisty korpus funkcji) w porównaniu z makrem.

+0

Dobrze wiedzieć. Dziękuję Ci! Po prostu ciekawy, jakiego narzędzia używałeś do optymalizacji swojego kodu C? Nie próbowałem wcześniej optymalizacji i chciałbym spróbować. – Ralph

+0

Narzędzie? Vim, 'gprof' i czcigodne polecenie' time'. 'gprof' to całkiem niezły profiler z niskim obciążeniem, ale musisz uważać, ponieważ nie śledzi on makr (nie widzi ich, inny wady makr). – pavpanchekha

+0

Z jakiej wersji używanego kompilatora korzystasz z poziomu optymalizacji? –

0

Do tworzenia funkcji wbudowanych można użyć funkcji C Preprocessor. W twoim przykładzie kod będzie wyglądał tak, jakby wywoływał funkcję HASH, ale jest to po prostu kod wbudowany.

Korzyści z wykonywania funkcji makro zostały wyeliminowane, gdy C++ wprowadziło funkcje inline. Wiele starszych API, takich jak MFC i ATL, nadal wykorzystuje funkcje makr do wykonywania trików preprocesora, ale po prostu pozostawia kod zawiłe i trudniejsze do odczytania.

1

Twój przykład nie jest to funkcja w ogóle,

 
#define HASH(fp) (((unsigned long)fp)%NHASH) 
// this is a cast ^^^^^^^^^^^^^^^ 
// this is your value 'fp'  ^^ 
// this is a MOD operation   ^^^^^^ 

myślałem, że to tylko sposób pisania kodu bardziej czytelny z odlewu i mod opration zawinięte w jednym makro „HASH(fp)


teraz, jeśli zdecydujesz się napisać funkcję do tego, to prawdopodobnie wyglądać,

 
int hashThis(int fp) 
{ 
    return ((fp)%NHASH); 
} 

Dość overkill dla funkcji, jak to,

  • wprowadza alarmowego
  • wprowadza nazywają-stack ustawień i przywrócenie
+0

bardzo dobry punkt! – Ralph

24

Makra jak to uniknąć napowietrznej wywołania funkcji.

To może nie wydawać się dużo.Ale w swoim przykładzie makro zamienia 1-2 instrukcją w języku maszynowym, w zależności od procesora:

  • uzyskać wartość fp z pamięci i umieścić go w rejestrze
  • przyjąć wartość w rejestrze , czy moduł (%) obliczenie stałej wartości i pozostawić, że w tym samym rejestrze

natomiast odpowiednik funkcji byłoby dużo więcej instrukcji języka maszynowego, ogólnie coś

  • stick wartość fp na stosie
  • wywołania funkcji, która stawia również następny (powrót) zajęcia na stosie
  • Może zbudować ramkę stosu wewnątrz funkcji, w zależności od architektury procesora i konwencji ABI
  • Get wartość fp ze stosu i umieścić go w rejestrze
  • przyjąć wartość w rejestrze, czy moduł (%) obliczenie stałej wartości i pozostawić, że w tym samym rejestrze
  • Może podjąć wartość z rejestru i umieść z powrotem na stosie, w zależności od procesora i ABI
  • Jeśli rama stos został zbudowany, odpoczynek to
  • Pop adres powrotu ze stosu i wznowić wykonywanie instrukcji tam

Dużo więcej kodu, eh? Jeśli robisz coś takiego, jak renderowanie każdego z dziesiątków tysięcy pikseli w oknie w GUI, rzeczy działają o wiele szybciej, jeśli używasz tego makra.

Osobiście wolę używać inline C++ jako bardziej czytelnego i mniej podatnego na błędy, ale inlines są również bardziej wskazówką dla kompilatora, którego nie trzeba wykonywać. Makra preprocesora to młotek, z którym kompilator nie może się spierać.

+0

Wow, masz rację, to więcej kodu do zaimplementowania go jako funkcji! – Ralph

+5

Do tego służą wbudowane funkcje! :) – LiraNuna

+0

LiraNuna: funkcje inline nie są częścią ANSI C90, co obsługuje większość kompilatorów. Są tylko w C99 lub C++ –

3

Najczęstszym (i najczęściej błędnym) powodem, dla którego ludzie używają makr (w "zwykłym starym C") jest argument efektywności. Używanie ich dla wydajności jest w porządku, jeśli faktycznie profilowałeś swój kod i optymalizowałeś prawdziwe wąskie gardło (lub piszesz funkcję biblioteki, która kiedyś może być wąskim gardłem dla kogoś). Ale większość ludzi, którzy upierają się przy ich używaniu, nie przeanalizowała niczego i tworzy zamieszanie tam, gdzie nie przynosi żadnych korzyści.

Makr może być również użyty do niektórych przydatnych podstawień typu "odtwórz i zastąp", do których nie jest w stanie normalnie używać języka C.

Niektóre problemy, które miałem w utrzymywaniu kodu napisanego przez osoby nadużywające makro, to fakt, że makra mogą wyglądać zupełnie jak funkcje, ale nie pojawiają się w tabeli symboli, więc może być bardzo denerwujące, śledząc je z powrotem do ich początków. rozległe zestawy kodów (gdzie to jest zdefiniowane ?!). Pisanie makr w ALL CAPS jest oczywiście pomocne dla przyszłych czytelników.

Jeśli są one czymś więcej niż prostymi podstawieniami, mogą również powodować pewne zamieszanie, jeśli trzeba przejść przez nie przez debugger.

+0

możesz podać przykład jednego z tych zastępstw typu "znajdź i zamień", które można wykonać tylko w przypadku makr, o których mówisz? – Ralph

+3

Linie generujące kody, takie jak: #define COLORVAL (color, val) int color ## _ number = val; Nie zrobiłbym tego zbyt wiele, ale jeśli potrzebujesz linii na czerwony, niebieski, zielony, ..., czarny, to może pomóc. – JXG

+0

p.212 "Unix Hater's Handbook" (p.248 w .pdf http://www.simson.net/ref/ugh.pdf, http://www.cs.washington.edu/homes/ weise/uhh-download.html) omawia pewne powiązane trudności. – Kilo

16

Jedną ważną zaletą implementacji opartej na makrach jest to, że nie jest ona powiązana z żadnym konkretnym typem parametru. Makro funkcyjne w C działa, pod wieloma względami, jako funkcja szablonu w C++ (szablony w C++ narodziły się jako "bardziej cywilizowane" makra, BTW). W tym konkretnym przypadku argument makro nie ma konkretnego rodzaju.Może to być absolutnie wszystko, co można przekształcić na typ unsigned long. Na przykład, jeśli użytkownik tego wymaga (i jeśli są gotowi zaakceptować konsekwencje zdefiniowane przez implementację), mogą przekazywać typy wskaźników do tego makra.

W każdym razie, muszę przyznać, że to makro nie jest najlepszym przykładem elastyczności makr niezależnych od typu, ale ogólnie rzecz biorąc, elastyczność jest bardzo przydatna. Ponownie, gdy pewna funkcjonalność jest zaimplementowana przez funkcję, jest ona ograniczona do określonych typów parametrów. W wielu przypadkach w celu zastosowania podobnej operacji do różnych typów konieczne jest zapewnienie kilku funkcji z różnymi typami parametrów (i różnymi nazwami, ponieważ jest to C), podczas gdy to samo można zrobić za pomocą tylko jednego makro funkcyjnego. Na przykład, makro

#define ABS(x) ((x) >= 0 ? (x) : -(x)) 

współpracuje ze wszystkimi typami arytmetycznych, natomiast realizacja funkcji opartych musi dostarczyć sporo z nich (jestem sugerując standardowego abs, labs, llabs i fabs). (Tak, zdaję sobie sprawę z tradycyjnie wspomnianych niebezpieczeństw związanych z takim makro.)

Makra nie są idealne, ale popularna maksyma dotycząca "makr funkcyjnych, które nie są już potrzebne z powodu funkcji wbudowanych" jest po prostu nonsensem . Aby w pełni zastąpić makra funkcyjne, C będzie potrzebował szablonów funkcji (jak w C++) lub przynajmniej przeciążenia funkcji (jak w C++). Bez tego makra funkcyjne są i nadal będą niezwykle użytecznym narzędziem głównego nurtu w C.