Helsingin yliopisto Tietojenkäsittelytieteen laitos
 

Tietojenkäsittelytieteen laitos

Tietoa laitoksesta:

 
Helsingin yliopisto / Tietojenkäsittelytieteen laitos / Copyright © 2000 Jan Lindström. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

58127-1 C-ohjelmointi - Syksy 2000 : Lisätietoja


Vaihtuvanmittaiset funktioparametrit

Tässä osiossa muodostetaan minimaalinen printf-funktion toteutus. Esitys perustuu kurssikirjan K&R lukuun 7.3 (sivu 155 minun painoksessa). Tavoitteena on siis esitellä kuinka kirjoitetaan funktio, joka saa parametrikseen vaihtuvan määrän argumentteja. Funktion printf määrittely on muotoa:

int printf(char *fmt, ...);

, missä määrittely ... tarkoittaa, että argumenttien lukumäärä voi vaihdella. Määritys ... voi esiintyä vain argumenttilistan lopussa. Oma minprintf määritellään seuraavasti:

void minprintf(char *fmt, ...);

Ainoa vaikea asia on, kuinka käsitellä argumenttilista, kun argumenteillä ei ole edes nimiä tai tyyppejä. Standardikirjaston otsikkotiedosto <stdarg.h> sisältää joukon makromäärityksisä, jotka määrittelevät kuinka argumenttilistaa käsitellään. Markojen toteutukset vaihtelevat ympäristöstä toiseen, mutta rajapinta on standardoitu.

Tyypillä va_list määritellään muuttuja, joka viittaa jokaiseen käsiteltävään argumenttiin kerrallaan. Esimerkissä muuttujalle on annettu nimi ap (argument pointer). Makrolla va_start alustetaan ap osoittamaan ensimmäiseen nimeämättömään argumenttiin.

Jokainen va_arg makron kutsu palauttaa yhden argumentin ja siirtää muuttujan ap osoittamaan seuraavaan argumenttiin. va_arg käyttää tyypin nimeä selvittääkseen, mitä tyyppiä pitää palauttaa ja kuinka suuri on kasvatuskoko muuttujassa ap. Lopulta va_end tekee tarvittavan siivouksen. Tätä makroa täytyy kutsua ennen funktiosta paluuta.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void minprintf(char *, ...);
int main(void);

void minprintf(char *fmt, ...)
{
    va_list ap;
    char *p,*sval;
    int ival;
    double dval;

    va_start(ap,fmt); /* Hae ensimmäinen nimeämätön parametri */

    /* Läpikäydään kaikki formaatin merkit */

    for( p = fmt; *p; p++)
    {
        /* Jos kyseessä ei ole kentänmäärittely, tulosta merkki suoraan */
        if ( *p != '%' )
        {
            putchar(*p);
            continue; /* Jatka seuraavasta formaatin merkistä */
        }

        /* Kyseessä on siis kerntämäärittely muotoa %x, missä x on merkki */
        
        switch (*++p)
        {
            case 'd': /* Tulostetaan kokonaislukuja */
            {
                ival = va_arg(ap,int); /* Hae siis integer parametri */
                printf("%d",ival);     /* Laiskotellaan käytetään printf:ää */
                break;
            }

            case 'f': /* Tulostetaan realilukuja */
            {
                dval = va_arg(ap,double);
                printf("%f",dval);
                break;
            }

            case 's': /* Tulostetaan merkkijono */
            {
                for( sval = va_arg(ap,char *); *sval; sval++)
                    putchar(*sval);

                break;
            }

            default:
            {
                putchar(*p);
                break;
            }
        }
    }
    va_end(ap); /* Siivoa jälkesi */
}

int main(void)
{
    int a = 6;
    double d = 3.1415;
    char *s = "Merkkijonoa aika paljon";

    /* Testataanpa omaa funktiotamme */

    minprintf("Numero on %d ja realiluku on %f ja merkkijono on %s ja %i %%\n",
        a,d,s);
    return 0;
}

Komentoriviparametrien käsittely UNIX-koneissa

UNIX-ympäristössä (joihin Linux voidaan laskea) annetaan komentorivillä usein paljon parametrejä. Parametrit annetaan usein lyhyessä muodossa, esim:

ls -aAbcCdfFgilLmnopqrRstux1 , tai ls -a -c -d -r

Tällaisten parametrien käsittely on kohtuullisen hankalaa. Käsittelyfunktiota ei kannata tehdä itse. Useissa UNIX-ympäristöissä on valmiiksi toteutettuna getopt niminen palvelu (man 3C getopt komennolla Linux:ssa saa manuaalisivut). Tällä funktiolla saa aikaiseksi suurimman osan tarvittavista ominaisuuksista. Samassa manuaalisivussa kuvataan myös monipuolisempi getopt_long, joka jätetään tässä esityksessä väliin. Funktion määrittely on muotoa:

#include <unistd.h>
 
