Buggfix Plus
Aktuellt datum och tid: 01.44 2018-11-13

Alla tidsangivelser är UTC + 1 timme




Svara på tråd  [ 145 inlägg ]  Gå till sida 1, 2, 3, 4, 5 ... 10  Nästa
Författare Meddelande
 Inläggsrubrik: Enkla men fatala buggar
InläggPostat: 23.05 2013-05-17 
EF Sponsor
Användarvisningsbild

Blev medlem: 22.54 2006-09-23
Inlägg: 30207
Ort: Borås
Kan vi ha denna tråden typ klistrad eller nått, tanken är att lista fallgropar, vilka kan generera buggar, varav vissa kan vara svåra att hitta.

Nåväl, exempel 1:
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
if (i=1){......}

En lurig sak, dessutom extremt lätt att göra.
Man kan undvika den genom att slå på alla varningar, till exempel "wall" i gcc.
Dock, en mycket bättre variant är att vända på det.
Om man istället konsekvent sätter konstanten på vänster sida, dvs
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
if (1=i){....}

Då får man garanterat ett felmeddelande, så man kan fixa tioll raden till dess rätta innehåll, dvs
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
]if (1==i){....}


Exempel 2.
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
uint32 i;
sint16 arr[..]
i = arr[x++]
i |= arr[x++]<<16

Ovanstående kod funkar, kanske ibland, ibland alltid, ibland inte, ibland funkar den slumpmässigt.
Problemet uppstår när den högre byten i "arr" inte är 0, eftersom "i" i detta fallet är 32 bitar, och man läser in från en 16 bitars array, så kan vad som helst hamna i de övre 16 bitarna i variabeln "i",
Samma gäller ju även om "arr" är en vanlig variabel, då får de övre 16 bitarna "slumpmässiga" värden, dvs värden från dataomrrådet intill "arr".
för att slippa detta måste man naturligtvis casta 16bitars variabeln, så man enbart läser in 16 bitar, dvs
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
uint32 i;
sint16 arr[..]
i = (uint16)arr[x++]
i |= arr[x++]<<16


Det var två exempel på fallgropar, fyll gärna på med fler.


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 03.27 2013-05-18 

Blev medlem: 18.26 2006-07-04
Inlägg: 23239
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
for(x=0; x<10; x++);
{ printf(..); outportb(..); }

Exekverar inte så mycket.. ;)


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 04.15 2013-05-18 
Användarvisningsbild

Blev medlem: 14.52 2005-01-10
Inlägg: 23202
Ort: Kristinehamn
Och förklaringen till blueint's exempel:
Efter for(x=0; x<10; x++) finns det en ';' vilket gör att den räknar upp x till 10 - och inget mer under den uppräkning.

Därefter utförs utskriften och portgrejen - men bara en enda gång.


Senast redigerad av Icecap 07.24 2013-05-18, redigerad totalt 1 gång.

Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 07.17 2013-05-18 

Blev medlem: 15.11 2006-07-26
Inlägg: 394
Ort: Vasa, Finland
TomasL: Är du säker på att du tänker rätt om exempel 2? För mig förefaller det mycket märkligt att kopiering av en 16 bitsvariabel till en 32 bitsvariabel inte skulle rensa de övre 16 bitarna i destinationen. Åtminstone har jag mig veterligen aldrig stött på något sådant problem.

Däremot skulle jag vara försiktig med bitshiften:
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
i |= arr[x++]<<16
Det förefaller som att man shiftar en 16 bitsvariabel med 16 bitar, vilket inte kan ha önskad effekt. Jag hade nog castat arr[x++] först:
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
i |= ((uint32)arr[x++])<<16
Där finns kanske en onödig parentes, jag kommer inte ihåg rangordningen för operatorerna. Men genom att casta till 32 bitsvariabel först så får 16 bitsshiften rum i resultatet. Det är väl också så att man skall vara lite försiktig med att shifta signed-variabler också, då "minustecknet" kan ställa till det om man inte vet vad man gör.

Jag är absolut ingen mästare på programmering, så jag kan gott ha fel.


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 09.51 2013-05-18 
Användarvisningsbild

Blev medlem: 09.04 2007-09-03
Inlägg: 2088
Ort: Säffle
Exempel 2 av TomasL belyser ett problem, men det är inte slumpmässigt och inte en bugg.

Exemplet innehåller två del-problem, dels en teckenomvandling och dels ett bitshift.

Om teckenomvandlingen:
Man måste vara på det klara med att omvandla från sint16 till uint32 inte alltid är möjligt.
Om sint16 är noll eller positivt funkar det, men naturligtvis inte om sint16 är negativt eftersom uint32 inte kan representera ett negativt tal.

