webForumDet fria alternativet

Allokering av variabler

C/C++ur C/C++

34 svar · 1 109 visningar · startad av Sang-drax · sida 2 av 2

Frågan, av Sang-drax

Denna tråd fortsätter off-topic diskussionen i <http://www.webforum.nu/showthread.php?s=&threadid=75870> Jag menar att man ska vänta med deklareringen om man kan, medan PeW menar att det inte har någon betydelse för hastigeten och bör undvikas. Här kommer ett mycket enkelt exempel som visar på att olika maskinkod kommer att genereras. I detta fall används en enkel int, vilket gör att vinsten end

Läs frågan i sin helhet →
Medlem sedan juli 2002581 inlägg
#21

Tja... här är de två versionerna av den rekursiva funktionen.

void f()
{
004037F0  push        ebp  
004037F1  mov         ebp,esp 
004037F3  mov         eax,1FBD4h 
004037F8  call        _chkstk (42CFF0h) 
[1]
004037FD  mov         eax,dword ptr [___security_cookie (468EB8h)] 
00403802  xor         eax,dword ptr [ebp+4] 
00403805  push        edi  
00403806  mov         dword ptr [ebp-4],eax 
    //Utan denna variabel krashar aldrig funktionen
    //när w och q är deklarerade inom if-statserna
    char data[10000] = {0};
00403809  xor         eax,eax 
0040380B  mov         byte ptr [data],0 
00403812  mov         ecx,9C3h 
00403817  lea         edi,[ebp-0C353h] 
0040381D  rep stos    dword ptr [edi] 
0040381F  stos        word ptr [edi] 
00403821  stos        byte ptr [edi] 

    double w[10000] = {5}; 
00403822  xor         eax,eax 
00403824  mov         ecx,4E1Eh 
00403829  lea         edi,[ebp-1FBCCh] 
0040382F  mov         dword ptr [w],0 
00403839  mov         dword ptr [ebp-1FBD0h],40140000h 
00403843  rep stos    dword ptr [edi] 
    int q[10000] = {5}; 
00403845  mov         ecx,270Fh 
0040384A  lea         edi,[ebp-9C40h] 
00403850  mov         dword ptr [q],5 
0040385A  rep stos    dword ptr [edi] 

    if (b)
0040385C  mov         al,byte ptr [b (46A2A0h)] 
00403861  test        al,al 
00403863  pop         edi  
00403864  je          f+87h (403877h) 
    {
        //int q[10000] = {5};   
        std::cout << &q;
00403866  lea         eax,[q] 
0040386C  push        eax  
0040386D  mov         ecx,offset std::cout (46A3ACh) 
00403872  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (401041h) 
    }
    
    if (a)
00403877  mov         al,byte ptr [a (46A2A1h)] 
0040387C  test        al,al 
0040387E  je          f+0A1h (403891h) 
    {
        //double w[10000] = {5}; 
        std::cout << &w;
00403880  lea         ecx,[w] 
00403886  push        ecx  
00403887  mov         ecx,offset std::cout (46A3ACh) 
0040388C  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (401041h) 
    }

    count++;
00403891  mov         eax,dword ptr [count (46A2A4h)] 

    std::cout << "Anrop nummer " << count <<  data << std::endl;
00403896  push        offset std::endl (4010D2h) 
0040389B  lea         edx,[data] 
004038A1  push        edx  
004038A2  inc         eax  
004038A3  push        eax  
004038A4  push        offset ___xt_z+104h (468030h) 
004038A9  push        offset std::cout (46A3ACh) 
004038AE  mov         dword ptr [count (46A2A4h)],eax 
004038B3  call        std::operator<< (4013D9h) 
004038B8  add         esp,8 
004038BB  mov         ecx,eax 
004038BD  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (401122h) 
004038C2  push        eax  
004038C3  call        std::operator<< (4013D9h) 
004038C8  add         esp,8 
004038CB  mov         ecx,eax 
004038CD  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (4012B2h) 

    //Rekursiv funktion
    f();
004038D2  call        f (401258h) 

}[/1]
void f()
{
004037F0  push        ebp  
004037F1  mov         ebp,esp 
004037F3  mov         eax,15F94h 
004037F8  call        _chkstk (42CFF0h) 
[1]
004037FD  mov         eax,dword ptr [___security_cookie (468EB8h)] 
00403802  xor         eax,dword ptr [ebp+4] 
00403805  push        edi  
00403806  mov         dword ptr [ebp-4],eax 
    //Utan denna variabel krashar aldrig funktionen
    //när w och q är deklarerade inom if-statserna
    char data[10000] = {0};
00403809  xor         eax,eax 
0040380B  mov         byte ptr [data],0 
00403812  mov         ecx,9C3h 
00403817  lea         edi,[ebp-2713h] 
0040381D  rep stos    dword ptr [edi] 
0040381F  stos        word ptr [edi] 
00403821  stos        byte ptr [edi] 

    //double w[10000] = {5}; 
    //int q[10000] = {5}; 

    if (b)
00403822  mov         al,byte ptr [b (46A2A0h)] 
00403827  test        al,al 
00403829  je          f+65h (403855h) 
    {
        int q[10000] = {5};   
0040382B  xor         eax,eax 
0040382D  mov         dword ptr [q],5 
00403837  mov         ecx,270Fh 
0040383C  lea         edi,[ebp-0C350h] 
00403842  rep stos    dword ptr [edi] 
        std::cout << &q;
00403844  lea         eax,[q] 
0040384A  push        eax  
0040384B  mov         ecx,offset std::cout (46A3ACh) 
00403850  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (401041h) 
    }
    
    if (a)
00403855  mov         al,byte ptr [a (46A2A1h)] 
0040385A  test        al,al 
0040385C  je          f+0A2h (403892h) 
    {
        double w[10000] = {5}; 
0040385E  xor         eax,eax 
00403860  mov         dword ptr [w],0 
0040386A  mov         dword ptr [ebp-15F90h],40140000h 
00403874  mov         ecx,4E1Eh 
00403879  lea         edi,[ebp-15F8Ch] 
0040387F  rep stos    dword ptr [edi] 
        std::cout << &w;
00403881  lea         ecx,[w] 
00403887  push        ecx  
00403888  mov         ecx,offset std::cout (46A3ACh) 
0040388D  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (401041h) 
    }

    count++;
00403892  mov         eax,dword ptr [count (46A2A4h)] 

    std::cout << "Anrop nummer " << count <<  data << std::endl;
00403897  push        offset std::endl (4010D2h) 
0040389C  lea         edx,[data] 
004038A2  push        edx  
004038A3  inc         eax  
004038A4  push        eax  
004038A5  push        offset ___xt_z+104h (468030h) 
004038AA  push        offset std::cout (46A3ACh) 
004038AF  mov         dword ptr [count (46A2A4h)],eax 
004038B4  call        std::operator<< (4013D9h) 
004038B9  add         esp,8 
004038BC  mov         ecx,eax 
004038BE  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (401122h) 
004038C3  push        eax  
004038C4  call        std::operator<< (4013D9h) 
004038C9  add         esp,8 
004038CC  mov         ecx,eax 
004038CE  call        std::basic_ostream<char,std::char_traits<char> >::operator<< (4012B2h) 

    //Rekursiv funktion
    f();
004038D3  call        f (401258h) 

}[/1]

