Sida 4 av 5

Re: C data types, tar knäcken på mig

Postat: 5 januari 2014, 23:27:33
av TomasL
Dock, som skrivits tidigare, att använda datatyper som är kortare än den nativa ordlängden, medför oftast betydligt mer kod, och därmed långsammare program.
Ponera följande inte helt ovanliga sekvens:

Kod: Markera allt

uchar i;
for (i=0; i<100; i++)
{



}
Om man använder en processor med 16 bitar eller längre nativ ordlängd så måste kompilatorn lägga till en hel del operationer för att kunna läsa och använda "i", Detta för att "i" som är typad som 8 bitar, men internt lagrad som 16 eller 32 bitar, beroende på arkitektur.
De flesta arkitekturer stödjer inte dessa kortare ordlängder nativt, vilket innebär en massa extra manipulering.

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 10:25:30
av MiaM
bit96 skrev:Väldig förvirrande är ju att a == b[a].
Det brukar sätta griller i huvudet på många att man byta plats på fältets namn och indexet.
Hur kan indexet plötslig bli ett fält, och hur kan fältet, som just fattat är en pekare(minnesadress) plötsligt bli ett index?

Men a beräknas som *(a+b), d.v.s. "adressen a + offset b, och hämta värdet på den nya beräknade adressen"
Efter som det är en addition spelar det ju ingen roll om vi kastar om a och b, *(b+a) blir samma sak.

Men det rekommendera inte att göra så vid "normal" programmering. :)


Men det där funkar väl bara med vissa datatyper?

Om a är en array med 32-bitarstal så kommer b multipliceras med 4 innan den läggs på pekaren a[0].

Man lär ju få cast'a en del för att över huvud taget få b[a] genom kompilatorn om variablerna är deklarerade så att a går igenom kompilatorn.

(Detta förutsatt att man inte använder nån kompilator från 1983 eller så som inte ens stödjer första versionen av ANSI-C...)

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 10:52:33
av bit96
MiaM:
Nej, det ska funka med alla datatyper så vitt jag vet.
Definitionen är uttryck1[uttryck2] == *(uttryck1 + uttryck2).

Det är en av grundstenarna i C att man kan addera och subtrahera heltal till/från pekare.
T.ex

Kod: Markera allt

int *talp:
...
talp+=3; /* Pekaren räknas upp med 3 POSITIONER t.ex. 3x2=6 bytes, om int är 2 bytes  */
...
Det är just det att man angivit datatyper som gör att det fungerar.
Assemblerprogrammeraren måste hålla reda på om det är 1, 2 eller 4 byte per tal, med för C-programmeraren håller kompilatorn koll på detta.

Exakt när och hur detta infördes vet jag ej, men det funkade i alla fall redan på 80-talet när jag började med C, både på gamla Sun-maskiner och med TurboC på en 286:a.

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 11:50:41
av hummel
MiaM skrev:
bit96 skrev:Väldig förvirrande är ju att a == b[a].
Det brukar sätta griller i huvudet på många att man byta plats på fältets namn och indexet.
Hur kan indexet plötslig bli ett fält, och hur kan fältet, som just fattat är en pekare(minnesadress) plötsligt bli ett index?

Men a beräknas som *(a+b), d.v.s. "adressen a + offset b, och hämta värdet på den nya beräknade adressen"
Efter som det är en addition spelar det ju ingen roll om vi kastar om a och b, *(b+a) blir samma sak.

Men det rekommendera inte att göra så vid "normal" programmering. :)


Men det där funkar väl bara med vissa datatyper?

Om a är en array med 32-bitarstal så kommer b multipliceras med 4 innan den läggs på pekaren a[0].

Man lär ju få cast'a en del för att över huvud taget få b[a] genom kompilatorn om variablerna är deklarerade så att a går igenom kompilatorn.

(Detta förutsatt att man inte använder nån kompilator från 1983 eller så som inte ens stödjer första versionen av ANSI-C...)


Har alltid fungerat, kolla K&R.

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 12:13:58
av johano
Japp, kallas "pekararitmetik".

Kod: Markera allt

int *p = (int*)1000000;
p++;  // ökar adressen p pekar på med sizeof(p), alltså till 1000004, INTE till 1000001 (om en int är 4bytes)
/johan

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 12:46:29
av bit96
Ja, a == b[a] kan vara förvirrande som jag skrev tidigare.

Man kan se det som att kompilatorn aldrig använder [] utan först sker en "sök-och-ersätt" där a byts mot *(a+b).

Som programmerare ser jag:

Kod: Markera allt

long tabell[10]; /* Fält med 10 st long, (long är 4 bytes i detta exempel) */
int i; /* skall användas som index i fältet (int är 2 bytes i detta exempel) */
long a, b;

i=5; /* Jag skall plocka ut värdet på position 5 i fältet så jag sätter index i till 5 */ 

a=tabell[i]; /* Detta fattar alla att man plockar ut en long på plats 5 i fältet, alltså värdet på position 5 plockas fram  */
b=i[tabell]; /* Vissa C-programmerare fattar inget, hur f-n kan i bli en tabell och hur kan ett fält bli index ??? Jag går nog över till Pascal */
Men kompilatorn ser (efter att ha gjort 'sök-och-ersätt'):

Kod: Markera allt

a=*(tabell + 5); /* Jaha, pekaren 'tabell' som pekar på long skall skall ökas med 5 POSITIONER (5*4 bytes), och hämta sen värdet där */
b=*(5 + tabell); /* Jaha, pekaren 'tabell' som pekar på long skall skall ökas med 5 POSITIONER (5*4 bytes), och hämta sen värdet där */
Detta är vanlig pekar-aritmetik i C. Och det spelar ju ingen roll i vilken ordning uttrycken kommer kring ett plusstecken. :jimmyhacker:

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 14:35:17
av sodjan
> att använda datatyper som är kortare än den nativa ordlängden, medför oftast betydligt mer kod,

Varför inte testa och se... :-)

På min Alpha så ser själva loopen med en "unsigned char i;" ut så här:

Kod: Markera allt

             00DC       L$2:
A41D0018     00DC               LDQ     R0, 24(FP)
2E400000     00E0               LDQ_U   R18, I                                  ; R18, (R0)
4A4000C0     00E4               EXTBL   R18, R0, R0
400C99A0     00E8               CMPLT   R0, 100, R0
2FFE0000     00EC               UNOP
E4000009     00F0               BEQ     R0, L$4
A43D0018     00F4               LDQ     R1, 24(FP)
2E010000     00F8               LDQ_U   R16, I                                  ; R16, (R1)
4A0100D1     00FC               EXTBL   R16, I, R17                             ; R16, R1, R17
4A010050     0100               MSKBL   R16, I, R16                             ; R16, R1, R16
42203411     0104               ADDQ    R17, 1, R17
4A210171     0108               INSBL   R17, I, R17                             ; R17, R1, R17
46110410     010C               BIS     R16, R17, R16
3E010000     0110               STQ_U   R16, I                                  ; R16, (R1)
C3FFFFF1     0114               BR      L$2                                                                                 ; 001612
             0118       L$4:                                                                                                ; 001613
Och med "int i", "long i" och "long long i" (de ger lika många instruktiner med några små skillnader):

Kod: Markera allt

             00D4       L$2:
A63D0018     00D4               LDQ     R17, 24(FP)
A2310000     00D8               LDL     R17, I                                  ; R17, (R17)
422C93B1     00DC               CMPULT  R17, 100, R17
E6200006     00E0               BEQ     R17, L$4
A41D0018     00E4               LDQ     R0, 24(FP)
A0000000     00E8               LDL     R0, I                                   ; R0, (R0)
40003000     00EC               ADDL    R0, 1, R0
A43D0018     00F0               LDQ     R1, 24(FP)
B0010000     00F4               STL     R0, I                                   ; R0, (R1)
C3FFFFF6     00F8               BR      L$2                                                                                 ; 001612
             00FC       L$4:                                                                                                ; 001613
De flesta extra instruktionerna har med hanteringen av "bytes" att göra:

Kod: Markera allt

Instruction  Description             Byte(c, i) == 0   Nonzero Byte(c, i)
extbl        Extract Byte Low        i != 0            Byte(a, bl)
mskbl        Mask Byte Low           i == bl           Byte(a, i)
insbl        Insert Byte Low         i != bl           Byte(a, 0)
extqh        Extract Quad Word High  bl!=0 && i+bl<8   Byte(a, (i+bl8)&0x7)
Från: http://www.cs.cmu.edu/afs/cs/academic/c ... -guide.pdf
Speciellt "2.6. Byte Manipulation Operations" och "2.6.1. Working with Unaligned Byte Data".

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 14:57:40
av TomasL
15 resp 10 instruktioner, dvs en 50% ökning när ickenativ ordlängd används.

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 15:20:28
av DanG
"Out of topic", men i alla fall ..

Önskar att jag haft tillgång till denna "C-skola" i mitten av 80-talet!
Läser med stort intresse!!!

