Vraagje over C....

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

Nou weet ik weer waarom ik een hekel aan C heb. Iedereen denkt een optimaal programma te maken als je alles in 1 regel duwt zonder enig commentaar. Leesbaarheid?

Dat je over 1 regel zo lang moet nadenken is best een gebrek van die taal.

In modern C mag je een variabele declareren waar je hem nodig hebt. Dit feature dat bestaat al 20 jaar, maar ik heb lang afgehouden dat te gebruiken.

Dit helpt met de modulariteit: een variabele die je alleen in de tweede helft van een routine nodig hebt, kan je aldaar waar je hem gaat gebruiken declareren. En je kan een variabele die je alleen in een loop nodig hebt binnen die loop declareren, zodat je ook compiler waarschuwingen krijgt als je die zou gebruiken buiten de loop.

Ik had de syntax van een variant hiervan niet goed begrepen en een foutje gemaakt.

Ik weet dat je dingen in C heel compact kan opschrijven. Veel van die dingen maken gebruik van "standaard" constructies. Als je die niet kent, dan zal je mogelijk even moeten puzzelen om te lezen wat er staat.

Mijn programmeer stijl is dat als iets makkelijk op 1 regel past, ik het liever op 1 regel schrijf dan uitsprijd over meer. Dat is persoonlijk.

Ik googlede even naar hoe dit in python zou moeten. Ik verwachtte een lang(er) verhaal te krijgen. Maar voor mijn punt eigenlijk nog veel beter: het kan in python ook in 1 regel! :

code:

int('{:08b}'.format(n)[::-1], 2)

Ik snap een beetje python, maar dit gaat me boven m'n pet.
bron.
Iemand anders gaf het antwoord wat ik verwachtte:

code:

def reverse_Bits(n, no_of_bits):
    result = 0
    for i in range(no_of_bits):
        result <<= 1
        result |= n & 1
        n >>= 1
    return result
# for example we reverse 12 i.e 1100 which is 4 bits long
print(reverse_Bits(12,4))
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
PE9SMS

Special Member

Op 24 mei 2023 13:03:41 schreef Hoeben:
Nou weet ik weer waarom ik een hekel aan C heb. Iedereen denkt een optimaal programma te maken als je alles in 1 regel duwt zonder enig commentaar. Leesbaarheid?

Dat je over 1 regel zo lang moet nadenken is best een gebrek van die taal.

Je mist de clou. De compilers zijn zo goed dat het voor het resultaat (de gegenereerde assembly) helemaal niet uitmaakt of je de source code compact op 1 regel schrijft of in stappen opdeelt over meerdere regels.

Zie ook het testje van henri62 eerder in dit topic.

Ergo: schrijf code die voor jou leesbaar is en maak je geen zorgen over efficientie. Dat regelt de compiler wel.

This signature is intentionally left blank.
benleentje

Golden Member

Op 24 mei 2023 13:03:41 schreef Hoeben:
Dat je over 1 regel zo lang moet nadenken is best een gebrek van die taal.

Nee dat heeft werkelijk niets met taal te maken, ja ik trek het algemeen.

Ik kan nu ook in allerlei woordenboeken gaan duiken en hier een Nederlandse zin neer zetten die dan bij door niemand te begrijpen is maar dan wel correct Nederlands is. Dat word het echt advocaten of doctors jargon.
De gemiddelde Nederlander is niet zo hoog opgeleid zodat het gebruik van dat soort woorden niet handig is en daarom is onze spreektaal een iets meer simplistische en algemene afspiegeling van het totaal wat de Nederlandse taal is.

Zo zie ik het ook met een programmeertaal, de gemiddelde programmeur is wat lager opgeleid en gebruik de meer algemene vorm die voor iedereen leesbaar is. Degene die er echt induiken en verder leren dan het gemiddelde kan dan korte code schrijven die een stuk moeilijker te begrijpen is.
Ligt dat dan aan C, natuurlijk niet, maar gewoon aan het niveau van de rest. ;).

De vraag of je dat moet willen en dat ook toepassen is meer een ethische kwestie.

Mensen zijn soms net als een gelijkrichter, ze willen graag hun gelijk hebben.

Op 24 mei 2023 13:03:41 schreef Hoeben:
for (i=0, ch = 0;i<8;i++) ch |= ((c >> i ) &1) << (7-i);