Följande stycke är hämtat från MSVC++:

chkstk.asm skrev:

;Entry:
; EAX = size of local frame
;
;Exit:
; ESP = new stackframe, if successful
;
;Uses:
; EAX

Exemplet med varaiblern inom if-satserna använde således 0x15F94 bytes minne, medan koden där alla variablerna deklarerades i början av funktionen använde 0x1FBD4 bytes.

Skillnaden blir exakt 40000 bytes i decimal form, det vill säga exakt det utrymme som de tiotusen integer tar upp. Dessa integers lagrades nämligen i (delvis) samma utrymme som flyttalen

Medlem sedan juli 2002581 inlägg
#22

Ta en till på texten jag har bifogat här nedan.
Den visar att det kan spela roll oavsett vilken kompilator man har.

Jag har även skrivit brev till Bjarne Strousrup och bett honom kommentera denna fråga.

Förresten: Det är ingen risk för mig att skjuta mig i foten i dagsläget, jag sitter nämligen inomhus och blir utbildad i Windows NT istället för att flänga runt i skogen. :)

Medlem sedan juni 200010 432 inlägg
#23

Uj... jag ska ta mig en titt på detta imorrn. Nu är jag för trött efter x-antal timmars hackande på en restlab ;)

Men vi ska självfallet reda ut dessa skillnader och framför allt ta reda på vad som händer! Ett minne måste allokeras nånstans och om det sker på heapen i runtime är det mycket märkligt om det tar mindre tid än en allokering på stacken (av anledningar jag nämnt ett par inlägg ovanför) eftersom en stack ÄR statisk.