M.v.h DanG

Re: C data types, tar knäcken på mig

Postat: 6 januari 2014, 16:40:06
av sodjan
En annan intressant effekt har med "alignment" att göra.
Ta t.ex detta lilla exempel:

Kod: Markera allt

struct stru {
  unsigned char x;
  unsigned long y;
};

struct stru s;
unsigned long i;

void main()
{
  for (i=0; i<100; i++)
  {
    s.x = i;
    s.y = i;
  }
}
En struct med en "char" och en "long".
Default vid kompileringen sätta det in 3 st "dummy bytes" efter char'en så att
long'en ska komma på en jämn "long-adress". Processorn accessar alltid minnet
i 32 eller 64-bitars bitar, och om en variabel inte ligger på en jämn adress så
blir det lite pussel av det hela.

Om vi kompilerar med default (d.v.s med "member_align"):

Kod: Markera allt

$ cc /list /machin_code /nooptimize  stru
$ link /map /full stru
så får vi en kod som ser ut så här (d.v.s för de två tilldelningarna inne i loopen, rad 13 och 14):

Kod: Markera allt

...
      1      11   for (i=0; i<100; i++)
      2      12   {
      2      13     s.x = i;
      2      14     s.y = i;
      1      15   }
...
A41D0020     00E4               LDQ     R0, 32(FP)                                                     ; 000013
A0000000     00E8               LDL     R0, I                                   ; R0, (R0)
A43D0018     00EC               LDQ     R1, 24(FP)
A2010000     00F0               LDL     R16, S                                  ; R16, (R1)
4A003610     00F4               ZAP     R16, 1, R16
441FF000     00F8               AND     R0, 255, R0
46000410     00FC               BIS     R16, R0, R16
B2010000     0100               STL     R16, S                                  ; R16, (R1)
A63D0020     0104               LDQ     R17, 32(FP)                                                    ; 000014
A2310000     0108               LDL     R17, I                                  ; R17, (R17)
A65D0018     010C               LDQ     R18, 24(FP)
B2320004     0110               STL     R17, 4(R18)
Alltså 8 instruktioner för "s.x = i;" och 4 för "s.y = i;"
Ganska förväntat, en long är effektivare än en char.

Vi ser också (i MAP filen att structen tar *8* bytes utrymme eftersom kompilatorn
har tryckt in 3 "dummy-bytes" mellan x och y.

Om vi däremot kompilerar utan "member_align" så blir det (ungefär) samma kod för
tilldelningen av char'en, men för long'en (som nu ligger över två minnes platser) så
blir det en stor skillnad. Istället för *4* instruktioner så blir det:

Kod: Markera allt

$ cc /list /machin_code /nooptimize  /nomember_align stru.c
$ link /map /full stru

Kod: Markera allt

A65D0020     0104               LDQ     R18, 32(FP)                                                    ; 000014
A2520000     0108               LDL     R18, I                                  ; R18, (R18)
A41D0018     010C               LDQ     R0, 24(FP)
20000001     0110               LDA     R0, 1(R0)
2E000003     0114               LDQ_U   R16, 3(R0)
2C200000     0118               LDQ_U   R1, (R0)
4A400CF3     011C               INSLH   R18, R0, R19
4A400571     0120               INSLL   R18, R0, R17
4A000C50     0124               MSKLH   R16, R0, R16
48200441     0128               MSKLL   R1, R0, R1
46130410     012C               BIS     R16, R19, R16
44310401     0130               BIS     R1, R17, R1
3E000003     0134               STQ_U   R16, 3(R0)
3C200000     0138               STQ_U   R1, (R0)
Den gör dubbla minnes accesser och pusslar ihop det hela till en long.
Structen s tar nu dock bara 5 bytes eftersom ingen alignment har gjorts.
Från 4 instruktioner till 14.

I de flesta fall kan man låta kompilatorn fylla ut där den vill, men om man har
fasta format som man hanterar med struct's och union's, så är det inte alltid
man kan göra det. D.v.s om lagringsformatet på en struktur redan är given.

Allra bäst är att köra med default optimize, då försvinner loopen helt och ersätts
med två enkla tilldelningar av 99 till x och y... :-)

Re: C data types, tar knäcken på mig

Postat: 7 januari 2014, 13:21:44
av MiaM
Det här är dock sånt som skiljer avsevärt mellan olika arkitekturer.

