Newsgroup: it.comp.lang.c
Titolo: Operazioni in complemento a 2 e comportamento del C

---
int ticks = -26014;
int tmr = 32719;
if (ticks - tmr > 0)
  printf ("Timer esaurito\n");
---
Su un'architettura a 32 bit, ovviamente la printf() non viene
eseguita.
Cosa succede, invece, su un'architettura a 16 bit?
A me servirebbe che la printf() venga eseguita e ragionando in
aritmetica
in complemento a 2, dovrebbe essere proprio così.
Sembra però che il mio compilatore non ragioni in questo modo e la
printf() non
la esegua. Forse perchè va a vedere il bit di segno di entrambi i
numeri senza fare
l'operazione vera e propria.
C'è un modo per forzare il compilatore a fare la differenza, metterla
in un int e andare
a guardare lì il bit di segno?
mi aspetto?

Studiano meglio l'assembler prodotto dal compilatore, noto che
l'espressione nell'if viene tradotta con tre istruzioni assembler
(ometto gli operandi):
MOVW
CMPW
BLE
Viene spostato la word ticks nell'accumulatore. Poi viene fatta
la sottrazione di tmr con l'accumulatore, lasciando il risultato
nell'accumulatore. La BLE esegue il salto se:
(Z = 1) OR (V XOR N)
dove
- Z flag di zero (settato se il risultato dell'operazione è nullo)
- V flag di overflow (settato se c'è overflow)
- N flag negative (settato se il bit più significativo è 1)
A questo punto penso che il salto viene fatto (quindi l'istruzione
nell'if, la printf(), non viene eseguita) perchè c'è overflow.
Testualmente sul manuale:
"V (Overflow flag): This flag is set if a digit overflow occurs in a
signed value generated as the result of an arithmetic or logical
operation. Otherwise, the flag is cleared."
Non capisco perchè il compilatore C converte la condizione
"> 0" con BLE e non con altre istruzioni assembler (comunque
presenti) che vanno a vedere solo il bit di segno N o, al massimo,
anche Z.


Ho risolto, anche se non mi piace, con:
if (!((unsigned int)(ticks - tmr) & 0x8000))
  printf ("Timer esaurito\n");
In pratica vado a leggere io "manualmente" il bit del segno del
risultato,
anche se così facendo la condizione è superato anche quando c'è
l'uguaglianza, ma può cmq andar bene.


Non credo tu abbia scelta: cambia compilatore.


Bruttissimo.
Prima cosa: includi il file limits.h
controlla ogni valore per INT_MAX (e compagnia): se INT_MAX è 100 non
ci puoi mettere dentro 101.

 ci puoi mettere dentro 101.
Scusa, ma non ho capito.
Magari spiego meglio il problema così si capisce.
Ho un interrupt che scatta ogni millisecondo e che si limita ad
incrementare la
variabile globale
  volatile int ticks;
dichiarata volatile proprio perchè cambia il suo valore all'interno
dell'interrupt.
Naturalmente quando ticks vale 0x7FFF (cioè +32767), passerà a 0x8000
(cioè -32768), cioè ci sarà roll-over.
Per fare delle pause nel programma principale faccio questo:
int tmr;
tmr = ticks + 100;
while (ticks - tmr > 0)
  ;
printf ("Pausa terminata\n");
Ovviamente si tratta di un'architettura a 16 bit senza sistema
operativo con risorse
limitate.
Il tutto funziona a dovere, anche quando c'è roll-over di ticks.
Infatti, man mano che
ticks aumenta, la differenza ticks-tmr ovviamente diminuisce. Quando
ticks fa roll-over,
la differenza continua a diminuire senza discontinuità.
Infatti, se tmr = -32765 = 0x8003 e ticks = 32767 = 0x7FFF, la
differenza (in complemento
a 2) è 2. Quando ticks si incrementa e diventa -32768 = 0x8000, la
differenza sarà 3, ecc.
Però mi serve che il compilatore faccia il suo dovere.
Ma questo comportamento (differenza tra due signed int) non è
documentato? Mi sembra
che il mio compilatore sia fuori delle specifiche del linguaggio, non
trovate?
Ma a parte la "bruttezza estetica" della mia soluzione, ci sono altre
controindicazioni?


Allora... non vorrei prendere una cantonata, ma mi sembra che confondi
valori e rappresentazioni.
Per lo standard l'overflow di due interi è "implementation-defined",
quindi le cose sono due:
- il compilatore ha un implementazione di questa situazione che te non
aspetti. Magari sulla documentazione c'è scritto come si comporta in
questi casi.
- passi a unsigned int :-) (sperando che non sfori di nuovo)
Ciao!

   
Qual � l'archittura, per curiosit�?
Giovanni

   
Qual � l'architettura, per curiosit�?
Giovanni

 - passi a unsigned int :-) (sperando che non sfori di nuovo)
Sicuramente sforo. L'interrupt scatta sempre ogni millisecondo e
l'applicazione
è in funzione per giorni interi. La variabile che va in overflow e fa
roll-over è
voluta.
Non posso usare variabili più grandi (tipo long) perchè così perderei
l'atomicità
delle operazioni, importanti durante l'interrupt.
Infine non posso usare, come suggerisci, un unsigned perchè, dopo un
overflow,
non funzionerebbe.
Immagina che ticks valga ora 65000. Voglio impostare un timer dopo
1000ms,
quindi avrei 65000+1000=465. Facendo il test sulla differenza maggiore
di zero,
otterrei subito che ticks è maggiore di tmr e il timer scatterebbe
subito, senza
aspettare i 1000ms richiesti.

 aspettare i 1000ms richiesti.
Nel caso di controllo asincrono (fuori dall'interrupt), in caso di 
overflow, basta una semplice somma.
Questo è un semplice esempio (in Windows con il tick che va in overflow 
dopo circa 49 giorni):
---
   tick = GetTickCount();
   ...
   ...
   DWORD t = GetTickCount();
   DWORD t2 = t - tick;
   if(t2 < MAX_TIMEOUT_REQ_RESP)
     return false;
   if(t < tick)
     t2 += (DWORD)-1;
   if(t2 < MAX_TIMEOUT_REQ_RESP)
     return false;
---
Nel tuo caso non basterebbe usare una variabile impostata al valore del 
timer richiesto e decrementarla ad ogni interrupt?
Quando arriva a zero, o gestisci il tuo codice all'interno 
dell'interrupt o segnali con un'altra variabile e reimposti il timer.
Ciao

 dell'interrupt o segnali con un'altra variabile e reimposti il timer.
E' il metodo che utilizzavo prima e che non mi piace per niente.
Infatti ad
ogni evento temporizzato che decido di aggiungere all'applicazione
devo:
1. modificare la funzione di interrupt (è come se andassi a modificare
il
   sistema operativo)
2. il tempo di esecuzione della funzione di interrupt è variabile a
seconda
   dei timer che mi servono.

 if (ticks - tmr > 0)
perche' probabilmente fa questo:
if(ticks - (tmr > 0))
invece di quello che vuoi tu:
if((ticks -tmr) > 0)
i compilatori dei micro ogni tanto si incasinano se devono fare
operazioni dentro le parentesi degli if (o dei while...)
Meglio quindi abbondare con le parentesi.
Infatti quando ha usato if (!((unsigned int)(ticks - tmr) & 0x8000))
hai usato le parentesi (anche inconsapevolmente) per forzarlo a fare
la sottrazione.
Ciao Jack

 if(ticks - (tmr > 0))
ok ho detto una cazzata, non avevo guardato i valori.
Cmq il consiglio di abbondare con le parentesi vale sempre.
E di leggerti il datasheet del micro E del compilatore, cosi da sapere
cosa fanno in caso di overflow.
Cmq affidarsi all'overflow HW del micro e' una pessima idea, ci sono
micro che fanno rollover, altri che saturano.
Meglio fare un controllo SW dell'overflow.
Ciao Jack

 Meglio fare un controllo SW dell'overflow.
In effetti è così, ma veramente mi cade un baluardo della
programmazione in C.
Ero convinto che la somma tra due signed fosse sempre fatta allo
stesso modo:
si sommano i registri e si elimina l'eventuale overflow, magari
segnandolo nel
registro di stato per eventuali consultazioni successive.
Se mi dici che ci sono micro che saturano, non riesco proprio a capire
come sono
fatti i circuiti logici di addizione. Mi risulta alquanto assurdo che
vadano a complicarlo
per controllare se c'è overflow e saturare il risultato. Forse è un
effetto del compilatore
piuttosto che dell'hw.

pozz ha scritto:

 >> Immagina che ticks valga ora 65000. Voglio impostare un timer
 >> dopo 1000ms, quindi avrei 65000+1000=465.
Tradotto per quello che vorresti tu (con una sola variabile che delimita 
il time da raggiungere):
usando valori unsigned a 16bit
tmp = ticks > timer ? ticks + 65535 : ticks;
if(tmp > timer)
     ....

 } while (++x != 0);
Quello che hai scritto non solo non è garantito che funzioni, ma sulla
maggior parte delle architetture che conosco io non funzionerebbe.
Forse giusto su qualche tipo di micro ad 8-bit..


Scusami, in realtà l'esempio voleva essere con x dichiarata unsigned
char
e non unsigned int. Ed era cmq per capire se il wrap-around dal valore
massimo (255 nel caso di unsigned char) a 0 fosse garantito oppure no
dallo standard C indipendentemente dall'architettura.

      ....
E tmp è a 32 bit? No, preferisco evitare...

pozz ha scritto:

No, tmp è unsigned a 16 bit.

 piuttosto che dell'hw.
no, e' il micro. Esempio: Freescale 56F800/E.
la saturazione si puo' attivare o disattivare (c'e' un istruzione asm
per farlo).
Per sommare il micro usa un accumulatore a 36 bit (il micro e' un
ibrido tra un microcontroller e un DSP), finita l'operazione se la
saturazione e' attivata controlla che il risultato non superi il
massimo, se si sostituisce il risultato con il massimo e setta il bit
di overflow, altrimenti lascia com'e'.
Ciao Jack