I'll be back :)

Medlem sedan juli 2002581 inlägg
#24

Ingen allokering sker på heapen. Stackarna är statiska i båda fallen. De är dock inte lika stora.

Medlem sedan juni 200010 432 inlägg
#25

Ingen allokering sker på heapen. Stackarna är statiska i båda fallen. De är dock inte lika stora.

Nej, jag ser det nu när jag piggnade till ;)

Om du byter ut double arrayen mot ytterligare en int array, får du samma resultat? Varför jag frågar det är att det ser ut som att det är doublen som skiljer genom att vara 8 bytes i den första till att vara 4 i den andra. Kom i håg att det finns ingen chans överhuvudtaget att flera datatyper kan dela adresser. Så, om denna double array fylldes upp till hela sin storlek skulle int arrayen bli överskriven... vilket inte låter sig göras. Detta måste avgöras vid kompileringen och genom att du initierar doublen, men inte ger det hela en chans att doublen ska kunna anta något annat optimeras denna till heltal. En smart optimering, men det fungerade inte så i VC6 och vad händer om man behöver använda hela doublen samt vad händer vid heltals arrayer och andra andra datatyper?

Stacken är mindre för denna funktion än i den med initialt deklararation. Troligen är stacken som main initierar dock lika stor som i fallet innan och därmed tillåts flera initieringar av f() inom denna adressrymd. Vilket isf ger att programmets minnesnyttjande är oförändrat.

Medlem sedan juni 200010 432 inlägg
#26

Från texten du rekommenderade för läsning:

Further, you help document the purpose of variables by initializing them in contexts in which their meaning is clear. Remember how in C you're encouraged to put a short comment after each variable definition to explain what the variable will eventually be used for? Well, combine decent variable names (see also Item 28) with contextually meaningful initialization arguments, and you have every programmer's dream: a solid argument for eliminating some comments.

Jag kan faktiskt inte ärligt påstå att jag nånsin stött på en enda uppmuntran att kommentera allt. Inte när det gäller C, enda undantaget är i assembler. Däremot tjatas det hejvilt om att ge variabler beskrivande namn.. vilket citatet här lägger fram som nån extra "finess" för C++. Det är en bra 'standard' som är gångbar i alla språk - juh ;)

Vidare nämns i texten samma sak som vi redan avhandlat.. nämligen icke primitiva datatyper vars konstruktor med fördel anropas lokalt och inte globalt i en funktion. Det finns olika sätt att göra detta på och det som står där är ett sätt. Jag förnekar inte att det är en bra sak att göra så i det fallet. Men nu började diskussionen i generella termer och jag hävdar fortfarande att det inte alls är självklart att man genom lokala deklarationer sparar minne i userspace och definitivt inte självklart att man sparar exekveringstid.

Diskussionen fortsätter ... ;)

Medlem sedan juli 2002581 inlägg
#27

PeW skrev:

Kom i håg att det finns ingen chans överhuvudtaget att flera datatyper kan dela adresser.

Jo, jo, jo!
Självklart kan de ju dela adress! De används ju inte samtidigt!

PeW skrev:

Så, om denna double array fylldes upp till hela sin storlek skulle int arrayen bli överskriven... vilket inte låter sig göras.

Fel!
Det fungerar alldeles utmärkt, eftersom de båda arrayerna inte kan användas samtidigt!

PeW skrev:

vad händer om man behöver använda hela doublen

Ingenting särskilt, den uppför sig som en vanlig double-array ju för att den är en vanligt double array.

PeW skrev:

Stacken är mindre för denna funktion än i den med initialt deklararation.

Helt korrekt. Detta är användbart vid till exempel rekursion. Jag har haft problem med rekursioner som varit för djupa och då är det en fördel om man kan minska stackstorleken.

EDIT: Tänk på att följande kod ger "undefined behaviour":

void f()
{
  int* p;
  if ()
  {
    int i;
    p=&i;
  }

  *p = 2;
}

EDIT 2:
André LaMothe menar i boken "Windows game programming for dummies" att man ska kommentera varenda rad. ;)

Medlem sedan juli 2002581 inlägg
#28

Brev från Bjarne Stroustrup

Jag skrev ett brev till Bjarne Stroustrup, skaparen av C++ (!), och bad honom om en kommentar angående detta.

Petter Strandmark skrev:

> Hi,
>
> I have a question regarding variable declarations in C++:
>
> Which coding style would you consider the most efficient in terms of
> possible optimizations/improved readability?
>
> void f()
> {
> if (...)
> {
> int a;
> ...
> }
>
> if (...)
> {
> double x;
> ...
> }
>
> }
>
> or:
>
> void f()
> {
> int a;
> double x;
>
> if (...)
> {
> //use a
> }
>
> if (...)
> {
> //use x
> }
>
> }
>
>
> To me, the first example seems better in every aspect, it allows for
> the compiler to allocate less memory for the function and the code is
> looking better in my eyes.

Bjarne Stroustrup skrev:

I agree, but the main argument is simply that a declaration should not be
available in a scope where it doesn't make sense, and in particularly not
before it can be initialized to a meaningful value. Doing so is to decrease
readability and increase the chance of errors (mainly maintenance errors). I
consider those statements objective. These arguments become stronger still
when you consider initialization.

You can find comments to that effect in D&E and TC++PL.

Petter Strandmark skrev:

> I am writing this letter because I have recently heard lots of
> arguments in favor of the latter style. I cannot list them here and I
> do not agree with them anyway.

Bjarne Stroustrup skrev:

I suspect that I have heard them all. Those arguments tend to come from people
trained in lanuages such as pascal that doesn't allow for the introduction of
variables except at the start of a block.

Petter Strandmark skrev:

> So, which style should be preferred: declaring every variable at the
> beginning of the function or postponing the declarations until the
> variable is needed?

Bjarne Stroustrup skrev:

The ideal is: Never declare a variable until you can initialize it. That
implies late declaration. C++ allows you to meet that ideal in most cases -
the obvious exception is a variable that gets its value from input.

Petter Strandmark = Sang-drax på wf
D&E = The Design and Evolution of C++
TC++PL = The C++ Programming Language (han gör reklam för sina egna böcker, såklart :) )

Medlem sedan juni 200010 432 inlägg
#29

Med risk för felskrivningar pga trötthet väljer jag ändock att lägga in ett svar nu ist för imorgon:

Jo, jo, jo!
Självklart kan de ju dela adress! De används ju inte samtidigt!

Jasså? Du skrev i första inlägg om denna kod att "a och b är bool som läses in från tangentbordet till true i main()" , vilket ger att båda if-satserna körs. Isf har vi argumenterat från olika förutsättningar.

Fel!
Det fungerar alldeles utmärkt, eftersom de båda arrayerna inte kan användas samtidigt!

Se ovan.

Ingenting särskilt, den uppför sig som en vanlig double-array ju för att den är en vanligt double array.

En double är en utökad integer...

Helt korrekt. Detta är användbart vid till exempel rekursion. Jag har haft problem med rekursioner som varit för djupa och då är det en fördel om man kan minska stackstorleken.

Då är vi inne på ditt specialfall. Du har ju inte sparat minne totalt sett utan endast lokalt i stacken i just denna kod. Samma sak går att uppnå på andra sätt. Kan såklart ändå hålla med om att optimeringen som skedde är trevlig, men jag börjar undra om vi talar om samma saker när det handlar om att spara minne :)

EDIT 2:
André LaMothe menar i boken "Windows game programming for dummies" att man ska kommentera varenda rad.

Efter att ha plöjt igenom den boken börjar man gråta... praktexempel på 'fulkodning' och den boken kan nog inte ses som alltför seriös vad det gäller som lärobok i C/C++ ;)

Din konversation med Bjarne gav väl som jag ser det inte så mycket, egentligen. Inga argument om minneshantering, inget om exekveringstid... utan det ser mer ut som standardsvar som man kan hitta på vilken C++ - sida som helst. Blev faktiskt lite besviken på svaren då de inte tillförde denna diskussion något utöver det redan så uppenbara (sånt som står i alla läroböcker och som föreläsare pläderar). Du bör nu ha i åtanke att det inte var globaler jag talade varmt för, utan funktionsvariabler & pekare som deklareras i samma sats i början av funktionen, prydligt och snyggt. Initiering av dessa sker så klart när det behövs och funktioner ska normalt inte vara särskilt stora, isf ska de delas upp i mindre delar. Så vad det gäller "stylen" känns inte argumenten särskilt relevanta då vi redan har samma tankar i andemeningen om att göra koden lättläst.

Däremot finns frågetecknen kvar kring i vilka fall det ena är vettigare än det andra - rent tekniskt sett. Förmodligen är det en kombination och det skulle vara intressant att utveckla detta.