int getopt(int argc, char * const argv[], const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;  

Funktio getopt() parsii komentoriviargumentit. Parametriksi annetaan main ohjelman saamat argc ja argv muuttujat. Osoitintaulukon argv elementti, joka alkaa '-' ( ja ei ole täsmälleen "-" tai "--") on optioelementti. Tämän elementin muut merkit ovat optiomerkkejä.

Jos funktiota getopt() kutsutaan useita kertoja, niin jokainen kutsu palauttaa jokaisen optimerkin jokaisesta optioelementistä. Jos getopt() löytää toisen optiomerkin, palauttaa se kyseisen merkin, päivittää ulkoista muuttujaa optind ja staattista muuttujaa nextchar siten, että seuraava getopt() kutsu voi jatkaa oikeasta kohtaa argv elementtiä.

Jos uusia optiomerkkejä ei löydy, getopt() palauttaa -1. Muuttujan optind arvona on ensimmäinen argv taulukon alkio, joka ei ole optio. optstring on merkkijono, joka sisältää sallitut optiomerkit. Jos optiomerkin perässä on ':'-merkki, tällöin optio vaatii argumentin (esim. -o filename). Tällöin getopt() asettaa optarg osoittimen seuraavaan tekstiin samassa tai seuraavassa argv elementissä.

Oletusarvoisesti getopt() permutoi argv elementtiä siten, että optioihin kuulumattomat parametrit ovat lopussa. (Muut piirteet jätän käsittelemättä). Erityinen argumentti '--' pakottaa optioiden skannauksen lopettamisen riippumatta tilasta.

Jos getopt() ei tunnista optiomerkkiä, tulostuu stderr syötevirtaan virheilmoitus, merkki tallettuu optopt muuttujaan ja funktio palauttaa '?'-merkin.

Funktio getop() palauttaa optiomerkin jos optio löytyy, ':'-merkin jos option parametri puuttuu jostain optiosta, '?'-merkin tuntemattoman optiomerkin tapauksessa tai -1 jos saavuttiin optiolistan loppuun.

Lisätietoja saa lukemalla manuaalisivua. Tässä vaiheessa on syytä katsoa esimerkkiä, joka koostuu tiedostoista transgen.h ja transgen.c:

#ifndef TRANSGEN_H
#define TRANSGEN_H

#define ARGS "t:B:w:d:D:a:r:p:c:W:P:R:b:o:O:f:"

typedef struct option
{
    ulong TransactionCnt;
    ulong DatabaseSize;
    ulong WriteFraction;
    ulong DeadlineMin;
    ulong DeadlineMax;
    ulong ArrRate;
    ulong ReadTime;
    ulong PreWriteTime;
    ulong CommitTime;
    ulong WriteTime;
    ulong PriorityMin;
    ulong PriorityMax;
    ulong BlockCnt;
    ulong OperationMin;
    ulong OperationMax;
    FILE *output;
} options;

typedef enum { BEGIN = 1, READ = 2,PREWRITE = 3, 
COMMIT = 4, WRITE = 5, END = 6, RESET = 7} OperType;

typedef struct Item
{
    struct Item *Next;
    struct Item *Prev;
    OperType Type;
    ulong Delay;
    ulong Oid;
    ulong Priority;
    ulong Deadline;
} ListItem;

#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include "transgen.h"

extern int cgetopt(int *,char **,options *);
int main(int,char **);

int main(int argc,char **argv)
{
    options transopt;
    ListItem head;
    
    if ( cgetopt(&argc,argv,&transopt) ) 
    {
        printf("Virhe komentorivillä!\n");
        exit(1);
    }
    
    return 0;
}

int cgetopt(int *argc,char **argv,options *transopt)
{
    int c,errflg = 0;

    if ( *argc < 2 )
        return (1);
        
/* Parsitaan parametrit (man 3 getopt) */

    while (!errflg && ((c=getopt(*argc,argv,ARGS)) != -1))
    {
        switch(c)
        {
            case 't':
            {
                transopt->TransactionCnt=atoi(optarg);
                break;
            }
            case 'B':
            {
                transopt->DatabaseSize=atoi(optarg);
                break;
            }
            case 'w':
            {
                transopt->WriteFraction=atoi(optarg);
                break;
            }
            case 'd':
            {
                transopt->DeadlineMin=atoi(optarg);
                break;
            }
            case 'D':
            {
                transopt->DeadlineMax=atoi(optarg);
                break;
            }
            case 'a':
            {
                transopt->ArrRate=atoi(optarg);
                break;
            }
            case 'r':
            {
                transopt->ReadTime=atoi(optarg);
                break;
            }
            case 'p':
            {
                transopt->PreWriteTime=atoi(optarg);
                break;
            }
            case 'c':
            {
                transopt->CommitTime=atoi(optarg);
                break;
            }
            case 'W':
            {
                transopt->WriteTime=atoi(optarg);
                break;
            }
            case 'P':
            {
                transopt->PriorityMin=atoi(optarg);
                break;
            }
            case 'R':
            {
                transopt->PriorityMax=atoi(optarg);
                break;
            }
            case 'b':
            {
                transopt->BlockCnt=atoi(optarg);
                break;
            }
            case 'o':
            {
                transopt->OperationMin=atoi(optarg);
                break;
            }
            case 'O':
            {
                transopt->OperationMax=atoi(optarg);
                break;
            }
            case 'f':
            {
                if(!( transopt->output=fopen(optarg,"w") ))
                    errflg++;
                break;
            }

            default:
                errflg++;break;
        }
    }
    
    return errflg;    
}


Jan Lindström (Jan.Lindstrom@cs.Helsinki.FI)