Yes. Il problema della sbagliata concezione del wrapping sull'overflow
degli unsigned (che avevo anch'io fino a qualche tempo fa, del resto)
è che nella programmazione x86 è considerata una finezza.
Personalmente sono anche sicuro che all'università ho dato un esame
dove il professore rompeva così tanto con il wrapping che mi son fatto
l'idea che fosse l'unica soluzione possibile al problema
dell'overflow :-)
P.S. Sugli x86 normalmente i timer vengono gestiti dal PIT, e
programmati per dare un'interrupt allo scadere, quindi in questo caso
il problema non si porrebbe :D

 No, tmp è unsigned a 16 bit.
Ok, ma l'operazione ticks + 65535 è un'operazione che fa overflow e
quindi, alla fine, siam sempre lì.

logicMind ha scritto:

scusa, ho scritto una cavolata, con una sola variabile può funzionare 
solo l'uguaglianza e solo nella routine di interrupt.
Riscrivo il codice, aggiustato, e con qualche commento.
----
BOOL checktime(uint16 time, uint16* starttime)
{
   /* assume che ticks sia globale */
   uint16 currtime = ticks;
   uint16 t = currtime - *starttime;
   if(t < time)
     return FALSE;
   /* se c'è stato wrap */
   if(currtime < *starttime) {
     /* lo spazio temporale è 0<->currtime + starttime<->MAX
        quindi il delta è currtime + MAX - starttime
        t è già currtime - starttime, basta sommargli MAX
     */
     t += (uint16)-1;
     if(t < time)
       return FALSE;
     }
   *starttime = currtime;
   return TRUE;	
}
...
uint16 time1 = 1000;
uint16 starttime1 = ticks;
...
if(checktime(time1, &starttime1))
   ...
----
ciao

 di overflow, altrimenti lascia com'e'.
Probabilmente serve per applicazioni particolare con i segnali
digitali.
Magari in alcuni casi è meglio saturare che "wrappare"...