Som vanligt är såna här intressanta diskussioner tidskrävande. Fram till den 11/6 har jag tentor så jag kan inte engagera mig i detta så som jag vill innan dess. Det finns mera aspekter på din exempelkod, men det kan vara mer intressant att ta fram lite mer rättvisande exempel än endast rekursion utan riktigt nyttjande av initierad data (Mao undvika sånt som en specifik kompilator gärna optimerar bort) och testa med lite olika plattformar.

Medlem sedan juni 200010 432 inlägg
#30

Fick lite tid över och kollade snabbt din rekursion en gång till, men nu med val från tangentbordet om vilken av boolerna som ska vara true. Detta eftersom vi tydligen testat med olika förutsättningar. Fick samma resultat som förut i VC6 så där var det íngen skillnad. Testade samma sak i cygwin g++ och nedan följer asm-dumpen av fallet med lokala deklarationer. Mao är den där optimeringen något som VC7 har för sig vid just denna kod och den optimeringen är ännu inget som kan antas för generellt gångbart för språket C++.

Asm-dump från g++ av funktionen f():

_f__Fv:
	pushl %ebp
	movl %esp,%ebp
	movl $130016,%eax
	call __alloca
	pushl %edi
	pushl %esi
	leal -10000(%ebp),%edi
	xorl %eax,%eax
	cld
	movl $2500,%ecx
	rep
	stosl
	addl $-8,%esp
	pushl $_endl__FR7ostream
	addl $-8,%esp
	leal -10000(%ebp),%eax
	pushl %eax
	addl $-8,%esp
	pushl $LC0
	pushl $_cout
	call ___ls__7ostreamPCc
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreamPCv
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreamPFR7ostream_R7ostream
	addl $16,%esp
	cmpb $0,_b
	je L297
	leal -50000(%ebp),%edi
	movl $LC1,%esi
	cld
	movl $10000,%ecx
	rep
	movsl
	addl $-8,%esp
	pushl $_endl__FR7ostream
	addl $-8,%esp
	leal -50000(%ebp),%eax
	pushl %eax
	addl $-8,%esp
	pushl $LC2
	pushl $_cout
	call ___ls__7ostreamPCc
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreamPCv
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreamPFR7ostream_R7ostream
	addl $16,%esp
L297:
	cmpb $0,_a
	je L298
	leal -130000(%ebp),%edi
	movl $LC3,%esi
	cld
	movl $20000,%ecx
	rep
	movsl
	addl $-8,%esp
	pushl $_endl__FR7ostream
	addl $-8,%esp
	leal -130000(%ebp),%eax
	pushl %eax
	addl $-8,%esp
	pushl $LC4
	pushl $_cout
	call ___ls__7ostreamPCc
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreamPCv
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreamPFR7ostream_R7ostream
	addl $16,%esp
L298:
	incl _count
	addl $-8,%esp
	pushl $_endl__FR7ostream
	addl $-8,%esp
	leal -10000(%ebp),%eax
	pushl %eax
	addl $-8,%esp
	movl _count,%eax
	pushl %eax
	addl $-8,%esp
	pushl $LC5
	pushl $_cout
	call ___ls__7ostreamPCc
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreami
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreamPCc
	addl $16,%esp
	movl %eax,%eax
	pushl %eax
	call ___ls__7ostreamPFR7ostream_R7ostream
	addl $16,%esp
	call _f__Fv
L296:
	leal -130024(%ebp),%esp
	popl %esi
	popl %edi
	movl %ebp,%esp
	popl %ebp
	ret
Medlem sedan juli 2002581 inlägg
#31

PeW skrev:

den optimeringen är ännu inget som kan antas för generellt gångbart för språket C++.

Det finns i varje fall ingen nackdel med att deklarera variablerna där de ska vara, och det finns utöver den här ovan in absurdum diskuterade anledningen både fler och bättre anledningar till att hålla variablerna inom ett så lokalt scope som möjligt.

När man deklarerar klasser gör man ju inte alla medlemmar publika, utan man använder ju sig av privata medlemmar för att kontrollera användandet av variablerna. Exakt samma resonemang kan tillämpas på vanliga variabler.

Om jag ännu inte har lyckats övertyga dig, något som jag tyvärr misstänker, rekommenderar jag en eller allra helst flera av följande alternativ:

  • Köp eller låna boken "Effective C++" av Scott Meyers
  • Gå in och ta upp detta ämne på news://comp.lang.c++
  • Rikta en fråga till bs@cs.tamu.edu
  • Läs "The C++ Programming Language" av Bjarne Stroustrup