Om sint16 är negativt sker omvandlingen i flera steg, enligt C-standard.
Först omvandlas det till sint32 så att antal bitar är korrekt.
Sedan adderas talet 2**32, vilket inte förändrar bitmönstret, men gör att tolkningen (flaggor m.m.) blir korrekt.

Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
/* Exempel med omvandling från 8 bitars signed till 16 bitars unsigned */
sint8 i;
uint16 j;
i=-2;
j=i; /* j får värdet 65534 */


Om bitshiftet:
Som ideotdea skriver måste man se upp med bitshiften.
Shiftar man ut 16 bitar ur ett 16-bitarstal blir det bara 0 kvar. Att sen omvandla till 32-bitars tal ger fortfarande noll.
Alltså måste man först omvandla till 32-bitars och därefter har man plats att shifta 16 bitar och behålla dom.

Jag vill också återigen slå ett slag för boken "Vägen till C" av Ulf Bilting och Jan Skansholm.
Där finns allt detta förklarat redan på sidan 53. :) (Ja, jag slog upp det för att vara säker)
Det är alltså grundkunskap för en C-programmerare att känna till typ-omvandlingar.

Mitt råd är:
Se ALLTID upp vid typomvandlingar.
Även om det är definerat vad som händer så kanske det inte är vad DU vill ska hända.
Använd därför type-casting, och rikligt med paranteser, vid minsta tveksamhet.


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 12.35 2013-05-18 
Användarvisningsbild

Blev medlem: 11.56 2004-05-08
Inlägg: 2850
Ort: Stockholm
"Pointer alignment"/aliasing

Exempel:
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
  1. int i;
  2. char data[DATA_LEN];
  3. int number;
  4.  
  5. for (i = 0; i < DATA_LEN; i++)
  6.     data[i] = read_byte(); /* Läs bytes någonstans ifrån */
  7.  
  8. ...
  9.  
  10. /* Hantera data[2]..data[5] som en 32-bit int */
  11. number = *((int *) &data[2]); /* Odefinierat resultat! */

Ovanstående kan fungera på vissa arkitekturer (ibland med sämre prestanda), men fungerar inte på alla. Problemet är att många arkitekturer kräver att pekare till datatyper ska vara alignade på jämna multiplar av storleken på datatypen, d.v.s. pekare till int måste ha värden som är multiplar av 4 (eller vad nu sizeof(int) är på respektive arkitektur). Och det lömska är att detta är ett "tyst" fel; kompilatorn kan inte upptäcka det och man får ingen exception eller likande om det inte fungerar, bara väldigt konstiga värden...

Evalueringsordning inom uttryck med bieffekter

Exampel:
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
i = ++i + 1;
Om i == 3 innan yttrycket, så är i == 4 eller i == 5 efter uttrycket.

Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
x[i] = i++;
x[2] == 2 eller x[2] == 3 efter uttrycket.

Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
a[i] = b[i++];
a[n] == b[n] eller a[n] == b[n + 1] efter uttrycket

Evalueringsordningen inom ett uttryck (egentligen mellan "sequence points", t.ex. ';') är odefinierad i C. Detta gör pre- & postfix increment/decrement farliga! Använd i =+ 1; på en separat rad istället.


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 13.45 2013-05-18 
Användarvisningsbild

Blev medlem: 20.23 2005-08-06
Inlägg: 24351
Ort: Oskarshamn (En bit utanför)
bit96:
"Exempel 2 av TomasL belyser ett problem, men det är inte slumpmässigt och inte en bugg."

Med "bugg" så menar TomasL säkerligen att det kan leda till en bugg i ens egna kod/program.


TomasL:
Bra initiativ! :tumupp: Jag klistrade.


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 15.08 2013-05-18 
Användarvisningsbild

Blev medlem: 11.56 2004-05-08
Inlägg: 2850
Ort: Stockholm
TomasL skrev:
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
uint32 i;
sint16 arr[..]
i = arr[x++]
i |= arr[x++]<<16

Ovanstående kod funkar, kanske ibland, ibland alltid, ibland inte, ibland funkar den slumpmässigt.
Problemet uppstår när den högre byten i "arr" inte är 0, eftersom "i" i detta fallet är 32 bitar, och man läser in från en 16 bitars array, så kan vad som helst hamna i de övre 16 bitarna i variabeln "i",
Samma gäller ju även om "arr" är en vanlig variabel, då får de övre 16 bitarna "slumpmässiga" värden, dvs värden från dataomrrådet intill "arr".
för att slippa detta måste man naturligtvis casta 16bitars variabeln, så man enbart läser in 16 bitar, dvs
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
uint32 i;
sint16 arr[..]
i = (uint16)arr[x++]
i |= arr[x++]<<16

