Vraagje over C....

Iemand bekend met de C taal? (ik voel me normaliter wel aangesproken, maar vandaag even niet.....)

Ik dacht dat:

c code:

 unsigned char ch;
  for (int i=0, ch = 0;i<8;i++) ch |= ((c >> i ) &1) << (7-i);

c code:

 unsigned char ch;
  ch = 0;
  for (int i=0;i<8;i++) ch |= ((c >> i ) &1) << (7-i);

hetzelfde zou moeten zijn. Wat doe ik verkeerd?

Gcc-voor-ARM en 'voor-X86-64 doen precies hetzelfde. te weten: ch wordt in het eerste geval kennelijk NA de loop nog een keer op nul gezet.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Ik ben niet zo actief meer op een toetsenbord, maar het verschil lijkt me te zijn dat in het eerste voorbeeld "ch" wordt gebruikt als "int"
en in het 2e voorbeeld als een "char".

Maar ik hou me aanbevolen voor het juiste antwoord op deze interessante vraag...

Overigens zie ik de constructie van het 1e voorbeeld voor het eerst.

[Bericht gewijzigd door Boudie op zondag 14 mei 2023 14:55:57 (12%)

Vervangen DOOR.

code:

int i=0, ch = 0;

Hier wordt een nieuwe int ch aangemaakt die alleen bestaat binnen de scope van de for statement.

Na de for lus bestaat die niet meer en blijft alleen nog de unsigned char ch over, en die is niet geïnitialliseerd.

Zo kan het wel:

code:


unsigned char ch;
int i;
  for (i=0, ch = 0;i<8;i++) ch |= ((c >> i ) &1) << (7-i);

[Bericht gewijzigd door deKees op zondag 14 mei 2023 15:26:36 (20%)

Edit: ik had hem al door na boudie z'n post, maar dekees heeft hem ook in 1x door...

Als eerste:
je mag:

c code:

  for (i=0, tot = 0; i< 10;i++) tot += mydata[i];

doen: er zijn nu twee initializatie statements.

F***

Ik denk dat je hem gevonden hebt.

mogelijk als ik hem anders schrijf:

for (ch=0, int i=0; ....

dat het dan wel werkt. Maar wat de compiler nu interpreteert is:

unsigned char piet;

for (int i=0, klaas=0; .... ) klaas = ...

en ik maar de variabele piet bekijken en me afvragen waarom die geen waarde heeft gekregen.

ik bedoel dus "init_statement1 , init_statement2" als eerste stukkie voor de ; in de for lus, met "int i" als statement1 en "ch=0" als statement 2. Maar de compiler ziet "int i=0, ch=0" als de declaratie van twee int variabelen.

Als ik de statements omdraai dan vind ie me niet lief unexpected expression before int.

@dekees: Of ik nu de declaratie van "int i" of de initializatie van ch buiten de lus haal maakt niet zo veel uit.

Mijn voorkeur gaat uit naar code die "compact, maar niet /te/" is...

code:

 unsigned char ch=0;
 for (int i=0;i<8;i++) ... 

edit: "typefout" in de laatste coderegel gefixed.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 14 mei 2023 15:28:44 schreef rew:...
Mijn voorkeur gaat uit naar code die "compact, maar niet /te/" is...

code:

 unsigned char ch=0;
 for (i=0;i<8;i++) ... 

Zo is 'ie mooi en ook voor ouwe løllen leesbaar. :)

Vervangen DOOR.

code:

unsigned char ch=0;
 for (i=0;i<8;i++) ... 

Zo istie wel mooi, maar het werkt niet want i is dan niet gedeclareerd. :)

Je kan ze allebei buiten de for declareren. Dan kun je de initialisatie binnen de for doen, want dan wordt het een assignment ipv een declaratie.

Maar dan is het mooie er wel van af jammer genoeg 8)7 .

Sorry. "typefout". Net wat te snel "inline" hier wat zitten tikken.

Dit soort fouten vind ik niet erg om te maken. De compiler komt met een waarschuwing. De fout die de aanleiding is voor dit topic is VEEL maar dan ook VEEL vervelender. Wat nu als die forlus niet "vrijwel altijd" maar "heel zelden" een aanpassing doet aan de ch? (i.e. hij hoort bijna altijd nul te zijn in de vervolgcode). Geen enkele hint van de compiler dat ik iets verkeerd doe. Waarom ik trouwens geen "uninitialized variable" op mijn code heb gezien snap ik trouwens niet.

Anyway, ik dacht dat ik hem maar 1x nodig had. Nee, op de terugweg moet het "weer". dus maar een functie gemaakt: Dit is nu m'n "productie" code.

c code:


unsigned char bswap (unsigned char c)
{
  unsigned char ch=0;
  for (int i=0;i<8;i++) ch |= ((c >> i ) &1) << (7-i);
  return ch;
}
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Ik heb de oorspronkelijke code in een funktie gegoten (Avr-gcc), en dan krijg ik wel een warning:

code:


unsigned char DoTest(unsigned char c)
{  unsigned char ch;
   for (int i=0, ch = 0;i<8;i++)
      ch |= ((c >> i ) &1) << (7-i);
   return ch;
}

Geeft warning:

code:


SourceFiles/Main.cpp: In function ‘unsigned char DoTest(unsigned char)’:
SourceFiles/Main.cpp:226:11: warning: ‘ch’ is used uninitialized in this function [-Wuninitialized]
    return ch;
           ^

Op 14 mei 2023 14:51:23 schreef Boudie:
Overigens zie ik de constructie van het 1e voorbeeld voor het eerst.

Dat is precies de reden dat ik niet van dit soort constructies hou. Dan maar een paar regeltjes code extra. Dan snap ik over een paar jaar of maanden ook nog wat ik uitgespookt heb.

Op 14 mei 2023 20:36:47 schreef deKees:
Ik heb de oorspronkelijke code in een funktie gegoten (Avr-gcc), en dan krijg ik wel een warning:

Dan zal rew de warnings of dit type warnings afgezet hebben.

Normaliter, als ik controle over m'n Makefile heb, dan staat -Wall gewoon aan.

Dit is een RP2040 project. Ik moet een CMakeLists.txt file maken, die dan op "magische" wijze tot een onleesbaar Makefile gemanipuleerd wordt. Ik hou me aanbevolen voor tips die me de -Wall weer aan laten zetten. /daarom/ wil ik die -Wall aan hebben. In 95% van de gevallen is het een onzinnige waarschuwing (ja dat bedoel ik zo), of "rustig, ik moet de code die ch gebruikt nog schrijven".

Maar zo nu en dan scheelt het een uur of meer aan debugging. Zoals vandaag.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

ESP-IDF gebruikt ook Cmake.

Daar kun je compiler opties setten in de CMakelist.txt dmv:

code:

...
list(APPEND cxx_compile_options "-Wall")
...
Lucky Luke

Golden Member

Ik zou verwachten dat het gewoon in hardware kan, en na een duik in de datasheet van de rp2040 blijkt dat inderdaad te kunnen: De ‘mov’ instructie kan ‘bit reverse’.

Bit reverse sets each bit n in Destination to bit 31 - n in Source, assuming the bits are numbered 0 to 31.

https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#page320

Eluke.nl | De mens onderscheid zich van (andere) dieren door o.a. complexe gereedschappen en bouwwerken te maken. Mens zijn is nerd zijn. Blijf Maken. (Of wordt, bijvoorbeeld, cultuurhistoricus)

Op 14 mei 2023 22:50:45 schreef Lucky Luke:
Ik zou verwachten dat het gewoon in hardware kan, en na een duik in de datasheet van de rp2040 blijkt dat inderdaad te kunnen: De ‘mov’ instructie kan ‘bit reverse’.

Een goede compiler zet die hele functie om in precies 1 instructie als de target CPU een bit reversal kan doen.

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.

Op 14 mei 2023 18:45:52 schreef rew:
Dit soort fouten vind ik niet erg om te maken. De compiler komt met een Wat nu als die forlus niet "vrijwel altijd" maar "heel zelden" een aanpassing doet aan de ch? (i.e. hij hoort bijna altijd nul te zijn in de vervolgcode). Geen enkele hint van de compiler dat ik iets verkeerd doe. Waarom ik trouwens geen "uninitialized variable" op mijn code heb gezien snap ik trouwens niet.

Als je de -Wshadow optie in de makefile (of CMakelists.txt) aan zet krijg je WEL een waarschuwing.

Want je maakt een NIEUWE declaratie van een al bestaande variable, maar dan in een andere scope. En dat is vrijwel altijd een bug in je code.

[Bericht gewijzigd door henri62 op zondag 14 mei 2023 23:15:30 (13%)

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.

Ja, dat is in de PIO. Dus dan zou ik de SPI die ik nu gebruik in PIO moeten programmeren (dat kan!) en dan gewoon ook die omgekeerde bitvolgorde programmeren.

Nu is m'n CPU op 125 MHz een paar clockjes bezig om de data om te draaien. Hmm. Ik zie "gaten" in de transmissie, dat zou wel eens dat omdraaien kunnen zijn.

code:


unsigned char bswap (unsigned char c)
{
  c = ((c & 0xf0) >> 4) | ((c & 0x0f) << 4);
  c = ((c & 0xcc) >> 2) | ((c & 0x33) << 2);
  c = ((c & 0xaa) >> 1) | ((c & 0x55) << 1);
  return c;
}

Zou dat sneller zijn?

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Deze twee code blokken doen hetzelfde:

c code:


uint8_t revbits1(uint8_t c)
{
  uint8_t ch = 0;
   for (int i = 0; i < 8; i++)
   {
      ch |= ((c >> i ) & 1) << (7-i);
   }
   return ch;
}

uint8_t revbits2(uint8_t c)
{
    uint8_t ch = 0;
    ch |= (c & 0x01) << 7;
    ch |= (c & 0x02) << 5;
    ch |= (c & 0x04) << 3;
    ch |= (c & 0x08) << 1;
    ch |= (c & 0x10) >> 1;
    ch |= (c & 0x20) >> 3;
    ch |= (c & 0x40) >> 5;
    ch |= (c & 0x80) >> 7;

    return ch;
}

Nu de humor: De gegenereerde code is EXACT hetzelfde. Zelfs als je van alle ORs een += maakt komt er nog steeds hetzelfde uit.

-edit-
Even deze er ook bij gezet (komt volgens mij ergens van het internet af als 'textbook' example voor optimized code?):

c code:


uint8_t revbits3(uint8_t c)
{
  c = ((c & 0xf0) >> 4) | ((c & 0x0f) << 4);
  c = ((c & 0xcc) >> 2) | ((c & 0x33) << 2);
  c = ((c & 0xaa) >> 1) | ((c & 0x55) << 1);
  return c;
}

Ook exact hetzelfde als de andere 2 kwa gegenereerde assembly.
Ik gebruik de compiler explorer (https://godbolt.org/) om dit te testen. Helaas zit daar nog geen RP2040 bij als target en moet je denk ik -mcpu=cortex-m0plus opgeven.
Maar de compiler genereerd gewoon een vracht instructies en niks bijzonders.

Moraal van het verhaal: Als je het gewoon uitschrijft op de begrijpelijke manier, maakt een goede compiler er de meest optimale code van. (Voor de gegeven target cpu)
In dit geval is revbits2() denk ik de meest leesbare.

Zou dat sneller zijn?

Nee dus.

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.

Op 14 mei 2023 23:44:47 schreef henri62:
Nu de humor: De gegenereerde code is EXACT hetzelfde. Zelfs als je van alle ORs een += maakt komt er nog steeds hetzelfde uit.

Nu de hamvraag: "Is de resulterende code geoptimaliseerd op snelheid (revbits2) of op geheugenruimte (revbits1)?"

Of je kiest een geschikte snippet van:
https://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious

En stopt een link naar die site in je code (evt. via WebArchive).

@Ohm pi. Ik heb voldoende flash geheugen dat ik een keer als grap de hichhikers guide to the galaxy, full text, ongecomprimeerd er bij heb gedaan. Dat werd vervelend: Het uploaden van m'n programma duurde dan een paar seconden langer.

@blurp. Heel ingenieuze truuks zitten er tussen. Omdat dit een 32bit CPU is, denk ik niet dat de 64bit truken werken.

@henry. Mijn arm compiler maakt er een loop van die 12 instrucites 8 keer uitvoert: 96 instructies.

De uitgeschreven bitshifts zijn maar 24 instructies.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
PE9SMS

Special Member

Op 15 mei 2023 00:35:20 schreef ohm pi:
[...]Nu de hamvraag: "Is de resulterende code geoptimaliseerd op snelheid (revbits2) of op geheugenruimte (revbits1)?"

Dat ligt eraan of je -O3 of -Os meegeeft zou ik denken?

This signature is intentionally left blank.

Ja en nee.
Hij maakt de code inderdaad kleiner als je -Os op geeft. Dus een lus van 4 iteraties zal ie niet meer uitrollen hetgeen ie met -O2 of -O3 wel doet.

Maar op veel processoren met een cache is kleinere code in het grote plaatje gewoon sneller. Dus dan maar liever 3 clocks extra per loop investeren om de code kleiner te houden. Als de code daardoor 20% kleiner wordt, dan wordt je cache effectief 20% groter en de performance winst van die effectief grotere cache is dus groter dan het performance verlies van die paar clocks door de loop-constructie.

Omdat niemand zin heeft dat op een per-fuctie basis te gaan beslissen ga je dus benchmarken op het hele ding en beslist dan dat -Os of -O2 beter is. Of je laat hem gewoon staan op waar ie stond. Als ik zelf m'n makefile maak staat ie op -O2. (dat was vroeger nodig om bepaalde foutmeldingen als "unused variable" te krijgen.)

Op 14 mei 2023 22:07:06 schreef deKees:
Daar kun je compiler opties setten in de CMakelist.txt dmv:

Ik heb het nu geprobeerd. Ik heb dat aan m'n CMakeLists.txt toegevoegd en volgens mij ziet cmake dat er niets veranderd is en daardoor heeft make niet m'n hele project opnieuw zitten compileren. De -Wall optie komt uiteindelijk niet in mijn compile command terecht.

Het blijkt te moeten zijn:

code:

add_compile_options(-Wall -Wextra -Wpedantic)

(dat is beter: het is gedocumenteerd als "the right way to add compile options"). Maar als laatste: Deze moet VOOR de "add_executable" regel staan. De extra dingen na -Wall heb ik niet verzonnen en zaten in het voorbeeld wat ik vond. Ik denk dat ze er weer uitgaan: Ik krijg duizenden waarschuwingen uit de build-environment. Die komt wel gewoon door de -Wall die ik normaal gebruik.

Het huidige project is een "port" van iets wat ik op STM32 heb ontwikkeld. -Wall heeft ondertussen al wat puntjes aangestipt die ik WIL fixen.

[Bericht gewijzigd door rew op maandag 15 mei 2023 13:35:30 (37%)

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 15 mei 2023 00:35:20 schreef ohm pi:
[...]Nu de hamvraag: "Is de resulterende code geoptimaliseerd op snelheid (revbits2) of op geheugenruimte (revbits1)?"

Nog even het antwoord: -O2 voor alle 3 varianten. => Exact dezelfde assembly code output. Ik kan het even niet zo snel meer reproduceren.

Op 15 mei 2023 13:16:09 schreef rew:
[...]Ik heb het nu geprobeerd. Ik heb dat aan m'n CMakeLists.txt toegevoegd en volgens mij ziet cmake dat er niets veranderd is en daardoor heeft make niet m'n hele project opnieuw zitten compileren.

Dan heb je iets fouts staan in je CMakeLists.txt file. Volgens mij moet je target_compile_options(<target> ....) gebruiken en aangeven voor WELKE target de opties moeten gelden.

Op 15 mei 2023 13:16:09 schreef rew: Ik krijg duizenden waarschuwingen uit de build-environment. Die komt wel gewoon door de -Wall die ik normaal gebruik.

Dan is er toch echt wat mis als je zoveel warnings krijgt. Of je hebt een gigantisch groot project.

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.

De raspberry pi pico "environment" die werkt niet echt met libraries. Er zijn allerlei libraries, maar die zijn niet precompiled, en de aangeraden cmake config trekt dus alle "standaard functies" naar binnen je project. Dus tussen 5 verschillende projecten wordt alle support dus 5x gecompileerd. Is niet zo'n probleem: in ieder project de eerste keer jast ie al dat spul door de compiler en vervolgens linkt ie het alleen maar.

Die "allemaal waarschuwingen" was met --pedantic. Ik doe dat normaliter nooit, maar iemand op het internet die wat van CMAKE afweet kennelijk wel. Dus zijn voorbeeld daar stond dat in. Ik heb dat voor de lol een keer aan laten staan en de resultaten van dat experiment hier gerapporteerd: Het geeft veel meldingen op code die wel clean door -Wall heenkomt.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

ESP_IDF doet dat ook voor de ESP32. En dat moet ook wel, want per project kun je een hele rits opties op en af zetten die resulteren in compiler switches voor de libraries en dus specifieke code per project.

Precies. Ik had niet uitgezocht waarom. Met dit soort dingen, als je het uitzoekt, dan kom je vrijwel altijd tot de conclusie: eigenlijk wel een prima beslissing. Dus dan heeft het alleen tijd gekost om uit te zoeken.

In dit geval kan je vast debug opties aanzetten en is het gewoon ook mogelijk om de ondersteuningsfuncties mee te debuggen.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/