Kontinuerlig deklarering av variabler tilläts i C++ av en anledning!

Medlem sedan juni 200010 432 inlägg
#32

Det vi diskuterar är i första hand:

Jag menar att man ska vänta med deklareringen om man kan, medan PeW menar att det inte har någon betydelse för hastigeten och bör undvikas.

Om det nu är absurdum eller ej kan diskuteras, men det var du som startade diskussionen. Vet inte riktigt vad du menar med att "övertyga" mig. Jag har hela tiden vetat att man kan deklarera i scope. Det jag motsatte mig är ett ickekonsekvent deklararerande och du hävdade att det blir effektivare nyttjande av (vad jag menar) cpu:n. Detta ifrågasätter jag - då det krävs lite mer än endast paradigmer för att visa detta. Speciellt när det till syvene og sist landade i en kompilatorspecifik optimering.

Jag har ivf diskuterat pga att jag finner det intressant om huruvida C++ bygger effektivare maskinkod än C. Och isf hur. - Inte för att vare sig hävda mig eller bevisa något.

Jag är nyfiken helt enkelt :)

Således har jag tänkt att spinna vidare på denna diskussion när tillfälle ges, om det passar dig?

Kontinuerlig deklarering av variabler tilläts i C++ av en anledning!

Ja det har jag ju inte förnekat, eller hur? Frågan är som sagt anledningen... för programmeraren eller för cpu:n?

Medlem sedan juli 2002581 inlägg
#33

Det jag har diskuterat är detta:

PeW skrev:

Dels ger det för kompilatorn korrekt kod och dels ger det kod som är betydligt lättare att överblicka.

Korrekt kod är korrekt C++, annars är kompilatorn trasig.

PeW skrev:

1: Att variabler, pekare m.m deklareras initialt i funktioner och inte lokalt "on the fly". Även om de större kompilatorerna i språket C++ (till skillnad från ANSI C) medger detta är det inte säkert att stacken allokeras rätt för det. Stort ansvar vilar på kompilatorn.
2: Att kasta in nya variabler lite hipp som happ ger ett svårläst program i undantaget om de är temporära och om de inte ges självklara och unika namn. Gränsen är hårfin och jag tycker ivf inte att första kodsnutten var särskilt strukturerad.

Båda påståendena är fel.

1: Stacken allokeras alltid rätt och ibland bättre, som vi har sett.

2: Variabler ska vara så lokala som möjligt av samma anledning som klassmedlemmar ska vara privata. Det ökar även effektiviteten då man håller på med listor, vektorer och strängar.

Hur du programmerar är din ensak, men att lära ut sådant som de flesta anser är fel bör undvikas.

PeW skrev:

för programmeraren eller för cpu:n?

För att programmeraren ska kunna skriva snyggare och mer lättläst kod, samt att det passar in med C++:s filosofi om "data hiding".

Medlem sedan juni 200010 432 inlägg
#34

Å herregud... är det den här nivån du vill ha så kunde du väl kläckt ur dig det från början :OO

1: Stacken allokeras alltid rätt och ibland bättre, som vi har sett.

Nej det har vi inte alls sett. Du har presenterat det optimerade resultatet av din VC7:a på just den koden - inget annat. Jag har vidhållit motsatsen med VC6 och g++. Vidare så har vi endast gått igenom funktionens stack. Hur ser det allokerade utrymmet ut i main? Jag har redan gissat på att det utrymmet är större i fallet med de lokala variablerna men du väljer tydligen nu att ignorera den icke avslutade diskussionen och retirera med nån slags missriktad sammanställning.

2: Variabler ska vara så lokala som möjligt av samma anledning som klassmedlemmar ska vara privata. Det ökar även effektiviteten då man håller på med listor, vektorer och strängar.

Du kan inte vara så generell och samtidigt vara så effektiv som möjligt. Lite av att välja mellan äpplen och päron.

Hur du programmerar är din ensak, men att lära ut sådant som de flesta anser är fel bör undvikas.

Jag är ingen lärare och har aldrig haft intentionen att vara lärare. Däremot tar jag upp saker som jag anser vettigt att kommentera. I den koden som föranledde denna (enligt dig) absurda diskussion var det i mitt tycke en ologisk och ostrukturerad skrivning av variabler.
Du generaliserar mina intentioner lite väl långt, min bäste herre ;)