68000 har instruktionsuppsättning för att hantera 8, 16 och 32 bitar. De som har 16 bitars bredd på den externa bussen (68000, 68010) kräver att allt bredare än en byte är alignat på jämna 16 bitar, och alla kompilatorer jag stött på gör den aligningen default. De 68000-kretsar som har 32-bitarsbuss (68020 och högre) klarar vilken aligning/misaligning som helst, men man får en prestandaförlust för sådant som ligger fel. (68008 med åttabitarsbuss klarar nog vad jag minns också valfri aligning).

Det här gör att det blir ingen prestandaförlust att ha mindre datatyper, och på maskinerna med 16-bitarsbuss så vinner man till och med prestanda på att ha short (16-bit) istället för int (32-bit) till variabler som lagras i minnet.

(Nu har jag utgått från vad som är standard på AmigaOS, det finns säkert andra 68000-arkitekturer som brukar ha short, int och long definerat på annat sätt).

Re: C data types, tar knäcken på mig

Postat: 7 januari 2014, 13:31:57
av sodjan
Visst skiljer det. Alpha var en ren 64-bit arkitektur från början.
Byte-instruktionerna kom först efter några generationer. Andra
processorer försöker bevara bakåtkompatibilitet, men det fanns
inget behov av det för Alpha, eftersom den var helt ny.
För örvrigt så säger du i princip samma sak som mig, så
det stämmer säkert... :-)

Inom parentes så är Intel Itanium en processor som är väldigt känslig
för "miss-aligment". Prestanda går helt "down the drain". Processorn
har inga verktyg för att fixa det själv utan det blir ett interrupt
tillbaka till det aktulla OS'et som får fixa till det.

Re: C data types, tar knäcken på mig

Postat: 7 januari 2014, 17:38:49
av bit96
MiaM skrev:Det här är dock sånt som skiljer avsevärt mellan olika arkitekturer.

68000 har instruktionsuppsättning för att hantera 8, 16 och 32 bitar. De som har 16 bitars bredd på den externa bussen (68000, 68010) kräver att allt bredare än en byte är alignat på jämna 16 bitar, och alla kompilatorer jag stött på gör den aligningen default. De 68000-kretsar som har 32-bitarsbuss (68020 och högre) klarar vilken aligning/misaligning som helst, men man får en prestandaförlust för sådant som ligger fel. (68008 med åttabitarsbuss klarar nog vad jag minns också valfri aligning).

Det här gör att det blir ingen prestandaförlust att ha mindre datatyper, och på maskinerna med 16-bitarsbuss så vinner man till och med prestanda på att ha short (16-bit) istället för int (32-bit) till variabler som lagras i minnet.

(Nu har jag utgått från vad som är standard på AmigaOS, det finns säkert andra 68000-arkitekturer som brukar ha short, int och long definerat på annat sätt).
Jag jobbade med flera Motorolas mikrokontroller för många år sen och de var baserade på bl.a. 68000-kärnor och högre.
Fast även om de hade 8-bitars databuss så blev det någon klockcykels prestanda förlust om man läste in ett 16-bitars word som inte var alignat rätt i minnet. Det funkade alltså elektriskt men internt fick den göra nån swapning av hög och låg byte som kostade lite tid.

För att inte tala om Big Endian, suck. :tumner:
Vi hade långa debatter om vilket som var bäst, Little eller Big Endian. Till slut höll de andra med om att Little Endian faktisk är mer logiskt och enkelt uppbyggt.
Utom för bitmappning för grafik, där passade Big Endian perfekt att motsvara hur pixlar adresserades på skärmen och i minnet. :)

Re: C data types, tar knäcken på mig

Postat: 7 januari 2014, 18:51:52
av gkar
Intressant, förklara för mig att hur det är logiskt att lagra bitarna i minnet i en annan ordning än det lagras i dina register?

Re: C data types, tar knäcken på mig

Postat: 7 januari 2014, 18:57:38
av bit96
gkar:
Vid little endian lagras bitarna på samma sätt i register som i minnet.
Dessutom spelar det ingen roll om du ökar eller minskar mellan 8, 16, 32, 64 eller vad det nu är, det är bara att fylla på eller ta bort bytes, det blir alltid rätt.
Vid big endian måste du swappa om bytesen på olika sätt om du vill omtolka ett lagrat tal som 8, 16, 32 ... bitar.

När det gäller grafik däremot, i alla fall de lcd-skärmar vi jobbade med förr, så motsvarade lagringen big endian när man jobbade med bitmappar i minnet, även vid olika bitdjup. Exakta detaljer minns jag inte nu på rak arm, men det går ju att gräva fram.