Drobne programowanie

Kaczuś zaprasza do opowieści o algorytmach, językach programowania i strukturach danych

Na stronie stosowane są pliki cookies. Więcej na podstronie.
odsłon: 1967

Mikro tekst o makrach

Nie lubimy makr, ale jak napisałem w starszym tekście, czasami są one niezbędne, a przynajmniej przydatne. Szczególnie jeśli piszemy w języku C, gdzie nie mamy lepszych narzędzi do tworzenia szablonów generujących kod podczas kompilacji, który jest zależny od aktualnych potrzeb. Ponieważ natknąłem się w różnych miejscach na to, że potrzebne są wyjaśnienia, toteż postaram się zamieścić krótki tekst opisujący zagadnienie oraz podam przykład wraz z objaśnieniem jak i do czego jest to wykorzystywane w realnym kodzie.

Ogólnie makra tworzymy używając schematu:
#define co_definiujemy  jak_definiujemy
widzimy 3 bloki:
  • słowo kluczowe #define
  • co_definiujemy, czyli schemat bloku, który po znalezieniu zostanie zastapiony
  • jak_definiujemy, czyli schemat bloku, który podczas prekompilacji zastąpi znaleziony w kodzie wzorzec co_definiujemy.
Należy też jednak zwrócic uwagę na niebezpieczeństwa związane z makrami (o czym bedzie dalej)

Blok określający wzorzec definicji.

Blok ten może być zdefiniowany, jako etykieta, lub jako makroinstrukcja.
Przypadek etykiety jest dość prosty, np.:
#define BUFOR_SIZE 256
wszędzie, gdzie użyjemy BUFOR_SIZE kompilator podczas prekompilacji wstawi nam 256.
Najczęściej podawany przykład makroinstrukcji:
#define MY_MAX(x, y) (((x)>(y))?(x):(y))
Tak więc gdy dostaniemy kod:
z = MY_MAX(4, 5);
to zmienna z otrzyma wartość 5, gdyż zamiast MY_MAX, zostanie wstawiona instrukcja porównująca, gdzie za x podstawiona zastanie 4, a za y 5. Na razie (chyba) wszystko wygląda prosto i przejrzyście.

Blok definicji

Zajmijmy się zatem ostatnim blokiem - blokiem definicji.
Tu dochodzą znaki specjalne: '#', '##' oraz '\' na końcu linii.
  • znaczek '\' na końcu linii powoduje, że do definiowanego bloku dodana zostanie zawartość linii poniżej.
  • '#' zamieni nam zmienną stojącą za znaczkiem na tekst
  • '##' sklei nam nazwy zmiennych
Dodatkowo: należy pamiętać, że stojące obok siebie teksty się łączą.