Dat je over 1 regel zo lang moet nadenken is best een gebrek van die taal.

Of gebrek aan kennis van de programmeur. Wat voor de een gesneden koek is, is voor de ander onbegrijpelijke code.
Bij mij hoort bovenstaande coderegel tot de onbegrijpelijk code. Een beetje commentaar zou voor mij wel handig zijn.

Op 24 mei 2023 17:07:26 schreef PE9SMS:
[...]... De compilers zijn zo goed dat het voor het resultaat (de gegenereerde assembly) helemaal niet uitmaakt of je de source code compact op 1 regel schrijft of in stappen opdeelt over meerdere regels.

Ik vraag me af of de compiler voor deze twee instructies dezelfde code genereert.

c code:


A = B || !B && C;
A = B || C;

[Bericht gewijzigd door ohm pi op woensdag 24 mei 2023 19:23:20 (32%)

Op 14 mei 2023 23:10:34 schreef henri62:
[...] Een goede compiler zet die hele functie om in precies 1 instructie als de target CPU een bit reversal kan doen.

Denk je ?
De compiler moet dan een heel stuk code analyseren en concluderen dat het een bit reversal is. Compilers zijn wel slim, maar ik vraag mij af of ze zo slim zijn.

Met wat inline assembly instructies kan je iets dergelijks vaak wel efficient implementeren als er hardware support is.

Op 24 mei 2023 19:17:26 schreef ohm pi:
[...]Of gebrek aan kennis van de programmeur. Wat voor de een gesneden koek is, is voor de ander onbegrijpelijke code.
Bij mij hoort bovenstaande coderegel tot de onbegrijpelijk code. Een beetje commentaar zou voor mij wel handig zijn.
[...]Ik vraag me af of de compiler voor deze twee instructies dezelfde code genereert.

c code:


A = B || !B && C;
A = B || C;

Ja, genereert dezelfde code (Microsoft C++):

c code:


int tst1(int B, int C)
{
        return B || !B && C;
}


int tst2(int B, int C)
{
        return B || C;
}

code:



; Listing generated by Microsoft (R) Optimizing Compiler Version 19.33.31630.0

include listing.inc

INCLUDELIB MSVCRT
INCLUDELIB OLDNAMES

PUBLIC  tst1
PUBLIC  tst2
; Function compile flags: /Ogtpy
;       COMDAT tst2
_TEXT   SEGMENT
B$ = 8
C$ = 16
tst2    PROC                                            ; COMDAT
; Line 11
        test    ecx, ecx
        jne     SHORT $LN3@tst2
        test    edx, edx
        jne     SHORT $LN3@tst2
        xor     eax, eax
; Line 12
        ret     0
$LN3@tst2:
; Line 11
        mov     eax, 1
; Line 12
        ret     0
tst2    ENDP
_TEXT   ENDS
; Function compile flags: /Ogtpy
;       COMDAT tst1
_TEXT   SEGMENT
B$ = 8
C$ = 16
tst1    PROC                                            ; COMDAT
; Line 5
        test    ecx, ecx
        jne     SHORT $LN4@tst1
        test    edx, edx
        jne     SHORT $LN4@tst1
        xor     eax, eax
; Line 6
        ret     0
$LN4@tst1:
; Line 5
        mov     eax, 1
; Line 6
        ret     0
tst1    ENDP
_TEXT   ENDS
END

Op 24 mei 2023 19:39:38 schreef Tuxracer:
Denk je ?
De compiler moet dan een heel stuk code analyseren en concluderen dat het een bit reversal is. Compilers zijn wel slim, maar ik vraag mij af of ze zo slim zijn.

In gcc heet het de peephole optimizer.

Die kijkt steeds met een pattern-matcher naar de tot dan toe gegenereerde code en vervangt die door wat nieuws, korters. Dus als "bit reversal" een instructie in je CPU is, dan zouden ze dat er in kunnen zetten, dat de instructie-sequence om een byte om te draaien door de bit-reversal instructie vervangen wordt.

Het enige is: Ik ken geen CPUs die de bit reversal in de instructie set hebben. Even ge-googled, en... ARM (waar de originele code op moet draaien) heeft hem wel.

Maar ik heb naar de geproduceerde code gekeken en daar werd ie niet in gebruikt.

Even de "oneliner" door gcc-amd64 en gcc-arm gehaald. CISC: 16 instructies, RISC: 12 instructies.

Ik heb de bswap routine herschreven tot:

c code:


int bswap8 (int c)
{
  asm ("rbit %0, %0" : "+r"  (c));
  return c >> 24;
}

(merk op dat ik dit doe om er van te leren, niet omdat het sneller moet!!!!)

Dit compileert naar 3 instructies:

code:

        rbit r0, r0
        asr     r0, r0, #24
        bx      lr

maar de compiler weet meer dan ik:

code:

/tmp/cc7oCHXj.s:27: Error: selected processor does not support `rbit r0,r0' in ARM mode

Dus mijn processor heeft die instructie TOCH niet. (ook niet in thumb mode wat de uiteindelijke target doet).

Dus... Toch is mijn "ik weet geen processor die een bitvolgorde kan omdraaien" WEER correct. :-)

Voor de lol tuxracer z'n code door de arm compiler gehaald: 1) Hij heeft gelijk: Identieke code. 2) RISC is weer korter dan x86!

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

Op 24 mei 2023 20:55:30 schreef rew:
[...]
Dus... Toch is mijn "ik weet geen processor die een bitvolgorde kan omdraaien" WEER correct. :-)

Analog Devices ADSP-21060 heeft een bit-reverse instructie, en heeft ook een bit-reversed addresserings mode. Andere DSP's waarschijnlijk ook, het wordt gebruikt voor het implementeren van butterfly FFT.

4.3.3.2 Bit-Reverse Instruction
The BITREV instruction modifies and bit-reverses addresses in any DAG index register (I0-I15) without actually accessing memory. This instruction is independent of the bit-reverse mode. The BITREV instruction adds a 32-bit immediate value to a DAG1 index register (or a 24-bit immediate value to a DAG2 index register), bit-reverses the result and writes the result back to the same index register.
Example:
BITREV(I1,4); I1 = Bit-reverse of (I1+4)

Andere trucs die ik langs zag komen:

- Lookup table

- Twee IO-poorten bit-reversed met elkaar doorverbinden. Schrijf naar de ene poort, lees bit-reversed terug van de andere poort.

Op 24 mei 2023 19:39:38 schreef Tuxracer:
[...]

Denk je ?
De compiler moet dan een heel stuk code analyseren en concluderen dat het een bit reversal is. Compilers zijn wel slim, maar ik vraag mij af of ze zo slim zijn.

Met wat inline assembly instructies kan je iets dergelijks vaak wel efficient implementeren als er hardware support is.

Ja de meeste compilers zijn zo slim. Maar dan moet je wel de juiste machine type etc meegeven.

Een voorbeeld wat je zelf kunt proberen is deze (32 bit) endianess conversion:

c code:


#include <stdint.h>

uint32_t ntohll(uint32_t be);


int main() {

  volatile  uint32_t be = 0x12345678;
  ntohll(be);

  return 0;
}

uint32_t ntohll(uint32_t be)
{
  uint32_t rc = 0;
  rc |= (be & 0xFF) << 24;
  rc |= (be & 0xFF00) << 8;
  rc |= (be & 0xFF0000) >> 8;
  rc |= (be & 0xFF000000) >> 24;

  return rc; 
}

De functie ntohll compileert tot 1 instructie + return:

c code:


ntohll(unsigned int):
        rev     w0, w0
        ret

Gooi maar in godbolt.org en kies bijvoorbeeld de arm64 of andere target cpu.
Meestal maar 1 instructie.

Voor een 64 bits endian conversion is het voor een M3 arm ook maar 3 instructies terwijl het er best ingewikkeld uit zit als die volledig uitgeschreven is.

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

Ik heb dat stukje code door de compiler gehaald.... Hij optimaliseert de call naar de functie waar het om gaat weg. Je moet

c code:

 be = ntohll(be);

schrijven: De volatile declaratie forceert dan dat ie de assignment moet doen en dus ook de berekening.

De optimizer haalt er dan trouwens NOG twee instructies uit: De call en return. De hele functie call wordt 1 inline instructie.

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

Op 24 mei 2023 20:55:30 schreef rew:
[...]
Voor de lol tuxracer z'n code door de arm compiler gehaald: 1) Hij heeft gelijk: Identieke code. 2) RISC is weer korter dan x86!

gcc-x86 doet het slimmer dan de Microsoft compiler:

code:


int tst1(int B, int C)
{
        return B || !B && C;
}


int tst2(int B, int C)
{
        return B || C;
}



tst1(int, int):
        xor     eax, eax
        or      edi, esi
        setne   al
        ret


tst2(int, int):
        xor     eax, eax
        or      edi, esi
        setne   al
        ret


Op 24 mei 2023 23:29:18 schreef henri62:
[...] Ja de meeste compilers zijn zo slim. Maar dan moet je wel de juiste machine type etc meegeven.

Ik twijfelde een beetje omdat de bit-reverse functie een loop bevat, en dan is het moeilijker optimaliseren. Maar de compiler blijkt het ook met een loop slim te doen bij de endianess conversion:

code:



#include <stdint.h>

uint32_t ntohll(uint32_t be)
{
  uint32_t rc = 0;
  rc |= (be & 0xFF) << 24;
  rc |= (be & 0xFF00) << 8;
  rc |= (be & 0xFF0000) >> 8;
  rc |= (be & 0xFF000000) >> 24;

  return rc;
}

uint32_t ntohll2(uint32_t be)
{
        uint32_t retval;

        unsigned char *ps = (unsigned char *) &be;
        unsigned char *pd = (unsigned char *) &retval;

        pd[3] = ps[0];
        pd[2] = ps[1];
        pd[1] = ps[2];
        pd[0] = ps[3];

        return retval;
}

uint32_t ntohll3(uint32_t be)
{
        uint32_t retval;

        unsigned char *ps = (unsigned char *) &be;
        unsigned char *pd = (unsigned char *) &retval;

        int i;

        for (i=0; i<4; i++) {

                pd[3-i] = ps[i];

        }

        return retval;
}



ntohll(unsigned int):
        mov     eax, edi
        bswap   eax
        ret


ntohll2(unsigned int):
        mov     eax, edi
        bswap   eax
        ret


ntohll3(unsigned int):
        mov     eax, edi
        bswap   eax
        ret

Die array variant is formeel NIET correct. Als je dat op een big endian machine draait swapped die altijd de bytes wat dan niet de bedoeling is.

@rew: Klopt, als je niks volatile declareert en je gebruikt constantes blijft er zelfs niets van over en wordt meteen de geswapte constante gebruikt.

In godbolt.org was het voldoende om zelfs alleen de functie erin te zetten om te zien wat die er van bakt.

Dat de code inlined wordt in jouw geval komt door LTO en niet de compiler. De Link Time Optimization dus.

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

Op 24 mei 2023 17:07:26 schreef PE9SMS:
[...]Je mist de clou. De compilers zijn zo goed dat het voor het resultaat (de gegenereerde assembly) helemaal niet uitmaakt of je de source code compact op 1 regel schrijft of in stappen opdeelt over meerdere regels.

Zie ook het testje van henri62 eerder in dit topic.

Ergo: schrijf code die voor jou leesbaar is en maak je geen zorgen over efficientie. Dat regelt de compiler wel.

Dat is precies wat ik bedoel. Maar dan wel code die iedereen kan begrijpen. Dan weet je zelf na een jaar ook nog wat daar staat.

De reeks antwoorden dwalen steeds verder af van de initiële vraag (waarop deKees al een goed antwoord gaf).

Ik zie ook voorbij komen: B || !B && C
Dat is geen bitwise OR.
Ik snap niet wat de bedoeling is van die instructie bij het verkrijgen van een omgekeerde bit-volgorde in een byte.
..maar goed: De discussie ging al ergens anders over.

Het is jammer dat men in C geen carry-bit heeft (net als in assembler).
In AVR-assembler kan het op deze manier:

avr asm code:

ldi R16, 123    ; laad een byte waarde in register R16
;en dan 8 keer doen:
lsr R16         ; schuif bits uit R16. Rechtse bit valt eruit, in de carry-flag
lsl R17         ; schuif de carry in R17 (van rechts, en schuif naar links)

Na afloop heeft R17 de omgekeerde bit-volgorde die werd aangereikt in R16.