Het artikel bespreekt de veelgemaakte fouten bij het implementeren van een terugwaartse loop in de programmeertaal C. De auteur legt uit dat het gebruik van sizet in een i >= 0 loop problematisch is vanwege de aard van unsigned integers, en dat het gebruik van ssizet (signed) leidt tot portabiliteits- en type-problemen.
De voorgestelde optimale oplossing is de volgende constructie: for(size_t i = count; i--; )
Door gebruik te maken van de postdecrement-operator wordt de loop veilig en efficiënt afgehandeld, waarbij de index altijd binnen de correcte grenzen blijft zonder dat er complexe type-conversies nodig zijn.
Terugwaarts itereren in C
Dit is korter dan sommige van mijn andere berichten, maar ik zie steeds mensen die dit fout doen. In het tijdperk van LLM's is dit zelfs erger geworden.
Stel dat je een buffer en een teller hebt en je moet hier om een of andere reden terugwaarts doorheen itereren:
_Bool find_last(size_t count, int *haystack, int needle, size_t *where);
Bijna iedereen kan de voorwaartse versie op de automatische piloot schrijven:
for(size_t i = 0; i < count; i++){
if(haystack[i] == needle){
*where = i;
return 1;
}
}
return 0;
Daarom proberen velen hetzelfde te doen, maar dan terugwaarts:
for(size_t i = count - 1; i >= 0; i--){
// zoek needle
}
Maar dat werkt helemaal niet! i >= 0 is tautologisch waar (moderne compilers zullen hiervoor waarschuwen). Erger nog: als count 0 is, wordt i onmiddellijk SIZE_MAX.
Oké, dus ze proberen de volgende versie:
for(ssize_t i = count - 1; i >= 0; i--){
// zoek needle
}
Dit werkt in de praktijk met een signed index, maar introduceert enkele problemen:
- Je indexvariabele is nu signed, wat problemen kan veroorzaken bij gebruik in expressies vanwege de conversieregels van C.
ssize_t is een POSIX-standaard, dus op Windows moet je dit zelf definiëren.
- Als je type 32-bit is, kun je tegen promotieproblemen aanlopen en misschien zelfs tegen truncatieproblemen.
- Deze schrijfwijze van de loop is overgecompliceerd en vereist elke keer een speciale inspectie. Heb je de conditie in de tweede clausule wel correct geformuleerd?
De juiste methode
Het vermijden van deze problemen brengt ons bij de eigenlijke, juiste manier om terugwaarts te itereren in C:
for(size_t i = count; i--; ){
// zoek needle
}
Dit is veel eenvoudiger! Laten we het analyseren:
De sleutel is de postdecrement-operator op i. Deze verlaagt i met één, maar geeft de waarde terug die i had vóór de operatie. In dit geval betekent dit dat i in het lichaam van de loop altijd binnen de grenzen valt, aangezien de waarde na initialisatie wordt verlaagd (waardoor deze maximaal count - 1 is), terwijl de conditie controleert of de waarde eerder 0 was; in dat geval eindigt de loop. i zal wrappen, maar unsigned wrap-around is goed gedefinieerd in C, dus dat is geen probleem.
De definitieve versie
_Bool find_last(size_t count, int *haystack, int needle, size_t *where){
for(size_t i = count; i--; ){
if(haystack[i] == needle){
*where = i;
return 1;
}
}
return 0;
}
Door het op deze manier te doen, hoef je niet te klooien met het converteren naar een signed loop-index. Kijk hoe simpel die loop is! Je kunt in één oogopslag zien of iemand de reverse loop correct heeft geïmplementeerd of niet.
Dus alsjeblieft! Als je iemand ziet die terugwaarts probeert te itereren, laat ze dan deze methode zien!
***
Alle code in dit artikel is vrijgegeven in het publieke domein.
Terugwaarts itereren in C
Dit is korter dan sommige van mijn andere berichten, maar ik zie steeds mensen die dit fout doen. In het tijdperk van LLM's is dit zelfs erger geworden.
Stel dat je een buffer en een teller hebt en je moet hier om een of andere reden terugwaarts doorheen itereren:
_Bool find_last(size_t count, int *haystack, int needle, size_t *where);
Bijna iedereen kan de voorwaartse versie op de automatische piloot schrijven:
for(size_t i = 0; i < count; i++){
if(haystack[i] == needle){
*where = i;
return 1;
}
}
return 0;
Daarom proberen velen hetzelfde te doen, maar dan terugwaarts:
for(size_t i = count - 1; i >= 0; i--){
// zoek needle
}
Maar dat werkt helemaal niet! i >= 0 is tautologisch waar (moderne compilers zullen hiervoor waarschuwen). Erger nog: als count 0 is, wordt i onmiddellijk SIZE_MAX.
Oké, dus ze proberen de volgende versie:
for(ssize_t i = count - 1; i >= 0; i--){
// zoek needle
}
Dit werkt in de praktijk met een signed index, maar introduceert enkele problemen:
- Je indexvariabele is nu signed, wat problemen kan veroorzaken bij gebruik in expressies vanwege de conversieregels van C.
ssize_t is een POSIX-standaard, dus op Windows moet je dit zelf definiëren.
- Als je type 32-bit is, kun je tegen promotieproblemen aanlopen en misschien zelfs tegen truncatieproblemen.
- Deze schrijfwijze van de loop is overgecompliceerd en vereist elke keer een speciale inspectie. Heb je de conditie in de tweede clausule wel correct geformuleerd?
De juiste methode
Het vermijden van deze problemen brengt ons bij de eigenlijke, juiste manier om terugwaarts te itereren in C:
for(size_t i = count; i--; ){
// zoek needle
}
Dit is veel eenvoudiger! Laten we het analyseren:
De sleutel is de postdecrement-operator op i. Deze verlaagt i met één, maar geeft de waarde terug die i had vóór de operatie. In dit geval betekent dit dat i in het lichaam van de loop altijd binnen de grenzen valt, aangezien de waarde na initialisatie wordt verlaagd (waardoor deze maximaal count - 1 is), terwijl de conditie controleert of de waarde eerder 0 was; in dat geval eindigt de loop. i zal wrappen, maar unsigned wrap-around is goed gedefinieerd in C, dus dat is geen probleem.
De definitieve versie
_Bool find_last(size_t count, int *haystack, int needle, size_t *where){
for(size_t i = count; i--; ){
if(haystack[i] == needle){
*where = i;
return 1;
}
}
return 0;
}
Door het op deze manier te doen, hoef je niet te klooien met het converteren naar een signed loop-index. Kijk hoe simpel die loop is! Je kunt in één oogopslag zien of iemand de reverse loop correct heeft geïmplementeerd of niet.
Dus alsjeblieft! Als je iemand ziet die terugwaarts probeert te itereren, laat ze dan deze methode zien!
***
Alle code in dit artikel is vrijgegeven in het publieke domein.