För att programmeraren ska kunna skriva snyggare och mer lättläst kod, samt att det passar in med C++:s filosofi om "data hiding".

Vart tog dina argument om hastighet och minnesutnyttjande vägen? Att det är en filosofisk fråga har jag aldrig förnekat så varför likt Don Quijotte fäkta mot väderkvarnar?

red
Har nu testat VC7 med den enligt dig in till absurdum omdiskuterade koden och då samma cpp-fil som med g++, med vägval från tangentbordet. Resultat: Antalet rekursionssteg blir 7 i båda fallen. Inställningar i VC7: default. Vad hade du för inställningar?

Medlem sedan juni 200010 432 inlägg
#35

Efter att nu ha fått en stund över, i kombination med ett påstående om att jag sprider felaktigheter känns det motiverat med en sammanfattning här. Jag tar gärna kritik, men då bör den vara relevant i sammanhanget och av det konstruktiva slaget. Med en viss tydlighet framkommer det att frågeställningen bör delas upp i två huvudfrågor. Om än kan man nu undra vad frågeställningen egentligen hade för syfte.

1. Vad är effektivast? (med effektivitet avser ivf jag prestanda)
2. Hur ser en lämplig kod ut. (programmeringsfilosofi, struktur m.m)

*Det inte alltid dessa två punkter sammanfaller.

För att sammanfatta vad som framkommit i denna tråd (kan vara intressant för dem som inte orkar läsa hela tråden):

Not. Den som inte orkar läsa detta heller kan gå direkt till den korta sammanfattningen

1.a För varje funktion, metod eller vad man nu väljer att kalla den så allokeras en egen liten stack (förvaringslåda med en viss storlek) vid initieringen av funktionen/metoden. Storleken på den stacken avgörs av antalet variabler och dess storlekar (int = 4, short = 2 osv..). Av det ter det sig ganska självklart att en stack som är exakt så stor som variablerna kräver, är en optimal lösning. Diskussionen handlade dels om minnesutnyttjande och dels om hastighet. Enligt ovan är det då rimligt att anta att en optimerad stack tar minst utrymme.

1.b Vad det gäller hastighet har inte storleken på stacken lika stor betydelse så länge hela funktionen ryms i ett datablock (512, 1024…4096 eller vad nu systemet har). Det som spräcker hastigheten även om funktionen ligger i samma block är i så fall anrop till adresser utanför blocket. (Globala variabler, dynamiskt allokerat minne, konstruktorer i klasser eller funktionsanrop). Anledningen till ovan är att hastigheten med avseende på variabler sätts i relation med hur lång tid det tar att hämta data på given adress. Utanför blocket, så är risken stor att CPU:n måste hämta data i primärminnet, innanför blocket är risken stor att CPU:n hämtar data i cache. Men om man allokerar 10 byte eller 512 byte spelar ingen som helst roll för hastigheten – så länge det ligger i samma block och inte skrivits sönder av något annat i cache vilket kan hända om endera kompilatorn är olydig eller att programmeraren älskar goto-satser.

Vid de små tester som gjorts i tråden (vilket inte på något sätt bör ses som allmänt rådande) har det framkommit:

Res.1 Vid allokering av primitiva datatyper (int, char) spelar det ingen roll vart man skriver dessa i koden. Resultatet blir detsamma men det lämnas stort ansvar på kompilatorn att få det rätt om det ska vara likvärdigt, då testerna med åtminstone 2 kompilatorer av erkänd kvalitet, gav att en del byte blev otillgängliga, trots att de var allokerade – vid ”on the fly” deklaration.

Res.2 Vid deklaration av mer komplexa datatyper (egna objekt, standardklasser m.m) kan man vinna framför allt tid om man deklarerar dessa när de behövs. Dvs tid när de inte används, men samtidigt blir det ett inte alltför omärkbart antal extra klockcykler mitt i körning när de ska användas. Man kan alltså här hävda att det beror på situationen om hur man bör göra. En till uppfattning är att man vinner minnesutrymme när man deklarerar som ovan, men det gav ivf inte mina tester och det är i så fall en följd av att kompilatorn på förhand vet exakt vilken väg programflödet går. Det är mao en kompilatorspecifik situation och overhead för resonemanget.