Wszystko to w przykładowym kodzie:
#include <stdio.h>
#define specprint(aname, atype, avalue, astr) \
({ \
    atype aname##_v1 = avalue;\
    puts(#aname);\
    puts(#atype);\
    puts("xxx"astr"yyy");\
    printf("%d\n", aname##_v1);\
    aname##_v1++;\
    printf("%d\n", aname##_v1);\
})

int main()
{
    specprint(nazwa, int, 1, "abc");
    return 0;
}
po uruchomieniu programu na ekranie zobaczymy:
nazwa
int
xxxabcyyy
1
2
No tak, zabawa zabawą, ale do czego to może nam się przydać w realnym kodowaniu?
Przykładem wykorzystania makr mogą być inklude'y systemu MorphOS w tzw wersji inline'owej.
Powiedzmy, że napiszemy bardzo prosty program, wyświetlający komunikat "Witaj świecie":
#include <proto/intuition.h>

int main()
{
    struct EasyStruct es;
    struct IntuitionBase *IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0);
    if (IntuitionBase)
    {
        es.es_StructSize   = sizeof(es);
        es.es_Flags        = 0;
        es.es_Title        = (STRPTR) "Komunikat";
        es.es_TextFormat   = (STRPTR) "Witaj świecie";
        es.es_GadgetFormat = (STRPTR) "OK";
        EasyRequestArgs(NULL, &es, NULL, NULL);
        CloseLibrary(IntuitionBase);
    }
}
Zobaczmy jak wygląda rzeczywiste wywołanie funkcji EasyRequestArgs:
#define EasyRequestArgs(__p0, __p1, __p2, __p3) \
	LP4(588, LONG , EasyRequestArgs, \
		struct Window *, __p0, a0, \
		CONST struct EasyStruct *, __p1, a1, \
		ULONG *, __p2, a2, \
		CONST APTR , __p3, a3, \
		, INTUITION_BASE_NAME, 0, 0, 0, 0, 0, 0)
Jak łatwo zauważyć EasyRequestArgs wywołuje tak naprawdę makro LP4. Poszukajmy definicji tego makra:
#define LP4(offs, rt, name, t1, v1, r1, t2, v2, r2, t3, v3, r3, t4, v4, r4, bt, bn, cm1, cs1, cl1, cm2, cs2, cl2 ) \
({                                                        \
	t1 _##name##_v1 = v1;                            \
	t2 _##name##_v2 = v2;                             \
	t3 _##name##_v3 = v3;                             \
	t4 _##name##_v4 = v4;                             \
	REG_##r1           = (ULONG) _##name##_v1;        \
	REG_##r2           = (ULONG) _##name##_v2;        \
	REG_##r3           = (ULONG) _##name##_v3;        \
	REG_##r4           = (ULONG) _##name##_v4;        \
	REG_A6             = (ULONG) (bn);                \
	(rt) (*MyEmulHandle->EmulCallDirectOS)(-offs);    \
})
czyli możemy wg tego schematu nasz program zapisać:
#include <proto/intuition.h>
#ifndef EMUL_EMULINTERFACE_H
#include <emul/emulinterface.h>
#endif
#ifndef EMUL_EMULREGS_H
#include <emul/emulregs.h>
#endif

int main()
{
    struct EasyStruct es;
    struct IntuitionBase *IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0);
    if (IntuitionBase)
    {
        es.es_StructSize   = sizeof(es);
        es.es_Flags        = 0;
        es.es_Title        = (STRPTR) "Komunikat";
        es.es_TextFormat   = (STRPTR) "Witaj świecie";
        es.es_GadgetFormat = (STRPTR) "OK";
        REG_A0 = NULL;
        REG_A1 = (ULONG) &es;
        REG_A2 = NULL;
        REG_A3 = NULL;
        REG_A6 = (ULONG) IntuitionBase;
        (*MyEmulHandle->EmulCallDirectOS)(-588);
        CloseLibrary(IntuitionBase);
    }
}
I też zadziała. I to dokładnie tak samo. Różnica jednak jest taka, że pierwszy kod skompilujemy pod AmigaOS-em, dodatkowo, chyba jest czytelniejsze. A to, że zostanie przekształcone na ten drugi sposób, to nie nasza rzecz - to zrobi za nas kompilator.

Na co uważać przy używaniu makr?

Należy pamiętać, że to co podajemy w argumencie zostanie bezpośrednio wstawione w kod, toteż:
#include <stdio.h>

#define MY_MAX(x, y) (((x)>(y))?(x):(y))

int main()
{
    int m = 1, n = 2, r;
    r = MY_MAX(m++, n++);
    printf("m = %d, n = %d, r = %d\n", m, n, r);
    return 0;
}
Wynikiem działania programu będzie:
m = 2, n = 4, r = 3
chociaż spodziealibyśmy się:
m = 2, n = 3, r = 2
Mam nadzieję, że ten króciutki tekst przybliżył wam odrobinę zagadnienie makr
#define Autor Kaczuś

2015-11-18 22:13:37


makra #define C język C morphos