Spesso quando si scrivono callback associate ad eventi, o meno spesso in altri casi, il programmatore ha l’ingrato compito di dover stabilire una condotta per gli argomenti di tali funzioni.

Se gli argomenti poi sono stringhe di lunghezza variabile da analizzare per effettuare operazioni in base al loro contenuto, il compito diventa ancora più ingrato.

Mi spiego meglio:

Mettiamo il caso di avere questo codice in un prompt interattivo:

...

struct fn_t {
  char command[kCommandSize];
  int (*func) (char *);
} myFunctions[] = {
  { "ping", doPing },
  { "help", onHelp },
  { {0}   , NULL }
}

...

int onRequest(char *cmd, char *parms) {
  int i = 0;
  while (myFunctions[i].func != NULL) {
     if (strcmp(myFunctions[i].command, cmd) == 0) {
         return myFunctions[i].func(parms);
     }
     i++;
  }
}

...

Il comando ping probabilmente avrà bisogno di un indirizzo ip ed eventualmente altri argomenti tipo il numero di ping da inviare, la grandezza del pacchetto ecc..; il comando help invece, probabilmente non ha bisogno di argomenti oppure eventualmente accetta un solo argomento per l’help interattivo sul comando specificato.

Gli argomenti quindi variano da evento a evento, da funzione a funzione.

Come risolvere?

Il metodo immediato è quello di organizzarsi con una lista di array e lasciare che sia la funzione stessa a verificare ed a “scegliere” i suoi argomenti.

Andiamo ad utilizzare quindi argomenti di tipo argc / *argv[], ovvero identici a quelli del main().

Siccome gli argomenti passati alle funzioni possono essere svariati dobbiamo stabilire il numero massimo di argomenti che la lista può contenere… oppure allocarli dinamicamente!

Il seguente set di funzioni alloca la memoria necessaria per contenere l’array di puntatori agli argomenti passati e lo fa in modo un pò.. inusuale.. Per tenere traccia della sua grandezza se la salva in posizione 0 e dopodichè tratta l’array come se iniziasse dalla posizione 1.

Nota: per il motivo sopra descritto, quando si è finito di lavorare con gli argomenti è necessario liberare la memoria non con i metodi convenzionali (free()) bensì con l’apposita funzione free_args(), che gestisce in modo trasparente la liberazione della memoria dalla posizione esatta, cosi’ come addarg() ne gestisce in modo trasparente l’allocazione.

Passiamo al codice:

char *getarg(char **rest)
{
    register char *o, *r;

    if (!rest)
        return *rest = NULL;
    o = *rest;
    while (*o == ' ')
        o++;

    if (*o == '\"')
    {
        r = ++o;
        while (*o && (*o != '\"'))
            o++;
    } else if (*o == '\'')
    {
        r = ++o;
        while (*o && (*o != '\''))
            o++;
    } else
    {
        r = o;
        while (*o && (*o != ' '))
            o++;
    }
    if (*o)
        *o++ = 0;
    *rest = o;
    return r;
}

const char **addarg(const char **argv, const char *val)
{
    const char **cpp;

    if (argv == NULL)
    {
        /*
         * 10 voci iniziali, 1 per salvare la grandezza, e 1 null
         */
        argv = malloc(sizeof(*argv) * 12);
        if (argv == NULL)
            return NULL;
        *argv++ = (char *) 10;
        *argv = NULL;
    }
    for (cpp = argv; *cpp; cpp++);
    if (cpp == &argv[(int) argv[-1]])
    {
        --argv;
        *argv = (char *) ((int) (argv[0]) + 10);
        argv = realloc(argv, sizeof(*argv) * ((int) (argv[0]) + 2));
        if (argv == NULL)
            return NULL;
        argv++;
        cpp = &argv[(int) argv[-1] - 10];
    }
    *cpp++ = val;
    *cpp = 0;
    return (argv);
}

void make_args(char *source, int *argc, char ***argv)
{
    char *workstr, *p;

    *argc = 0;
    *argv = (char **) 0;
    if (!source || !*source)
    {
        return;
    }

    workstr = source; /* se la stringa passata deve essere riutilizzata
                       * successivamente alla sua suddivisione in argomenti
                       * in questo punto bisogna cambiare il codice in:                       * workstr = strdup(source);
                       */
    while (1)
    {
        p = getarg(&workstr);
        if (!p || !*p)
            break;
        *argv = (char **) addarg((const char **) *argv, p);
        (*argc)++;
    }
    return;
}
void free_args(char **argv)
{
    if (argv - 1)
        free(argv - 1);
}

L’uso è molto semplice:

...

void myFunc(char *str) {
  int ac;
  char **av;

  make_args(str, &ac, &av);
  myCallback(ac, av);
  free_args(av);
}

...

Attenzione! Potete notare come anche questo codice non è totalmente esente da limiti. Manteniamo infatti solo 1 byte per salvare la grandezza dell’array, quindi praticamente addarg può aggiungere fino a 255 argomenti prima di andare in overflow… Hey 255 sono tanti! Se quindi vi state accingendo ad usare queste funzioni in codice di produzione è opportuno inserire un controllo sul numero di argomenti almeno 2 istruzioni prima del realloc().