Res.3 Fallet med rekursionen är inte entydigt och samma kod ger olika resultat i olika kompilatorer. Generellt sett ska man alltid vid rekursion ha ett basfall som sätter stopp och man bör kunna förutse när detta inträffar för att kunna allokera tillräckligt med utrymme för detta. Rekursion är hopplöst att optimera vad det gäller stackutrymme i ett språk som C++ och C. Valet mellan körbarhet och minnesutnyttjande blir tydligt vid liknande tillämpningar. Poängen med Sang-drax körning i VC-7 var att funktionens stack blev mindre. Anledningen till det kan man kanske tvista om, men i vilket fall så är det en optimering gjord av kompilatorn och denna optimering har jag inte lyckats få med g++, VC6 och VC7(!). Därmed menar jag (även om det var en smart optimering) att den ännu inte kan ses som allmänt rådande för en C++ kompilator. Faktum om att allokeringen av variabler sker i samband med att stacken allokeras, oavsett om det är ”on the fly” eller initialt som dessa variabler deklareras, kvarstår.

Sang-drax hänvisar till en del böcker. Men inte till vad i dessa böcker som förtäljer motsatsen till resonemanget om prestandavinster här ovan. Jag tvivlar inte på att dessa böcker är skrivna av kompetenta personer och jag tvivlar inte på uppgifternas riktighet i de sammanhang som uppgifterna är tänkta. Då kommer den filosofiska frågan. Vad menas med effektiv kod? Till slut framkom det att det inte var CPU:n som avsågs utan programmeringsfilosofin.

2.a Till att börja med så kan vi konstatera att en filosofi man eftersträvar i såväl C++ som i andra språk är gömmande av data. I en klass anger man private, protected eller public. I C kan man också gömma data, men det är mer omständigt. En bra sak med C++ är att man kan utnyttja scope för gömmande av data, men det betyder inte att det är bra att alltid gömma data, lika lite som det alltid bäst ur hastighetssynpunkt.
Vi kan även konstatera att det är bra att ge variabler beskrivande namn. Nu skulle man kunna vända det där med fördelen av gömmande av data till en nackdel. Nämligen så är det inte direkt en uppmuntran till unikt beskrivande namn med gömmande av data, samma namn kan förekomma hur många gånger som helst om de är i olika scope. Kan bli tillrört och svårläst.

2.b En annan filosofi har med programflöde och storlek på funktioner/metoder att göra. Den allmänna uppfattningen är att dessa bör hållas så små som möjligt och om det behövs brytas ned i flera. Av detta kan man då undra hur många scope det egentligen blir i en funktion/metod och om man har valt den optimala lösningen om man sitter med en många selektioner. För att återknyta till gömmande av data, så ger ett inträde i en funktion/metod automatiskt ett scope och deklarationerna där är gömda. Hur stor funktion är det när man behöver gömma data inom funktionen/metoden? Sang-drax nämnde att man måste ”scrolla” så det tyder på mycket kod i dessa funktioner vilket kan diskuteras hur vettigt det är med tanke på prestanda.

Målet är detsamma, men vägen dit är inte alltid samma väg. Jag påminner om det jag skrev i tråden som föranledde denna diskussion: ” Men smaken är väl som baken även i detta fall” och när det kommer till slutfasen på denna tråd så verkar resultatet landa där med ;)

Den kortfattade sammanfattningen:

Strukturerad deklaration i början av en funktion:
+ lätt att hitta, lätt att förändra
- om det är en konstruktor som anropas och den ska bara ’kanske’ användas kan det bli slöseri med utrymme.

Strukturerad deklaration i början av ett block (scope)
+ lätt att göra
+ lättare att hitta
- ger samma beteende som ovan, angående krav på kompilatorn

Ostrukturerad deklaration lite hipp som happ:
+ lätt att göra
- ställer krav på kompilatorn med avseende på allokeringen av stacken
- kan bromsa hastigheten mitt i körning om konstruktor anropas
- kan vara ett elände att avlusa

Jag anser att min välmenande kommentar som föranledde denna diskussion passar in på närmast ovanstående – inget annat.

Ja, det var nog allt jag hade att säga om detta :)

Vill man spetsa till resonemangen och kanske t.o.m lägga in parametrar som inte ens har med topic att göra, så varsågod - jag lägger ivf ned den här diskussionen nu.

red
Fixade ett sånt där förbenat stavfel

164 ms totalt · 3 externa anrop · v20260731065814-full.19008ce9
0 ms — hämta forumlista (cache)
0 ms — hämta statistik (cache)
161 ms — hämta tråd, inlägg och bilagor (db)