2016-08-04

Jedan problem iz prakse programiranja u C-u

Često imam potrebu da nekoj funkciji prosledim niz stringova, nešto oblika char* names[]. Naravno ako već nemam niz stringova, napraviću neku tabelu, to jest dvodimenzionalni niz karaktera, nešto oblika char table[][10]. I da li će raditi, pa naravno da neće, čak i kompajler kaže da nešto nije u redu. Pa da, mi smo lokalno deklarisali veliki niz karaktera (grupisanih po 10), a funkcija očekuje niz pokazivača na karakter, to jest niz stringova. Eto belaja. Kako se ovo rešava, pa klasika, napravimo taj niz stringova, lepo ga napunimo i pozovemo funkciju. Dobro, nema tu puno spasa, možemo da paralelno održavamo te dve strukture i sve je super. Problem rešen.

Ali, desi mi se, s vremena na vreme, da je tabela statična, poznata u trenutku kompajliranja. Stvarno ću da pravim funkciju za punjenje names? Neću iskoristiti neki mehanizam, recimo makroe? Iskustvo me uči, ajde prvo napiši ručno pa da vidimo da li može neka automatizacija i koliki je problem ako ne može. I eto prve verzije:

char table[][10] =
{
  "a1",
  "a2",
  "a3",
  "a4"
};

char* names[] =
{
  table[0],
  table[1],
  table[2],
  table[3]
};
Čuj, upotrebljivo je, nije čak i neka velika frka dodavanje novog stringa u table, samo treba da nešto dopišem u names, dodam na kraj i to je to. Ali ajde igre radi da pokusamo da to malo automatizujemo. Koristićemo X makroe, i ajde da prvo napravimo fajl table.x u kojem ćemo nabrojati stringove, ali i neka njihova imena koja cemo koristiti u enum-u. Korišćenje enum-a je korisno, svako ko je pisao nešto ovako zna da vredi dodati enum sa indexima stringova u tabeli. Dakle fajl table.x:
#if defined(TABLE)

TABLE(A1, "a1")
TABLE(A2, "a2")
TABLE(A3, "a3")
TABLE(A4, "a4")

#endif
Ovo je prošlo lagano, kako sada pravimo enum? Pa definisaćemo makro TABLE, inkludujemo table.x i zavrsimo nabrajanje necim. Pa to ponovimo ponovo za sve ostalo. Evo kako naš primer izgleda sada. Ko želi neka iskoristi gcc -E opciju da proveri da li radi.
enum table_indexes
{
  #define TABLE(index, string) TABLE_INDEX_##index,
  #include "table.x"
  #undef TABLE
  TABLE_SIZE
};

char table[][10] =
{
  #define TABLE(index, string) string,
  #include "table.x"
  #undef TABLE
  ""
};

char* names[] =
{
  #define TABLE(index, string) table[TABLE_INDEX_##index],
  #include "table.x"
  #undef TABLE
  NULL
};
Meni radi, ne znam za vas, ali ajde da krenemo da cepidlačimo malo: novi fajl? Ma samo mi to treba, ajde da malo zabiberimo. Napravićemo makro FOR_EACH_TABLE koji kao argument ima ime novog makroa koji ćemo pozivati. Naravno \ na kraju celog makroa zahteva da iza bude jedan prazan red. Da se ne lažemo malo ko neće odvojiti ovaj makro praznim redom, a sada barem svi redovi "database-a" liče.
#define FOR_EACH_TABLE(call) \
  call(A1, "a1") \
  call(A2, "a2") \
  call(A3, "a3") \
  call(A4, "a4") \

enum table_indexes
{
  #define TABLE_INDEXES_CALL(index, string) TABLE_INDEX_##index,
  FOR_EACH_TABLE(TABLE_INDEXES_CALL)
  TABLE_SIZE
};

char table[][10] =
{
  #define TABLE_CALL(index, string) string,
  FOR_EACH_TABLE(TABLE_CALL)
  ""
};

char* names[] =
{
  #define NAMES_CALL(index, string) table[TABLE_INDEX_##index],
  FOR_EACH_TABLE(NAMES_CALL)
  NULL
};
I ovo možete proveriti, meni naravno radi :). Šta dalje? Pa ne znam ni sam, recimo onaj enum mi je višak (mada izuzetno koristan u realnosti, kao sto rekoh). Kada bi postojao neki način da makro broji. Ovde vec zalazimo u domen GCC-a. Ko ne želi da zna nešto više o GCC-u neka stane sa čitanjem, nastavak se odnosi samo na njihovu extenziju. Naime postoji predefinisani makro __COUNTER__ koji svakim pozivom se razvija u sledeći broj, počevši od 0. Ha, pa sada je ovo baš izuzetno lako:
#define FOR_EACH_TABLE(call) \
  call("a1") \
  call("a2") \
  call("a3") \
  call("a4") \

char table[][10] =
{
  #define TABLE_CALL(string) string,
  FOR_EACH_TABLE(TABLE_CALL)
  ""
};

char* names[] =
{
  #define NAMES_CALL(string) table[__COUNTER__],
  FOR_EACH_TABLE(NAMES_CALL)
  NULL
};
Nije loše, ali ja ću uvek izabrati varijantu sa enum-om, ostati lepo u standardnom C-u i završiti posao :)

P.S. Mogao sam da napravim gomilu makroa koji broje, ali nisam baš siguran da je pametno definisati jedno 1000 makroa da bi pokrio sve slučajeve. To je za jedan drugi post, jednom ...

No comments:

Post a Comment