Det finns problem med exempelkoden, men jag tror inte heller att TomasLs förklaring (eller fix) är korrekt.

Innan operatorer appliceras på en heltalsoperand så uppgraderas typen ("type promotion") enligt följande:

signed char, short, signed bitfield --> int
char, unsigned char, unsigned short, unsigned bitfields --> int (om alla originaltypens möjliga värden får plats) eller unsigned int annars.

Alltså (givet sizeof(unsigned int) == 4 och sizeof(short) == 2):
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
  1. unsigned int i;
  2. short arr[..];
  3. i = arr[x++]; /* typeof(arr[..]) --> int */
  4. i |= arr[x++]<<16; /* Här shiftar vi en int (signed) */

Det finns flera problem här:
Rad 3: Type conversion int -> unsigned int. OK om originalvärdet är >= 0. Ingen varning från kompilatorn (definierat och korrekt enligt C-standarden).
Rad 4a: Bit-shift av signed är odefinierat om värdet antingen är negativt från början eller om det shiftas in en 1:a i sign bit. Här är det alltså viktigt att casta till unsigned int (ej unsigned short om jag förstår rätt).
Rad 4b: Type conversion int -> unsigned int. OK om originalvärdet är >= 0.

Originalkoden använder dessutom icke-standard-typer vilket gör det omöjligt att se hur koden kommer att fungera. Även standard (C99) fixed-width-typer (int16_t, uint32_t) är definierade i termer av bastyperna int etc, så även här kommer koden att bete sig olika på olika plattformar.


Slutsats 1: Använd alltid unsigned int för bit-operationer (lite yxigt men säkert!). Alternativt: skifta aldrig upp så att resultatet fyller alla bitar i den "uppgraderade" typen, och aldrig ett negativt värde.

Slutsats 2: Vill du ändra signedness, casta till int eller unsigned int, även om både originaltypen och resultattypen är kortare. Mer generellt: Använd om möjligt variabler av typen int/unsigned int, så slipper du otrevliga överraskningar.

Slutsats 3: Undvik typedefs eftersom de gör koden svårare att förstå (sällsynta undantag finns dock, t.ex. funktionspekare).

Edit: la till Slutsats 2 & 3.


Senast redigerad av arvidb 15.34 2013-05-18, redigerad totalt 1 gång.

Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 15.12 2013-05-18 

Blev medlem: 18.26 2006-07-04
Inlägg: 23239
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
i = ++i + 1;


Leder till i += 2;
Eftersom först inkrementeras i++ med ett och därefter läggs +1 till vilket resulterar 2 mer än man började med. ++ har högre precedens än + om jag minns rätt.

De övriga exemplen arvidb visar upp markerar betydelsen över att fundera på evalueringsordningen.


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 16.37 2013-05-18 
Användarvisningsbild

Blev medlem: 11.56 2004-05-08
Inlägg: 2850
Ort: Stockholm
Ok, efter lite mer googling:

Resultatet av uttrycket 'i = ++i + 1' (liksom av 'i = i++ + 1') är odefinierat i C-standarden, eftersom två tilldelningar till samma variabel sker utan sekvenspunkt emellan. Resultatet kan alltså vara att inget alls händer, att det motsvarar i += 2, att programmet kraschar eller något annat, beroende på kompilator och månens fas...


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 16.45 2013-05-18 

Blev medlem: 18.26 2006-07-04
Inlägg: 23239
Gäller visst att se upp.. ;)


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 16.58 2013-05-18 
EF Sponsor
Användarvisningsbild

Blev medlem: 22.54 2006-09-23
Inlägg: 30207
Ort: Borås
Citera:
TomasL: Är du säker på att du tänker rätt om exempel 2? För mig förefaller det mycket märkligt att kopiering av en 16 bitsvariabel till en 32 bitsvariabel inte skulle rensa de övre 16 bitarna i destinationen.

Det gör den inte (i alla fall inte gcc), det var därför jag hittade felet.

Beträffande mina typer uint32, sint16 osv, är det en mycket bra skola att INTE använda de inbyggda typerna, eftersom konstigheter kan uppkomma när man porterar kod.
I C är det väl i princip bara "char" som har samma längd oavsett system.

Därför skall man använda egna typer, där man vet längden, därav till exempel "uint32", som namnet framgår är det en unsigned int, vilken är 32 bitar lång, oavsett vilket system den körs på.


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 17.16 2013-05-18 
Användarvisningsbild

Blev medlem: 11.56 2004-05-08
Inlägg: 2850
Ort: Stockholm
blueint skrev:
Gäller visst att se upp.. ;)

Jupp. :) Vill man skriva buggfri kod så är det faktiskt bra att låta bli "++" och "--".

