Nie rozumiem dobrze koncepcji makro. Co to jest makro? Nie rozumiem, czym różni się od funkcji? Zarówno funkcja, jak i makro zawierają blok kodu. Czym różni się makro i funkcja?
Nie rozumiem dobrze koncepcji makro. Co to jest makro? Nie rozumiem, czym różni się od funkcji? Zarówno funkcja, jak i makro zawierają blok kodu. Czym różni się makro i funkcja?
Odpowiedzi:
Chciałbym dodać następujące wyjaśnienie po zaobserwowaniu polaryzującego modelu głosowania w tej odpowiedzi.
Odpowiedź nie została napisana z myślą o technicznej dokładności i szerokim uogólnieniu. Była to pokorna próba wyjaśnienia nowemu programistowi, w prostym języku, różnicy między makrami i funkcjami, bez prób bycia kompletnym lub dokładnym (w rzeczywistości wcale nie jest to dokładne). Podczas programowania odpowiedzi miałem na myśli język programowania C, ale przepraszam, że nie wspomniałem o tym wyraźnie i nie spowodowałem (potencjalnego) zamieszania.
Naprawdę uważam odpowiedź udzieloną przez Jörga W. Mittaga . Wnikliwe było przeczytanie go (o ile mniej wiem) i głosowałem za nim wkrótce po opublikowaniu. Właśnie zacząłem na Software Engineering Stack Exchange, a dotychczasowe doświadczenia i dyskusje były naprawdę wnikliwe.
Pozostawię tę odpowiedź tutaj, ponieważ może być pomocna dla innych początkujących twórców oprogramowania, próbujących zrozumieć koncepcję bez zagłębiania się w techniczne dokładności.
Zarówno makro, jak i funkcja reprezentują niezależną jednostkę kodu. Oba są narzędziami, które pomagają w modułowej konstrukcji programu. Z punktu widzenia programisty, który pisze kod źródłowy, wyglądają dość podobnie. Różnią się one jednak sposobem obsługi podczas cyklu życia programu.
Makro jest definiowane raz i jest używane w wielu miejscach w programie. Makro jest rozszerzane w linii podczas etapu wstępnego przetwarzania. Zatem technicznie nie pozostaje odrębnym bytem po skompilowaniu kodu źródłowego. Instrukcje w definicji makra stają się częścią instrukcji programu, podobnie jak inne instrukcje.
Motywem pisania makra jest ułatwienie programistom pisania i zarządzania kodem źródłowym. Makra są na ogół pożądane do prostszych zadań, w których napisanie pełnoprawnej funkcji stanowiłoby narzut wydajności / karę działania. Przykłady sytuacji, w których makro jest lepsze niż funkcja, to:
Korzystanie ze stałych wartości (takich jak wartości matematyczne lub naukowe) lub niektórych parametrów specyficznych dla programu.
Drukowanie komunikatów w dzienniku lub obsługa asercji.
Wykonywanie prostych obliczeń lub kontroli stanu.
Korzystając z makra, łatwo jest wprowadzać zmiany / poprawki w jednym miejscu, które są natychmiast dostępne wszędzie tam, gdzie makro jest używane w programie. Aby zmiany odniosły skutek, wymagana jest prosta rekompilacja programu.
Z drugiej strony kod funkcji jest kompilowany jako osobna jednostka w programie i jest ładowany do pamięci podczas wykonywania programu tylko wtedy, gdy jest wymagany. Kod funkcji zachowuje swoją niezależną tożsamość od reszty programu. Załadowany kod zostanie ponownie użyty, jeśli funkcja zostanie wywołana więcej niż jeden raz. Kiedy w uruchomionym programie napotkane jest wywołanie funkcji, sterowanie jest przekazywane do niego przez podsystem wykonawczy i kontekst uruchomionego programu (adres instrukcji powrotu) zostaje zachowany.
Jednak podczas wywoływania funkcji należy się liczyć z niewielkim spadkiem wydajności (przełączanie kontekstu, zachowanie adresu zwrotnego instrukcji programu głównego, przekazywanie parametrów i obsługa wartości zwracanych itp.). Dlatego użycie funkcji jest pożądane tylko w przypadku złożonych bloków kodu (w stosunku do makr, które obsługują prostsze przypadki).
Z doświadczeniem programista podejmuje rozsądną decyzję, czy fragment kodu będzie dobrze pasował jako makro lub funkcja w ogólnej architekturze programu.
Niestety, istnieje wiele różnych zastosowań terminu „makro” w programowaniu.
W rodzinie języków Lisp i językach przez nich inspirowanych, a także w wielu współczesnych językach funkcjonalnych lub inspirowanych funkcjonalnie, takich jak Scala i Haskell, a także w niektórych imperatywnych językach, takich jak Boo, makro jest fragmentem kodu uruchamianym w czasie kompilacji (lub przynajmniej przed środowiskiem uruchomieniowym dla implementacji bez kompilatora) i może przekształcić abstrakcyjne drzewo składni (lub jakikolwiek odpowiednik w danym języku, np. w Lisp, byłoby to wyrażenia S) w coś innego podczas kompilacji. Na przykład w wielu implementacjach schematu for
jest makrem, które rozwija się w wiele wywołań do treści. W językach o typie statycznym makra często są bezpieczne pod względem typu, tzn. Nie mogą wytworzyć kodu, który nie jest dobrze wpisany.
W rodzinie języków C makra bardziej przypominają zastępowanie tekstu. Co oznacza również, że mogą produkować kod, który nie jest dobrze wpisany, a nawet nie jest zgodny pod względem składniowym.
W makro-asemblerach „makra” odnoszą się do „instrukcji wirtualnych”, tj. Instrukcji, których procesor nie obsługuje natywnie, ale które są przydatne, a więc asembler pozwala na użycie tych instrukcji i rozszerzy je na wiele instrukcji zrozumiałych przez CPU .
W skryptach aplikacji „makro” odnosi się do szeregu działań, które użytkownik może „nagrać” i „odtworzyć”.
Wszystkie są w pewnym sensie rodzajem kodu wykonywalnego, co oznacza, że w pewnym sensie mogą być postrzegane jako funkcje. Jednak na przykład w przypadku makr Lisp ich dane wejściowe i wyjściowe są fragmentami programu. W przypadku C ich wejściem i wyjściem są tokeny. Pierwsze trzy mają również bardzo ważne rozróżnienie, że są wykonywane w czasie kompilacji . W rzeczywistości makra preprocesora C, jak sama nazwa wskazuje, są faktycznie wykonywane, zanim kod dotrze nawet do kompilatora .
W rodzinie języków C definicja makra , polecenie preprocesora, określa sparametryzowany szablon kodu, który jest zastępowany podczas wywołania makra bez kompilacji w definicji. Oznacza to, że wszystkie wolne zmienne powinny być powiązane w kontekście wywołania makra. Argumenty parametrów z efektem ubocznym, takim jak, i++
mogą być powtarzane przez użycie liczby mnogiej parametru. Tekstowe podstawienie argumentu 1 + 2
jakiegoś parametru x
następuje przed kompilacją i może powodować nieoczekiwane zachowanie x * 3
( 7
io 9
). Błąd w treści makra definicji makra pojawi się tylko podczas kompilacji podczas wywołania makra.
W definicji funkcji określa kod z wolnymi zmiennymi związanymi z kontekstem ciała funkcji; nie wywołanie funkcji .
Makro, jednak pozornie ujemne, zapewnia dostęp do wywołania, numeru linii i pliku źródłowego, argument jako ciąg znaków .
Mówiąc nieco bardziej abstrakcyjnie, makro ma składniać tak jak funkcja danych. Funkcja (w skrócie) zawiera pewną transformację danych. Bierze argumenty jako dane ewaluowane, wykonuje na nich pewne operacje i zwraca wynik, który jest również tylko danymi.
Makro w przeciwieństwie do tego wymaga pewnej nieocenionej składni i działa na tym. W przypadku języków podobnych do C składnia występuje na poziomie tokena. W przypadku języków z makrami podobnymi do LISP otrzymują one składnię przedstawioną jako AST. Makro musi zwrócić nowy fragment składni.
Makro ogólnie odnosi się do czegoś, co jest rozszerzony w miejscu , zastępując makro „Call” podczas kompilacji lub wstępne przetwarzanie z poszczególnych instrukcji w języku docelowym. W czasie wykonywania zazwyczaj nie ma wskazania, gdzie makro zaczyna się i kończy.
Różni się to od podprogramu , który jest fragmentem kodu wielokrotnego użytku, który jest umieszczony osobno w pamięci, do którego kontrola jest przekazywana w czasie wykonywania . „Funkcje”, „procedury” i „metody” w większości języków programowania należą do tej kategorii.
Jak dyskutuje odpowiedź Jörga W. Mittaga , dokładne szczegóły różnią się w zależności od języka: w niektórych, takich jak C, makro dokonuje podstawienia tekstu w kodzie źródłowym; w niektórych, takich jak Lisp, wykonuje manipulację formą pośrednią, taką jak abstrakcyjne drzewo składniowe. Istnieją również szare obszary: niektóre języki mają notację dla „funkcji wbudowanych”, które są zdefiniowane jak funkcja, ale rozszerzone do skompilowanego programu jak makro.
Większość języków zachęca programistów do samodzielnego uzasadnienia każdego podprogramu, definiowania kontraktów typu dla danych wejściowych i wyjściowych oraz ukrywania innych informacji o kodzie wywołującym. Wywołanie podprogramu często ma wpływ na wydajność, którego makra nie ponoszą, a makra mogą być w stanie manipulować programem w sposób, w jaki podprogram nie może tego zrobić. Makra można zatem uznać za „niższy poziom” niż podprogramy, ponieważ działają one w sposób mniej abstrakcyjny.
Makro jest wykonywane podczas kompilacji, a funkcja jest wykonywana w czasie wykonywania.
Przykład:
#include <stdio.h>
#define macro_sum(x,y) (x+y)
int func_sum(x,y) {
return x+y;
}
int main(void) {
printf("%d\n", macro_sum(2,3));
printf("%d\n", func_sum(2,3));
return 0;
}
Podczas kompilacji kod jest zmieniany na:
#include <stdio.h>
int func_sum(x,y) {
return x+y;
}
int main(void) {
printf("%d\n", (2+3));
printf("%d\n", func_sum(2,3));
return 0;
}