Enkla men fatala buggar

PIC, AVR, Arduino, Raspberry Pi, Basic Stamp, PLC mm.
Användarvisningsbild
TomasL
EF Sponsor
Inlägg: 45291
Blev medlem: 23 september 2006, 23:54:55
Ort: Borås
Kontakt:

Re: Enkla men fatala buggar

Inlägg av TomasL »

Är det "int" mm som du avser med inbyggda typer och "uint32" som egen typ?
Svar: Ja.
Användarvisningsbild
arvidb
Inlägg: 4537
Blev medlem: 8 maj 2004, 12:56:24
Ort: Stockholm

Re: Enkla men fatala buggar

Inlägg av arvidb »

blueint skrev:typedef unsigned char ui32[4];
... fast detta ger ju typen "pekare till array av unsigned char", så den går inte att använda som 32-bitstal utan en massa typecasting. (Don't do it! ;))
Användarvisningsbild
stekern
Inlägg: 453
Blev medlem: 2 november 2008, 08:24:18
Ort: Esbo, Finland

Re: Enkla men fatala buggar

Inlägg av stekern »

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.
Nej, inte ens "char" har nödvändigtvis samma längd oavsett system, det är bara definierat att den skall vara >= 8 bitar.
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.
Varför inte använda de standardiserade uint32_t och int16_t?
Användarvisningsbild
stekern
Inlägg: 453
Blev medlem: 2 november 2008, 08:24:18
Ort: Esbo, Finland

Re: Enkla men fatala buggar

Inlägg av stekern »

Jag kan ju också bidra med en enkel men fatal bugg, som finns i gcc.

Något förenklat ser den ut såhär:

Kod: Markera allt

void f0(int x, int y);
void f1(int x, int y);

void f2(int x, int y)
{
  void (*f) (int, ...) = f0;

  f1(x+1, y+1);
  f(x, y);
}
Detta fungerar av en slump på ett stort antal arkitekturer som hanterar variabla argument (t.o.m. ett visst antal argument) på samma sätt som
icke variabla, men det är inte alls garanterat att så är fallet (t.ex. så fungerar detta inte på OpenRISC, där jag drabbades av denna bugg).

Det finns en bugg-rapport gjord för 10 år sedan, med en uppdatering 2007, men inget har hänt sen dess.
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=12081
blueint
Inlägg: 23238
Blev medlem: 4 juli 2006, 19:26:11
Kontakt:

Re: Enkla men fatala buggar

Inlägg av blueint »

Fast definitionen "void (*f) (int, ...)" är ju egentligen en icke-definition eftersom den inte bestämmer hårt vad slags argument funktionspekaren skall ha.
Användarvisningsbild
Swech
EF Sponsor
Inlägg: 4694
Blev medlem: 6 november 2006, 21:43:35
Ort: Munkedal, Sverige (Sweden)
Kontakt:

Re: Enkla men fatala buggar

Inlägg av Swech »

Hade det inte varit ide att lägga buggarna i WIkin när ni dividerat klart om huruvida buggarna
är buggar eller ej

Swech
Användarvisningsbild
stekern
Inlägg: 453
Blev medlem: 2 november 2008, 08:24:18
Ort: Esbo, Finland

Re: Enkla men fatala buggar

Inlägg av stekern »

blueint skrev:Fast definitionen "void (*f) (int, ...)" är ju egentligen en icke-definition eftersom den inte bestämmer hårt vad slags argument funktionspekaren skall ha.
Förstår inte riktigt vad du försöker poängtera?
Det är klart det är en definition, det definierar en funktionspekare till en funktion med variabla argument.
Användarvisningsbild
kimmen
Inlägg: 2042
Blev medlem: 25 augusti 2007, 16:53:51
Ort: Stockholm (Kista)

Re: Enkla men fatala buggar

Inlägg av kimmen »

arvidb skrev: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...
Just det. Och det verkar vara samma del av standarden som gäller även för dina andra två exempel där en array indexeras med i samma uttryck. Standarden säger:
Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.
Användarvisningsbild
kimmen
Inlägg: 2042
Blev medlem: 25 augusti 2007, 16:53:51
Ort: Stockholm (Kista)

Re: Enkla men fatala buggar

Inlägg av kimmen »

Angående originalinläggets:

Kod: Markera allt

uint32 i;
sint16 arr[..]
i = arr[x++]
i |= arr[x++]<<16
Första tilldelningen kommer att läsa en sint16 ur arraryen och lagrar det värdet omvandlat till uint32 i i. Detta är det första problemet, eftersom negativa tal enligt C-standard hanteras som att unsigned-variabeln slår runt. -1 från arrayen omvandlas då till exempel till 0xFFFFFFFF, vilket troligen var oönskat. Så en cast till uint16 är ju en möjlighet, men man kan ju också fundera på varför arrayen är signed om talen ändå skall tolkas som unsigned i slutändan?

Sedan andra radens beteende beror på storleken på int. Är int 16 bitar kommer ingen ytterligare integer promotion att utföras och shiften utförs som en 16-bitars skift. Det är dock undefined behavior att skifta en 16-bitars integer 16 steg eller mer, så här kan vad som helst hända (inte ens begränsat till att i får fel värde).

Är int 32 bitar kommer istället sint16 expanderas till int (sint32) och skiften utföras. Om talet ur arrayen är negativt eller skiften ger overflow är detta också undefined behavior enligt C-standarden på samma sätt som ovan, men använder man GCC är detta väldefinierat och kommer att ge det resultat som verkar vara det önskade. Se nedan, men observera min fetmarkering!
The results of some bitwise operations on signed integers (C90 6.3, C99 6.5).

Bitwise operators act on the representation of the value including both the sign and value bits, where the sign bit is considered immediately above the highest-value value bit. Signed ‘>>’ acts on negative numbers by sign extension.

GCC does not use the latitude given in C99 only to treat certain aspects of signed ‘<<’ as undefined, but this is subject to change.
http://gcc.gnu.org/onlinedocs/gcc/Integ ... ementation

Vill man inte göra några antaganden om miljö och endast använda standard-grejer bör koden vara:

Kod: Markera allt

uint32_t i;
int16_t arr[..];
i = (uint16_t)arr[x++];
i |= ((uint32_t)(uint16_t)arr[x++])<<16;
Observera omvandlingen i två steg till uint32_t! Utan den kommer andra raden få samma problem som första raden hade i originalet!
Användarvisningsbild
jesse
Inlägg: 9235
Blev medlem: 10 september 2007, 12:03:55
Ort: Alingsås

Re: Enkla men fatala buggar

Inlägg av jesse »

Rent generellt kan man väl säga att man bör undvika alla former av uttryck som inte ger ett definierat resultat i C. Annars kommer väl fler och fler mer eller mindre "avancerade" exempel upp här där resultatet är odefinierat. Håll er till kod som är definierad.

Sen vet jag inte om "void (*f) (int, ...)" hör hemma i den här tråden... Det var ju "enkla men fatala buggar", men använder man uttryck som detta så ska man nog också vara påläst om vad det innebär. Och med "buggar" menar alltså TS buggar i det egna programmet, inte buggar i GCC.

----- next subject ----

Personligen anser jag väl annars att ett av de största misstagen man gjorde när man skapade C var att samma definition av variabel är helt okila beroende på plattform. int kan vara 16, 32 eller 64 bitar, char kan vara 8 eller 16 och ibland kan även char vara unsigned.

Ett enkelt men fatalt fel jag har gjort har varit just på grund av denna korkade char:

Kod: Markera allt

char x;
if (x == 0x81) ... 
Detta uttryck blir alltid falskt, eftersom char bara kan anta värden mellan -127 och +128.

(om char är definierat som signed char alltså, vilket det är i GCC per default. Man kan komma förbi detta genom att ha -funsigned-char som parameter för GCC vid kompileringen. Men då gäller det ju att man alltid har det, annars står man ju där med skägget i brevlådan nästa gång i alla fall!)

För att vara helt garanterad från sådana fel borde man alltså skriva:

Kod: Markera allt

char x;
if ((uint8_t)x == 0x81) ... 
eller ännu hellre, skippa char helt och hållet i programmet:

Kod: Markera allt

uint8_t  x;
if (x == 0x81) ... 
blueint
Inlägg: 23238
Blev medlem: 4 juli 2006, 19:26:11
Kontakt:

Re: Enkla men fatala buggar

Inlägg av blueint »

Så här kan man också göra:

Kod: Markera allt

unsigned char ch;
if( ch == 0x81 )
  printf("Seems it's an unsigned day today ;-)\n");
Användarvisningsbild
bit96
Inlägg: 2492
Blev medlem: 3 september 2007, 10:04:29
Ort: Säffle

Re: Enkla men fatala buggar

Inlägg av bit96 »

blueint: Ja, men nu glömde du att klamra in det som kommer efter "if()".
Inte nödvändigt, men rekommenderat för att undvika fatala buggar.
:)
Användarvisningsbild
Micke_s
EF Sponsor
Inlägg: 6741
Blev medlem: 15 december 2005, 21:31:34
Ort: Malmö