En tänkbar mekanism bakom att man får resultatet i += 1:

Operationerna som krävs för '++':
A1: Hämta värdet av i
A2: Addera 1
A3: Spara nya värdet till minne

Operationerna som krävs för '+1':
B1: Hämta värdet av ++i
B2: Addera 1
B3: Spara nya värdet till minne

Dessa är inte sekvenserade (förutom att A2 antagligen måste göras innan B1). Om de görs i denna ordning blir det fel:

A1: Hämta värdet av i
A2: Addera 1
B1: Hämta värdet av ++i
B2: Addera 1
B3: Spara nya värdet till minne
A3: Spara nya värdet till minne

Det är antagligen mer komplicerat i verkligheten. :)

Edit: Här är en mycket bättre förklaring till vad som händer: http://c-faq.com/~scs/cgi-bin/faqcat.cgi?sec=expr


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 17.40 2013-05-18 

Blev medlem: 18.26 2006-07-04
Inlägg: 23239
TomasL skrev:
Beträffande mina typer uint32, sint16 osv, är det en mycket bra skola att INTE använda de inbyggda typerna, eftersom konstigheter kan uppkomma när man porterar kod.
I C är det väl i princip bara "char" som har samma längd oavsett system.


Är det "int" mm som du avser med inbyggda typer och "uint32" som egen typ?

Är ju vanligt att typer definieras som:
Kod: [Expandera/Minimera] [Hämta] (Untitled.txt)
typedef unsigned char ui32[4];

int main(){
ui32  no;
 printf("len=%d\n", sizeof(no));
return 0;
}


Alternativt: typedef unsigned int ui32; /* Förutsatt att int är 32-bit vilket måste kontrolleras */

T.ex if( sizeof(int) != 4 ) { printf("FAIL!\n"); exit(1); }

Länkar:
http://publications.gbdirect.co.uk/c_bo ... pedef.html
http://www.tutorialspoint.com/cprogramm ... ypedef.htm


Upp
 Profil  
 
 Inläggsrubrik: Re: Enkla men fatala buggar
InläggPostat: 17.44 2013-05-18 
Användarvisningsbild

Blev medlem: 11.56 2004-05-08
Inlägg: 2850
Ort: Stockholm
TomasL skrev:
Citera:
TomasL: Är du säker på att du tänker rätt om exempel 2? För mig förefaller det mycket märkligt att kopiering av en 16 bitsvariabel till en 32 bitsvariabel inte skulle rensa de övre 16 bitarna i destinationen.

Det gör den inte (i alla fall inte gcc), det var därför jag hittade felet.
Troligtvis är det något annat som var fel? Din beskrivning av att "värden från dataområdet intill "arr"" används stämmer i alla fall inte. Det låter snarare som att du beskriver en typecast från (sint16 *) till (uint32 *) - alltså mellan olika pekartyper? Derefererar du sådana så får du precis den effekt som du beskriver (givet att du inte får aliasingproblem... :roll:)

TomasL skrev:
Beträffande mina typer uint32, sint16 osv, är det en mycket bra skola att INTE använda de inbyggda typerna, eftersom konstigheter kan uppkomma när man porterar kod.
I C är det väl i princip bara "char" som har samma längd oavsett system.

Därför skall man använda egna typer, där man vet längden, därav till exempel "uint32", som namnet framgår är det en unsigned int, vilken är 32 bitar lång, oavsett vilket system den körs på.
Om man är beroende av en viss bitlängd - som i ditt fall - så kan jag hålla med om att koden blir tydligare av att använda just fixed width-typer. Det jag är mest allergisk mot är den typiska typedef struct {} new_type; ...

Dock måste man komma ihåg att fixed width-typer inte är tillräckligt för att få samma beteende på en annan plattform. Du kan fortfarande få ändrat beteende p.g.a. annorlunda type promotion, som beskrivet ovan.


Upp
 Profil  
 
Visa inlägg nyare än:  Sortera efter  
Svara på tråd  [ 145 inlägg ]  Gå till sida 1, 2, 3, 4, 5 ... 10  Nästa

Alla tidsangivelser är UTC + 1 timme


Vilka är online

Användare som besöker denna kategori: Inga registrerade användare och 1 gäst


Du kan inte skapa nya trådar i denna kategori
Du kan inte svara på trådar i denna kategori
Du kan inte redigera dina inlägg i denna kategori
Du kan inte ta bort dina inlägg i denna kategori
Du kan inte bifoga filer i denna kategori

Sök efter:
Hoppa till:  
   
Drivs av phpBB® Forum Software © phpBB Group
Swedish translation by Peetra & phpBB Sweden © 2006-2010