Co to jest makro? Różnica między makrem a funkcją?

16

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?

Edoardo Sorgentone
źródło
15
Masz na myśli jakiś konkretny język programowania?
mkrieger1
15
Musisz być bardziej szczegółowy; makro odnosi się do trzech bardzo różnych pojęć, makr tekstowych / makr preprocesora w stylu C, makr Lisp i makr aplikacji.
Chrylis

Odpowiedzi:

7

Uwaga

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.

Nimesh Neema
źródło
9
Co z wprowadzaniem funkcji?
Nathan Cooper
1
W języku C & co makro może być również wywołaniem funkcji. Tak więc rozróżnienie między nimi nie ma większego sensu.
Sombrero Chicken
5
Makra w stylu C nie są samodzielne - nie wprowadzają zakresu leksykalnego i mają nieograniczony dostęp do nazw lokalnych w zakresie w miejscu podstawienia.
Bezużyteczne
@Sombrero Chicken: Może mógłbyś pokazać przykład? Możesz mieć makra, które wywołują funkcje, ale (AFAIK) makro nie jest wywołaniem funkcji.
jamesqf
3
W tej odpowiedzi jest wiele rzeczy, które wprowadzają w błąd. Wspomniane przykłady absolutnie nie są w żaden sposób sytuacjami, w których makra są „preferowane” - wręcz przeciwnie. Makr nie należy używać do tych rzeczy, chyba że istnieje bardzo dobry powód. Wydajność na ogół nie jest dobrym uzasadnieniem dla używania makr. Z powodów wymienionych w innych komentarzach uzasadnienie użycia makr w ten sposób może skutkować podatnym na błędy kodem z wszelkimi niezamierzonymi konsekwencjami, szczególnie w większej bazie kodu.
Ben Cottrell,
65

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 forjest 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 .

Jörg W Mittag
źródło
1
„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 napisany” - naprawdę? Do jakich języków się odwołujesz? AFAICS, co do zasady jest to możliwe tylko w języku zależnym od typu. W Haskell makra TH są bezpieczne tylko pod względem składniowym, ale sprawdzanie typów jest uruchamiane później. (Pod względem rodzaju dają jeszcze mniej gwarancji niż szablony C ++).
lewo około
1
Poza skryptami aplikacji, nie sądzę, żeby trzy podane przez ciebie przykłady były zupełnie inne. Wszyscy dokonują pewnego rodzaju zamiany w programie, w przeciwieństwie do przeskakiwania do udostępnionego fragmentu kodu podczas jego wykonywania.
IMSoP
Być może możesz rozszerzyć wzmiankę o makrach C o ogólną koncepcję języków makr, takich jak M4, ponieważ dla C jest to po prostu, że specyfikacja definiuje pomocniczy język makr CPP i standaryzuje jego użycie z C.
JoL
1
Ogólny motyw wydaje się polegać na tym, że makra generują kod, który ma być interpretowany jako część kodu źródłowego większego programu, a nie uruchamiany po uruchomieniu programu.
jpmc26
1
@ jpmc26 Powiedziałbym, że ogólnym tematem jest podstawienie, w którym robisz drobną rzecz i rozwija się do wielkiej rzeczy. Język makr M4 nie jest specjalnie przeznaczony do kodu. Możesz go użyć do wygenerowania dowolnego tekstu. Są też makra klawiaturowe, takie jak wspomniana odpowiedź. Naciskasz 1 lub 2 klawisze, a one rozszerzają się na większą liczbę naciśnięć klawiszy. Makra vim też takie są. Zapisz dużą sekwencję poleceń trybu normalnego pod jednym klawiszem, a wywołujesz tę sekwencję, uruchamiając polecenie trybu normalnego, które ją wykonuje.
JoL
2

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 + 2jakiegoś parametru xnastępuje przed kompilacją i może powodować nieoczekiwane zachowanie x * 3( 7io 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 .

Joop Eggen
źródło
2

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.

Ashton Wiersdorf
źródło
0

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.

IMSoP
źródło
0

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;
}
david72
źródło