Re: Enkla men fatala buggar

Inlägg av Micke_s »

Körde en funktionspekare i c som en callback i ett interrupt på en ARM cortex-m3

Problemet var att gcc tyckte att länkregistret(LR) var ledigt att använda eftersom den inte hittade några subanrop i interruptet.
När interruptet skulle lämnas så var länkregistret(LR) trashat och den hoppade till en illegal address och jag fick ett "hard fault".

Så kan det gå när gcc optimerar.

Löste det genom att ha en liten subrutin som är deklarerad noinline i gcc.
blueint
Inlägg: 23238
Blev medlem: 4 juli 2006, 19:26:11
Kontakt:

Re: Enkla men fatala buggar

Inlägg av blueint »

Brukar man inte antingen blockera registren, typ "register R8" är min! ;)

Eller trycka kopia på alla register vid anrop, och sedan kopiera tillbaks så att inget förstörs?

GCC optimeringen lät iaf lite väl "för smart".
Användarvisningsbild
stekern
Inlägg: 453
Blev medlem: 2 november 2008, 08:24:18
Ort: Esbo, Finland

Re: Enkla men fatala buggar

Inlägg av stekern »

jesse skrev: Sen vet jag inte om "void (*f) (int, ...)" hör hemma i den här tråden... Det var ju "enkla men fatala buggar", men använder man uttryck som detta så ska man nog också vara påläst om vad det innebär. Och med "buggar" menar alltså TS buggar i det egna programmet, inte buggar i GCC.
Det är väl ett hyfsat enkelt misstag att råka casta en void (*f) (int, int) till en void (*f) (int, ...)?
Och resultatet kan vara fatalt under vissa omständigheter.
Varför måste exemplen vara från program skrivna av en själv?
(För att klargöra, buggen finns i gccs kod, det är alltså inte så att gcc gör något fel med uttrycket)
Micke_s skrev:Körde en funktionspekare i c som en callback i ett interrupt på en ARM cortex-m3

Problemet var att gcc tyckte att länkregistret(LR) var ledigt att använda eftersom den inte hittade några subanrop i interruptet.
När interruptet skulle lämnas så var länkregistret(LR) trashat och den hoppade till en illegal address och jag fick ett "hard fault".

Så kan det gå när gcc optimerar.

Löste det genom att ha en liten subrutin som är deklarerad noinline i gcc.
Det där låter konstigt tycker jag, kompilatorn borde inte göra skillnad på funktionsanrop genom funktionspekare och "vanliga" funktionsanrop.
I alla fall inte med avseende på nödvändigheten att spara undan LR.
Så antingen är det nåt som fattas i din beskrivning, eller så har du helt enkelt stött på en bugg i gcc.
Skriv svar