Over_ en Onder_ flow testen in C

Inhoud


Inleiding

Uit veel lezen is mij opgevallen dat het niet mogelijk is om overflow en underflow te detecteren in (GNU) C .
Dat wil zeggen je kunt rustig a=0x7FFFFFFF + 0x20; uitvoeren , hetgeen ook een getal geeft maar iets anders is dan je zou verwachten . Dit onstaat doordat C geen overflow op integers herkent in een of andere form .
De processoren _x86 van Intel en ook vele andere kennen echter wel degelijk een overflow in deze situatie alleen wordt er C geen gebruikt van gemaakt . Op een dergelijke instruktie wordt in een Status Register wel een of meerdere flaggen(bits) gezet waaraan je kunt afleiden wat er gebeurd is .

Je zou nu iets kunnen maken als :

if (add(0x7FFFFFFF,0x20,a)) {
       /* geen overflow */
       return a;
    } else {
       /* overflow converteer in real */
       return (double)(#7FFFFFFF) + (double)(#20) ;
    }
Het probleem zit nu in de funktie add() .

Een manier die ik ergens zag :

  • Converteer de getallen eerst naar real's .
  • Dan te kijken of ze binnen de grensen MAX_INT en MIN_INT vallen .
  • Zoja het ruseltaat te converteren naar een integer .
    Dit kost veel tyd als je bedenkt dat iedere optelling hierdoor heen moet .
    Natuurlijk heb ik dit gedaan en gekonstateerd dat het 30% meer tijd koste .
    Een veel elementairdere weg is dit probleem in machine taal optelossen .

    Je zou nu iets kunnen maken als :

    add(arg1,arg2,result):
        load arg1    -> acc=arg1
        add  arg2    -> acc=acc+arg2
        store result -> result=acc
        load 0        -> acc=0
        jo   1         -> spring naar label 1 indien overflow_bit is gezet
        cmpl          -> acc=-acc
    1:  nop
    
    Niet schelden , dit is een zelf bedachte machine ala PDP9 (one adress machine) .

    Een dergelijk iets kun je in C doen met behulp van 'Extended Asm' . Feitelijk assembler instruktie die je schrijft in een C programma , maar door C worden doorgeven aan de assembler (GAS) . Deze plaatst de gevraagde machine code inline je C code . Of anders gezegd de gevraagde assembler code wordt in de assembler code gezet die door de C vertaler wordt gemaakt .
    Oh ja de vraag wat kost dit aan tyd ? Ik heb gezien dat dit iets tussen de 3% en 5% meer aan tyd kost .


    Inline assembler

    In GNU C is het mogelijk assembler code in je C code te zetten . Het werkt bij de meeste GNU vertalers het zelfde . Ik heb het getest met DJCPP(dos/windows) MINGWIN(windows) C(Linux) . In het voorbeeld heb ik twee methoden gebruikt .
    Als eerste het gebruikt van een functie .
    #include <stdio.h>
    
    int sub(long ebx, long eax, long* result ){
            long flags,res;
            /* ebx = ebx - eax */
    	__asm__ __volatile__ ("subl %1,%0;movl $0,%1;jo 0f; not %1;0:"
    		: "=b"((long)*result),"=a"((long)flags)
    		: "a"((long)eax),
                      "b"((long)ebx)
    		
    	);
            return flags;
    }
    int add(long ebx, long eax, long* result ){
            long flags,res;
            /* ebx = ebx + eax */
            __asm__ __volatile__ ("addl %1,%0;movl $0,%1;jo 0f; not %1;0:"
    		: "=b"((long)*result),"=a"((long)flags)
    		: "a"((long)eax),
                      "b"((long)ebx)
    		
    	);
            return flags;
    }
    int mul(long ebx, long eax, long* result ){
            long flags,res;
            /* ebx = ebx * eax */
    	__asm__ __volatile__ ("imull %1,%0;movl $0,%1;jo 0f; not %1;0:"
    		: "=b"((long)*result),"=a"((long)flags)
    		: "a"((long)eax),
                      "b"((long)ebx)
    		
    	);
            return flags;
    }
    
    int incr(long ebx, long* result ){
            long flags,res;
            /* ebx = +1 */
    	__asm__ __volatile__ ("incl %0;movl $0,%1;jo 0f; not %1;0:"
    		: "=r"((long)*result),"=r"((long)flags)
    		: "r"((long)ebx)
    		
    	);
            return flags;
    }
    
    int decr(long ebx, long* result ){
            long flags;
            /* ebx = -1 */
    	__asm__ __volatile__ ("decl %0;movl $0,%1;jo 0f; not %1;0:"
    		: "=r"((long)*result),"=r"((long)flags)
    		: "r"((long)ebx)
    	);
            return flags;
    }
    
    
    int main(void) {
    
            long ebx=4,eax=0x80000003; /* invoer data */
            long flags,result; 
                 
            printf("\nTest function's's with underflow/overflow full range \n");
    
            flags = incr(ebx,&result);/* result = ebx +1 */
    	printf("incr       %x = %x + 1 ; flags=%x ", result,ebx,flags);
            if (flags) printf("Correct\n"); else printf("Overflow !!\n");
    
            flags = decr(ebx,&result);/* result = ebx -1 */
    	printf("decr       %x = %x - 1 ; flags=%x ", result,ebx,flags);
            if (flags) printf("Correct\n"); else printf("Overflow !!\n");
    
            flags = mul(ebx,eax,&result);/* result = ebx * eax */
    	printf("mul        %x = %x * %x ; flags=%x ", result,ebx,eax,flags);
            if (flags) printf("Correct\n"); else printf("Overflow !!\n");
    
            flags = add(ebx,eax,&result);/* result = ebx + eax */
    	printf("add        %x = %x + %x ; flags=%x ", result,ebx,eax,flags);
            if (flags) printf("Correct\n"); else printf("Overflow !!\n");
    
            flags = sub(ebx,eax,&result);/* result = ebx - eax */
    	printf("sub        %x = %x - %x ; flags=%x ", result,ebx,eax,flags);
            if (flags) printf("Correct\n"); else printf("Overflow !!\n");
    
    	return 0;
    }
    
    Hier de manier met inline code en een afwijkende ondergrens .
    /* define's with a other underflow number (used by PEU) */
    
    #include <stdio.h>
    #define decrement(longinteger,longresult) ({\
            long flags;\
    	__asm__ __volatile__ ("movl $0,%1;cmpl $0xfd000000,%0;jle 0f;decl %0;jo 0f; not %1;0:"\
    		: "=r"((long)*longresult),"=r"((long)flags)\
    		: "r"((long)longinteger) );\
            flags;})
    
    #define increment(longinteger,longresult) ({\
            long flags;\
    	__asm__ __volatile__ ("incl %0;movl $0,%1;jo 0f;not %1;0:"\
    		: "=r"((long)*longresult),"=r"((long)flags)\
    		: "r"((long)longinteger) );\
    	flags;})
    
    #define multiply(ebx,eax,result) ({\
            long flags; /* ebx = ebx * eax */\
    	__asm__ __volatile__ ("imull %1,%0;movl $0,%1;jo 0f;cmpl $0xfd000000,%0;jle 0f; not %1;0:"\
    		: "=b"((long)*result),"=a"((long)flags)\
    		: "a"((long)eax),"b"((long)ebx) );\
            flags;})
    
    #define adding(ebx,eax,result) ({\
            long flags; /* result = ebx + eax */\
    	__asm__ __volatile__ ("addl %1,%0;movl $0,%1;jo 0f;cmpl $0xfd000000,%0;jle 0f; not %1;0:"\
    		: "=b"((long)*result),"=a"((long)flags)\
    		: "a"((long)eax),"b"((long)ebx) );\
            flags;})
    
    #define subtract(ebx,eax,result) ({\
            long flags; /* result = ebx - eax */\
    	__asm__ __volatile__ ("subl %1,%0;movl $0,%1;jo 0f;cmpl $0xfd000000,%0;jle 0f;not %1;0:"\
    		: "=b"((long)*result),"=a"((long)flags)\
    		: "a"((long)eax),"b"((long)ebx) );\
            flags;}) 
    int main(void) {
    
    	long ebx=4,eax=0x80000003; /* invoer data */
            long flags,result;
    
            printf("\nTest define's with other underflow value (FD000000) \n");
    
            if (increment(ebx,&result)){/* result = ebx +1 */
    	printf("Increment Oke %x = %x + 1 \n", result,ebx);
            } else printf("Increment Overflow !!\n");
    
            if (decrement(ebx,&result)){/* result = ebx -1 */
    	printf("Decrement Oke %x = %x - 1 \n", result,ebx);
            } else printf("Decrement Overflow !!\n");
    
            if (multiply(ebx,eax,&result)){/* result = ebx * eax */
    	printf("Multiply  Oke %x = %x * %x \n", result,ebx,eax);
            } else printf("Multiply  Overflow !!\n");
    
            if (adding(ebx,eax,&result)){/* result = ebx + eax */
    	printf("Adding    Oke %x = %x + %x \n", result,ebx,eax);
            } else printf("Adding    Overflow !!\n");
    
            if (subtract(ebx,eax,&result)){/* result = ebx - eax */
    	printf("Subtract  Oke %x = %x - %x \n", result,ebx,eax);
            } else printf("Subtract  Overflow !!\n");
    
    	return 0;
    }
    
    Bedenk dat de asm(GAS) code de notatie van AT&T gebruikt d.w.z <opr> <source> -> <dist> .
    Dit in tegenstelling van Intel welke <opr> <dist> <- <source> gebruikt .
    Als je de asm code over meerdere regels wil schrijven dan schijnt de conventie van GAS te zijn de regel te eindigen met een '\n' en de nieuwe tebeginnen met een tab '\t' . Bedenk daarbij wel dat GCC de assembler code doorgeeft aan GAS en daarna gaat optimaliseren . Door __volatile__ tegebruiken verbied je de optimalisator je code te gaan schuiven .

    Het waarom van al deze moeite

    Ik houd van systemen met een dynamische data 'typing' . In het moderne hype jargon heet dat 'Duck Typing' . In de meeste talen moet je een variable een type geven . B.v. a is Integer z is String etc . Een bewerking tussen met een variable van het type Integer en String is dan meestal verboden . Even als een optelling met een variable van het type Integer en het type Real . Waarom mag je niet zeggen a=2.0 en b=34 dus is z=a+b -> 36.0 ? Waarom moet je altyd moeilijke conversie doen zoals in C z=a+(double)b of als in Fortran z=a+float(b) . Aangenomen dat z type real is .
    Is het niet veel meer zo dat de bewerking bepaald wordt door de inhoud van de variabele en niet door het type van de variabele .
    Dit is eigenlijk wat het Class syteem doet in Object Oriented Lanquage's . Een functie variabele met de inhoud Integer en een met een inhoud van een Real geeft een Real . Een functie variabele met de inhoud String en een met de inhoud Integer geeft een String .
    Dit laaste moet genuanseerde worden bekeken , n.l. wat is een String .
    In C is een String een char[] in Fortran een character * , feitelijk een verzameling (array) van 8bits(byte) elementen . Tel je hier iets bij op wat groter is dan 8bits , dan wordt het een verzameling van integers . Tel je er iets bij op dat groter is dan MAX_INT dan wordt het een verzameling van real's .
    Een String is feitelijk een array van elementen of tewel een lijst van elementen . Dat betekend dat iedere array een lijst van elementen is . Dit zou ook kunnen betekenen dat ieder element van een lijst weer een lijst is . (Lisp)
    Dit betekend wel dat het heel wat test en rekenwerk kost maar geeft de gebruiker een groot gemak . Wil je echter tijd kritische programma's maken dan zul je toch moeten terug gaan naar statische data typen .

    Hopelijk helpt je dit wat . Kommentaar en toevoegingen kun je me altijd eMailen .

    Menno S Ter Haseborg