[C++] Pass by reference/pointer/gewoon verschil?

Pagina: 1 2 Laatste
Acties:
  • 1.029 views sinds 30-01-2008
  • Reageer

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Wat Delphi doet moet Delphi weten. In C++ is een reference type een afgeleid type, en kun je niet eens objecten van reference type hebben. Of je nou een een object op de stack zet, of op de heap, in beide gevallen heb je gewoon een object/value daar. Dat object kun je kopieren, als het een copy ctor heeft (of alle members er een hebben), je kunt er pointers naar toe laten wijzen, en je kunt er references naar toe laten wijzen.

Als je een reference in een object hebt, dan wordt die inderdaad meegekopieerd, maar dat heeft natuurlijk geen consequenties voor deleten. Deleten doe je met pointers, niet references :) Die reference kun je dus lastig naar een nieuw object laten wijzen; welk nieuw object zou dat dan moeten zijn?

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Weng
  • Registratie: Juni 2001
  • Laatst online: 11-05-2024

Weng

Are y'all ready kids

MSalters schreef op 28 december 2003 @ 14:03:
Als je een reference in een object hebt, dan wordt die inderdaad meegekopieerd, maar dat heeft natuurlijk geen consequenties voor deleten. Deleten doe je met pointers, niet references :) Die reference kun je dus lastig naar een nieuw object laten wijzen; welk nieuw object zou dat dan moeten zijn?
Wat ik dacht dat LordLarry bedoelde was:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
class CObject1{
    private:
        CObject2 * Obj2;

    public:
        ~CObject(){ 
            if(Obj2 != NULL){
                delete Obj2;
                Obj2 = NULL;
            }
        }
}

Je hebt natuurlijk wel gelijk dat je geen reference delete, maar een pointer. Maar dit heeft lijkt mij toch wel consequenties voor het deleten als je CObject1 kopieert met de assignment operator.

[ Voor 7% gewijzigd door Weng op 28-12-2003 14:36 ]

Aye aye captain


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
Weng schreef op 28 december 2003 @ 13:47:
[...]

Dat is bij Delphi wel het geval idd. Maar bij C++ hoeft niet alles dynamisch aangemaakt te worden mbv een pointer. In het geval als je het niet dynamisch aanmaakt is de klasse/object geen reference type.


[...]

Die referentie wordt gewoon gekopieerd ja. Niet erg handig bij het deleten/free-en ;)

/Edit: RayNbow was me net voor.
Dit klinkt een beetje verwarrend zo. Ik neem aan dat met 'referentie' hier eigenlijk pointer wordt bedoelt.

In C++ kunnen de data members uit 2 types bestaan:

1) De data zelf
2) Een verwijzing (pointer) naar data.

In een taal als Java kunnen basic (non class) types alleen als data in een object voorkomen en class types alleen als pointer. Omdat je geen keus hebt is er ook geen expliciete syntax voor.

Bv

code:
1
2
3
4
5
6
7
8
9
10
class dataTest {
 char a[100]; 
};

class passTest {
 int a;
 int* b;
 dataTest c;
 dataTest* d;
};


Als we nu naar een object van class passTest kijken dan staat het volgende in het geheugen. (aantal bits zijn een idencatie en machine afhankelijk)

------------------------start mem--------------
32 bits : integer
---------
32 bits: integer pointer
---------
800 bits: object dataTest
---------
32 bits: dataTest pointer
--------------------------end mem--------------

Als je nu een passTest object by value doorgeeft dan:

Wordt een zogenaamde 'shallow copy' van het passTest object gemaakt. Dit houdt in dat het hele gebied van start mem t/m end mem naar het nieuwe object copieerd wordt (indien er geen copy ctors zijn zoals in bovenstaand voorbeeld).

1) In het bijzonder houdt dit in dat als de int* b naar address '1000' verwijst, er rechtstreeks het getal '1000' in het object staat. Deze wordt gecopieerd. In het nieuwe object staat dus ook het getal '1000'.

M.a.w. beide objecten wijzen naar de zelfde integer op address 1000. -> de integer zelf is niet gecopieerd, alleen de verwijzing erna. Als je nu b gaat deleten (bv met een dtor vanuit passTest) dan gaat dit bijna altijd fout omdat beide objecten niet weten dat ze een integer delen.

(een 'deep copy' betekent dat je dmv copy ctors een nieuwe integer maakt bij het copieeren en b dan naar deze integer gaat verwijzen. De nieuwe integer krijgt de waarde van de integer waarna b in het originele object wees)

2) Tevens houdt het in dat de 800bits van het embedded object dataTest c wel helemaal gecopieerd wordt. Als je alleen de waarde van a in de functie waaraan je je passTest object meegeeft wilt veranderen dan wil je dit juist weer niet.

Als je nu een passTest object by pointer doorgeeft dan:

Dan wordt er een verwijzing naar het passTest object doorgegeven en voorderest niks. By pointer semantics bestaat eigenlijk niet. Het is gewoon by value semantics. (de pointer wordt by-value doorgegeven).

By-reference is voor het grootste gedeelte syntactic sugar; het is bijna hetzelfde als een pointer, maar 'address of' en dereferencing gebeuren automatisch.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • DieterVDW
  • Registratie: Juli 2002
  • Laatst online: 12-02-2017
Ik denk dat ik het eindelijk doorheb!
Bedankt hé mensen!

  • LordLarry
  • Registratie: Juli 2001
  • Niet online

LordLarry

Aut disce aut discede

Weng schreef op 28 december 2003 @ 13:47:
Dat is bij Delphi wel het geval idd. Maar bij C++ hoeft niet alles dynamisch aangemaakt te worden mbv een pointer. In het geval als je het niet dynamisch aanmaakt is de klasse/object geen reference type.
Delphi is idd waar ik de meeste ervaring mee heb en ik heb ook altijd gedacht dat dit bij C++ op dezelfde manier ging. Blijkbaar niet dus :) Bedankt voor het duidelijk maken mensen.

We adore chaos because we like to restore order - M.C. Escher


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
Ok dan :)

Het wordt trouwens wel wat ingewikkelder als je multiple inheritance erbij gaat betrekken, maar goed dat is voor de liefhebbers en een nieuw topic ;)

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

flowerp: waarom wordt het dan ingewikkelder :? inheritance heeft weinig te maken met parameter passing, behalve natuurlijk als het verwachte type van het argument een base class is van het gegegeven argument. Maar dan is het bij multiple inheritance niet een ander geval oid. De (default) copy ctor wordt gewoon aangeroepen

C++:
1
2
3
4
5
6
7
8
9
10
11
struct A { };
struct B { };
struct C : public A, public B { };

void f (A);

void g ()
{
    C c;
    f (c);
}


Hier wordt voor de aanroep van f () in feite A::A (const A &) aangeroepen, met als parameter de c. Maar die is niet gedefinieerd, wat natuurlijk resulteert in de default member-wise copy, maar alleen dus alle members van A, en die van B of C worden niet in beschouwing genomen

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
Je geeft nu zelf al aan dat het met multiple inheritance ingewikkelder wordt. Niet alleen de parameter overdracht, maar ook de hele memory layout van een object (en daar ging dit topic ook over, hoe een object nu eigenlijk in het geheugen staat).

Bv een class C met 2 base classes A en B, waarbij A het meest links staat in de class header en B het meest rechts zal in het geheugen met de layout van A beginnen, gevolgt door de layout van B en daarna de layout van C.

Als je nu een C doorgeeft aan een functie die een void* als argument neemt (komt nog steeds voor in de praktijk) en dan via dezelfde (of een andere functie) deze als een void* terug krijgt en deze dan cast naar een B (wat opzich legaal is zou je denken), dan is het gedrag undefined. Dit omdat de pointer naar het begin van het C memory block wijst, en daar een A staat. Via de void* ben je info kwijt geraakt, en weet de compiler niet meer dat het een C was, en dat dus B na het A gedeelte komt.

Een zelfde soort verhaal geldt voor pointer to memberfunctions. Die werken ook alleen op de meest linkse base class in de class header van de child class.

Andere dingen die de zaak ingewikkeld maken zijn bv diamond shaped class hierarchies, of 2 dezelfde base classes via een aparte chain, bv A heeft B en C als base, en zowel B als C hebben een X als base. Als je dan vanuit een member functie in A een data member in X wilt benaderen heb je 2 versies (die je via bv casting paths kan benaderen, dwz eerst this naar een C casten, en dan C naar een A casten, en op dat object de data member benaderen).

Deze zaken zijn niet parameter passing specific ansich, maar treden wel vaak op bij parameter passing. Het is ook de hele definitie van polymorphic gedrag: een subobject kunnen geven, overal waar een base object verwacht wordt.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

flowerp schreef op 29 december 2003 @ 18:21:
Je geeft nu zelf al aan dat het met multiple inheritance ingewikkelder wordt. Niet alleen de parameter overdracht, maar ook de hele memory layout van een object (en daar ging dit topic ook over, hoe een object nu eigenlijk in het geheugen staat).
oh ok, memory layout, ik dacht dat het ging over het doorgeven van objecten aan functies by-value, wat dus in de laatste 10 reacties werd besproken, met uitzondering van die ene van jou, maar ik dacht dat dat was om de manieren van doorgeven duidelijk te maken 8)7

Maar goed, hoewel misschien interessant als je je wilt verdiepen in C++ implementatie, lijkt me het weinig interessant voor manieren van doorgeven. Het doet er namelijk niet echt toe in welke volgorde ze staan, je hoeft alleen maar te weten dat als je functie een Base verwacht dat je dan in je functie ook een Base hebt, en niet stiekem een Derived. En die wordt gemaakt door de (default) copy ctor van Base aan te roepen, waarbij de default copy ctor bestaat uit een memberwise copy (waarbij er dus weer (default) copy ctors op de members worden aangeroepen, etc.)
Bv een class C met 2 base classes A en B, waarbij A het meest links staat in de class header en B het meest rechts zal in het geheugen met de layout van A beginnen, gevolgt door de layout van B en daarna de layout van C.
dan vergeet je nog het geval waarbij A en B geen vtable hebben en C wel, waardoor je eerst een pointer krijgt naar de vtable van C, daarna A, daarna B, en daarna de members van C. Om nog maar te zwijgen over virtual base classes ;)
Als je nu een C doorgeeft aan een functie die een void* als argument neemt (komt nog steeds voor in de praktijk) en dan via dezelfde (of een andere functie) deze als een void* terug krijgt en deze dan cast naar een B (wat opzich legaal is zou je denken), dan is het gedrag undefined. Dit omdat de pointer naar het begin van het C memory block wijst, en daar een A staat. Via de void* ben je info kwijt geraakt, en weet de compiler niet meer dat het een C was, en dat dus B na het A gedeelte komt.
Mja, maar dat is net zoiets als een int * casten naar void * en daarna naar float *, dan kun je ook niet verwachten dat het goed gaat werken. Datzelfde verhaal gaat natuurlijk ook op voor C * -> void * -> B *, en het is dan ook gewoon foute code :)

[ Voor 19% gewijzigd door .oisyn op 29-12-2003 18:48 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 28 december 2003 @ 14:43:
In C++ kunnen de data members uit 2 types bestaan:

1) De data zelf
2) Een verwijzing (pointer) naar data.
Dit klopt voor een groot deel, je zou eventueel nog twee vormen toe kunnen voegen: Container classes en smart pointers. Een class optional<int> is een type wat een int kan bevatten, maar niet hoeft te bevatten (container met 0 of 1 element), std::vector<int> een container met 0, 1 of veel. Smart pointers zijn vergelijkbaar met gewone pointers, alleen hoef je je zelf minder zorgen te maken over het gedrag van kopieen. Een boost::shared_ptr<int> wijst naar een int, en alleen de laatste kopie daarvan doet de daadwerkelijke delete.
Bv
code:
1
2
3
4
5
6
7
8
9
10
class dataTest {
 char a[100]; 
};

class passTest {
 int a;
 int* b;
 dataTest c;
 dataTest* d;
};


Als we nu naar een object van class passTest kijken dan staat het volgende in het geheugen. (aantal bits zijn een idencatie en machine afhankelijk)

------------------------start mem--------------
32 bits : integer
---------
32 bits: integer pointer
---------
800 bits: object dataTest
---------
32 bits: dataTest pointer
--------------------------end mem--------------

Als je nu een passTest object by value doorgeeft dan:

Wordt een zogenaamde 'shallow copy' van het passTest object gemaakt. Dit houdt in dat het hele gebied van start mem t/m end mem naar het nieuwe object copieerd wordt (indien er geen copy ctors zijn zoals in bovenstaand voorbeeld).

1) In het bijzonder houdt dit in dat als de int* b naar address '1000' verwijst, er rechtstreeks het getal '1000' in het object staat. Deze wordt gecopieerd. In het nieuwe object staat dus ook het getal '1000'.

M.a.w. beide objecten wijzen naar de zelfde integer op address 1000. -> de integer zelf is niet gecopieerd, alleen de verwijzing erna. Als je nu b gaat deleten (bv met een dtor vanuit passTest) dan gaat dit bijna altijd fout omdat beide objecten niet weten dat ze een integer delen.

(een 'deep copy' betekent dat je dmv copy ctors een nieuwe integer maakt bij het copieeren en b dan naar deze integer gaat verwijzen. De nieuwe integer krijgt de waarde van de integer waarna b in het originele object wees)

2) Tevens houdt het in dat de 800bits van het embedded object dataTest c wel helemaal gecopieerd worden. Als je alleen de waarde van a in de functie waaraan je je passTest object meegeeft wilt veranderen dan wil je dit juist weer niet.
Voor alle duidelijkheid: Zowel a,b,c en d worden zonder wijzigingen gekopieerd. Als de originele b naar de originele a wijst, dan wijst de kopie van naar het origineel van a (zoals gezegd), en voor c en d gaat hetzelfde op. Als de originele d naar de originele c wijst, dan doet de kopie dat ook. Er is geen verschil (behalve in grootte) tussen int of dataTest.

Ik snap dus het stukje "de 800bits van het embedded object dataTest c wel helemaal gecopieerd worden" niet. Ze worden precies net zo gekopieerd als de 32 bits van int a.
Als je nu een passTest object by pointer doorgeeft dan:

Dan wordt er een verwijzing naar het passTest object doorgegeven en voorderest niks. By pointer semantics bestaat eigenlijk niet. Het is gewoon by value semantics. (de pointer wordt by-value doorgegeven).
Het gebruik van een pointer heet toch echt by-reference. De pointer zelf wordt niet als een pointer doorgegeven. Dat zou een passTest** worden, maar die zou je logischerwijs dan als een passTest*** moeten doorgeven, en die weer als een passTest****. Zo werkt dat dus niet. Als je een passTest meegeeft, dan kopieer je sizeof(passTest) bytes, en als je een passTest* dooegeeft, dan kopieer je sizeof(passtest*) bytes. De benaming by-value wordt gebruikt als er een kopie van het relevante object wordt gemaakt. De passTest* pointer is zelf niet het relevante object, het relevante object is wat die pointer referenced. Daarom by-reference.
By-reference is voor het grootste gedeelte syntactic sugar; het is bijna hetzelfde als een pointer, maar 'address of' en dereferencing gebeuren automatisch.
yep, plus de automatische conversie voor const&

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
.oisyn schreef op 29 december 2003 @ 18:42:
[...]


oh ok, memory layout, ik dacht dat het ging over het doorgeven van objecten aan functies by-value
Om by-value te begrijpen is natuurlijk kennis van de layout nodig. Vandaar :)

wat dus in de laatste 10 reacties werd besproken, met uitzondering van die ene van jou, maar ik dacht dat dat was om de manieren van doorgeven duidelijk te maken 8)7
[...]
dan vergeet je nog het geval waarbij A en B geen vtable hebben en C wel, waardoor je eerst een pointer krijgt naar de vtable van C, daarna A, daarna B, en daarna de members van C. Om nog maar te zwijgen over virtual base classes ;)
Tevens heb je nog een probleem als A en B geen vtable hebben, en dat is dat dynamic casting niet meer werkt. Natuurlijk is dynamic casting geen parameter passing techniek, maar treed wel vaak op naar aanleiding van parameter passing. (namelijk in een enkel blok code zijn de types 'meestal' al bekend voor de programmeur en hoeft er in de regel weinig dynamic gecast te worden).
[...]
Mja, maar dat is net zoiets als een int * casten naar void * en daarna naar float *, dan kun je ook niet verwachten dat het goed gaat werken. Datzelfde verhaal gaat natuurlijk ook op voor C * -> void * -> B *, en het is dan ook gewoon foute code :)
Volgens mij is dat toch fundamenteel anders. Namelijk, een pointer naar een basic type is niks anders dan een memory address. Het type waarna het verwijst is pas bij dereferencen van belang. Bv, een pointer naar het address 1000 zal als het bit patroon voor 1000 gecodeerd worden. Of er op address 1000 een integer, of float staat maakt voor de pointer representatie niks uit.

M.a.w placement new op address 1000 voor een int, en later voor een float, ook op adress 1000 zal dezelfde pointer opleveren (met alleen een andere 'flag' in de symbol table van de compiler).

Bij multiple inheritance zal een pointer naar een object met 2 base classes echt van waarde veranderen wanneer je cast naar de 2de base. Dit is de zogenaamde base offset.

Bv, de eerder genoemde C begin op 1000, en (stel) A begint ook op 1000, B op 1020 en het C specifieke deel op 1030.

Een pointer naar C, gecast naar A zal hetzelfde bitpatroon bevatten, maar een pointer naar C, gecast naar B zal in dit geval een ander bitpatroon hebben, namelijk 1020 ipv 1000.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

flowerp schreef op 29 december 2003 @ 21:01:
Tevens heb je nog een probleem als A en B geen vtable hebben, en dat is dat dynamic casting niet meer werkt.
maar dat heb je alleen nodig als je by-reference past, want voor by-value kun je namelijk keiharde aannames doen over het type dat je in je functie hebt, namelijk gewoon het type dat in de argumentenlijst staat :)
Volgens mij is dat toch fundamenteel anders. Namelijk, een pointer naar een basic type is niks anders dan een memory address. Het type waarna het verwijst is pas bij dereferencen van belang.
dat is bij mijn vergelijking met int en float natuurlijk ook het geval
Bv, een pointer naar het address 1000 zal als het bit patroon voor 1000 gecodeerd worden. Of er op address 1000 een integer, of float staat maakt voor de pointer representatie niks uit.
nee klopt, maar dat beweer ik ook helemaal niet.

Wat ik echter wel beweer is dat als je een C * terugcast naar void *, en daarna cast naar B *, dat dat gewoon technisch gezien foute code is. Het is fout ongeacht of B nou een base van C is of niet. Goed, het kan 'toevallig' goed gaan als het wel een base is, maar in principe is het gewoon foute code en moet je het nooit gebruiken. De enige correcte manier is terugcasten naar een C *, en dan een cast naar B *. Het object op het adres van die pointer is ook geen B maar een C. Pas als je weet dat het een C is, kun je er de B uithalen, en niet eerder.
Bij multiple inheritance zal een pointer naar een object met 2 base classes echt van waarde veranderen wanneer je cast naar de 2de base. Dit is de zogenaamde base offset.
niet alleen bij multiple inheritance, maar ook bij single inheritance. Als C bijvoorbeeld alleen een base class A heeft, waarbij A geen vtable heeft en C wel. Dat de base class zich op hetzelfde adres bevindt lijkt dus eerder uitzondering dan regel als je alleen kijkt naar alle mogelijkheden die je hebt. Ik zou dit geval dan ook gewoon als uitzondering willen bestempelen, en niet multiple inheritance.

Vandaar nogmaals, als je iets cast naar void * en dan cast naar een ander type dan het origineel is gewoon fout fout fout. Ook al bevindt dat andere type zich toevallig op hetzelfde adres, je moet er gewoon niet eens vanuit willen gaan.

Ik ben dus nog steeds van mening dat mijn int * -> void * -> float * vergelijking dezelfde is als B * -> void * -> A *, ook al is A de enige base van B. Het is natuurlijk ook gewoon vragen om moeilijkheden, als je je classdefinitions aanpast dan is je code niet eens meer correct, terwijl als je terugcast naar B * en vervolgens naar A * wel goed blijft werken :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 29 december 2003 @ 21:01:
Volgens mij is dat toch fundamenteel anders. Namelijk, een pointer naar een basic type is niks anders dan een memory address. Het type waarna het verwijst is pas bij dereferencen van belang. Bv, een pointer naar het address 1000 zal als het bit patroon voor 1000 gecodeerd worden. Of er op address 1000 een integer, of float staat maakt voor de pointer representatie niks uit.
Dacht je? Niet alle C++ compilers in de wereld zijn MSVC++, en zelfs daar weet je niet wat voor mooie features er misschien in de toekomst in komen. Wat dacht je van een 64 bits pointer, waarvan de bovenste 20 bits in debug mode het type bevatten? 44 bits is genoeg geheugen, en een miljoen types is meestal ook wel genoeg. Een andere reden kan zijn dat een pointer afhankelijk van z'n type in een verschillend register geladen wordt, en dat sommige pointer-registers automatisch aligment afdwingen. Als je dan een float* hebt, dan zou een CPU kunnen eisen dat de laatste 2 bits 0 zijn.
Kortom: een pointer is soms niet meer dan een memory address.
M.a.w placement new op address 1000 voor een int, en later voor een float, ook op adress 1000 zal dezelfde pointer opleveren (met alleen een andere 'flag' in de symbol table van de compiler).
Nee, want placement new heeft niks met symbols te maken. Die int krijgt namelijk geen naam, alleen een adres. Het zou ook niet kunnen, want placement new is een run-time operatie, en de symbol table is compiler-time.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
MSalters schreef op 30 december 2003 @ 03:43:
[...]
Dacht je? Niet alle C++ compilers in de wereld zijn MSVC++
En niet alle andere compilers zijn GCC... beiden waarheden, maar what's the point? Of doel je soms op het geval waarin verschillende object files gecompileerd werden door verschillende compilers en dan gelinked?
, en zelfs daar weet je niet wat voor mooie features er misschien in de toekomst in komen. Wat dacht je van een 64 bits pointer, waarvan de bovenste 20 bits in debug mode het type bevatten?
Mischien in debug mode, maar extra info in pointers stoppen lijkt me echt absoluut not-done. (heb er nog nachtmerries van toen in 1988 ofzo mijn 68000 code op een 68020 moest draaien waarbij ik ook allemaal extra bits voor eigen dingen had gebruikt)
Als je dan een float* hebt, dan zou een CPU kunnen eisen dat de laatste 2 bits 0 zijn.
En dat is een heel goed punt! :)
Inderdaad is dit zo bij sommige architecturen, en natuurlijk ook voor structs of arrays.

Toch lijkt het me dat een pointer naar een integer, wanneer gecast naar een pointer naar float nog steeds hetzelfde bitpatroon heeft, zelfs als de target machine allignment afdwingt. Wanneer je deze terug-cast naar een pointer naar integer zul je nog steeds hetzelfde patroon hebben. Of heb je onomstotelijk bewijs (bv passage in de standaard) dat het niet zo is? (om eerlijk te zijn, ik nam het aan, maar probeerde het nog nooit)
Kortom: een pointer is soms niet meer dan een memory address.
En in de andere gevallen is het dan? (die extra bits buiten de address space gebruiken geloof ik niet dat een compiler dat doet)
Nee, want placement new heeft niks met symbols te maken. Die int krijgt namelijk geen naam, alleen een adres. Het zou ook niet kunnen, want placement new is een run-time operatie, en de symbol table is compiler-time.
Duh! 8)7 Ik bedoel natuurlijk het symbol wat de pointer gaat bevatten. In het eerste geval bv int* a, en in het tweede geval bv float* b. In beiden gevallen zullen ze dezelfde 'waarde' (bit patroon) bevatten. Dwz, als je niet van allignment uitgaat, of als ze beide toevallig hetzelfde allignen.

Op het address waar de int* en float* heenwijzen staat dan natuurlijk wat anders.

[Ik vraag me trowuens af of je met placement new niet auto alignment overrided. Dat is mischien wel eens interesant om te testen. Ik bedoel, je wilt dat een data item in dat stuk geheugen wordt gemaakt wat jij opgeeft. Stel dat het precies de grote heeft van het item wat jij maakt, maar niet juist alligned is. Wat gebeurt er dan? Een runtime exception? Een compiler error? Vertraagde runtime? (bv cpu moet 2 loads doen ipv 1), of... ?]


Ik heb btw een pascal compiler geschreven in C++ mbv flex en bison dus ik weet wel ongeveer hoe compilers werken (maar kan natuurlijk altijd nog dingen bijleren :) ) 8)7

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 30 december 2003 @ 13:28:
Mischien in debug mode, maar extra info in pointers stoppen lijkt me echt absoluut not-done. (heb er nog nachtmerries van toen in 1988 ofzo mijn 68000 code op een 68020 moest draaien waarbij ik ook allemaal extra bits voor eigen dingen had gebruikt)
Not done voor user code, maar dat is omdat je niet weet wat de compiler doet. Een compiler kan het wel.
Toch lijkt het me dat een pointer naar een integer, wanneer gecast naar een pointer naar float nog steeds hetzelfde bitpatroon heeft, zelfs als de target machine allignment afdwingt. Wanneer je deze terug-cast naar een pointer naar integer zul je nog steeds hetzelfde patroon hebben. Of heb je onomstotelijk bewijs (bv passage in de standaard) dat het niet zo is? (om eerlijk te zijn, ik nam het aan, maar probeerde het nog nooit)
Geloof je het als ik zeg dat als ik aan dat stuk heb lopen schrijven? :) Het is zogenaamd undefined behavior, wat betekent dat de meeste compilers voor de snelste oplossing kiezen. Dat geldt voor alle reinterpret_casts, dus ook deze.
[Ik vraag me trowuens af of je met placement new niet auto alignment overrided. Dat is mischien wel eens interesant om te testen. Ik bedoel, je wilt dat een data item in dat stuk geheugen wordt gemaakt wat jij opgeeft. Stel dat het precies de grote heeft van het item wat jij maakt, maar niet juist alligned is. Wat gebeurt er dan? Een runtime exception? Een compiler error? Vertraagde runtime? (bv cpu moet 2 loads doen ipv 1), of... ?]
Undefined behavior, in de praktijk langzamer op x86, runtime abort op Sparc, afhankelijk van de settings op Power (support voor misalignment vertraagt alle loads IIRC). Je kunt het iha niet compile-time bepalen, maar waar dat kan is een compiler error ook legaal.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
MSalters schreef op 30 december 2003 @ 13:38:
Not done voor user code, maar dat is omdat je niet weet wat de compiler doet.
Het was assembly code, dus er ging alleen nog een assembler overheen die de code bijna 1 op 1 naar machine code vertaalde. Er kwam dus geen compiler aan te pas. De extra bits (dacht bit 24 t/m 31, maar het is 15 jaar geleden) had ik voor prive dingen gebruikt. Dat ging goed op de 68000, maar de 68020 verslikte zich erin omdat die wel een wijdere address bus had.
Een compiler kan het wel.
Maar een compiler weet toch niet op welke toekomstige cpu's de code moet gaan draaien?

Mischien dat ik (als compiler schrijver) nu rare fratsen met 64bits pointers kan uithalen zodat de 'getargette' c/c++ runtime sommige dingen wat makkelijker kan doen. Namelijk de huidige 64bits cpu's gebruiken bv toch alleen maar de eerste 40bits. Echter, wie zegt dat er niet ooit 64bits cpu's komen die wel meer dan 40bits voor address gebruiken. Gaat de runtime dan voor alle legacy code alle pointers guarden ofzo?

(of zit ik nu helemaal verkeerd te denken?)
Geloof je het als ik zeg dat als ik aan dat stuk heb lopen schrijven? :)
Kewl :) Om welk stuk gaat het precies?
Undefined behavior, in de praktijk langzamer op x86, runtime abort op Sparc, afhankelijk van de settings op Power (support voor misalignment vertraagt alle loads IIRC). Je kunt het iha niet compile-time bepalen, maar waar dat kan is een compiler error ook legaal.
Dat is inderdaad wat ik dacht dat er zou gebeuren. In het algemeen is het dus met placement new uitkijken, alhoewel de meeste allocaties waarop je placement new doet vaak al alligned zijn. Het is dan voornamelijk uitkijken als je zelf gaat rekenen met de geheugen gebieden. (bv een eerdere grote allocatie zelf verdelen over drie kleinere objecten).

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 30 december 2003 @ 17:49:
Maar een compiler weet toch niet op welke toekomstige cpu's de code moet gaan draaien?

Mischien dat ik (als compiler schrijver) nu rare fratsen met 64bits pointers kan uithalen zodat de 'getargette' c/c++ runtime sommige dingen wat makkelijker kan doen. Namelijk de huidige 64bits cpu's gebruiken bv toch alleen maar de eerste 40bits. Echter, wie zegt dat er niet ooit 64bits cpu's komen die wel meer dan 40bits voor address gebruiken. Gaat de runtime dan voor alle legacy code alle pointers guarden ofzo?
Nee, de debug runtime stript de bovenste 20 bits eraf nadat het type gechecked is. In release mode laat je die bits gewoon altijd 0, en &((1<<21)-1) is dan ook niet nodig.
Je zou dit zelfs op de x86 kunnen doen. Maak C++ pointers 64 bits, en stop in de bovenste 32 bits het type. Die bits vallen er automatisch vanaf als je de pointer in een adres register laadt. Er is geen harde reden waarom C++ pointers identiek moeten zijn aan CPU adressen, en de oude 32 bits FAR pointers op de 8086 zijn een goed voorbeel daarvan.
Kewl :) Om welk stuk gaat het precies?
reinterpret_cast, om dlopen en GetProcAddress te laten werken met function pointers en object pointers. Dat werkt in C met C casts wel. In C++ is het geen undefined behavior maar diagnostic required, wat een beetje pijnlijk is. Nou willen we ook geen extra undefined behavior introduceren, dus ik mag iets moois bedenken om aan te geven dat we bepaalde extensies gedogen.
[...]
Dat is inderdaad wat ik dacht dat er zou gebeuren. In het algemeen is het dus met placement new uitkijken, alhoewel de meeste allocaties waarop je placement new doet vaak al alligned zijn. Het is dan voornamelijk uitkijken als je zelf gaat rekenen met de geheugen gebieden. (bv een eerdere grote allocatie zelf verdelen over drie kleinere objecten).
malloc is altijd aligned, en new char[] ook. Daarna wordt het lastig, maar dat is een probleem wat met de volgende standaard aangepakt wordt (alignof( ) o.i.d. )

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
Ik bedoelde natuurlijk alleen de release versie. Ik had het nog in mijn post boven erbij willen zetten maar vergat het. Mijn fout.
Maak C++ pointers 64 bits, en stop in de bovenste 32 bits het type. Die bits vallen er automatisch vanaf als je de pointer in een adres register laadt.
Als je ze in een 32bits register laadt. Maar wat gebeurt er met een 64bits register?
Er is geen harde reden waarom C++ pointers identiek moeten zijn aan CPU adressen,
Om inline asm te kunnen mixen met c/c++ ?

Maar mischien dat ik er wel helemaal naast zit met mijn gedachten. Met gcc 3.1 op OS X 10.2.6 geeft het volgende aan dat int* en float* iniedergeval op een ppc met gcc wel degelijk gelijk zijn:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

int main () {

    unsigned long x;
    int a = 10;

    std::cout << "int value before casts:" << a << std::endl << std::endl;
    
    int* b = &a;
    x = reinterpret_cast<unsigned long>(b);
    std::cout << "int pointer "<< b << std::endl;
    std::cout << "long view of int pointer " << x << std::endl;

    float* c = reinterpret_cast<float*>(b);
    x = reinterpret_cast<unsigned long>(c);
    std::cout << "float pointer " << c << std::endl;
    std::cout << "long view of float pointer "<< x << std::endl;

    int* d = reinterpret_cast<int*>(c);
    x = reinterpret_cast<unsigned long>(d);
    std::cout << "int pointer " << d << std::endl;
    std::cout << "long view of int pointer " << x << std::endl;

    std::cout << std::endl<< "int value after casts:" << *d << std::endl;
    

    return 0;
    
}


Dit geeft de volgende output:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Computer-van-Augustientje:~/MyProjects/testit] karin% g++ -v
Reading specs from /usr/libexec/gcc/darwin/ppc/3.1/specs
Thread model: posix
Apple Computer, Inc. GCC version 1175, based on gcc version 3.1 20020420 (prerelease)
[Computer-van-Augustientje:~/MyProjects/testit] karin% g++ pointerCastTest.cpp
[Computer-van-Augustientje:~/MyProjects/testit] karin% ./a.out
int value before casts:10

int pointer 0xbffffbd4
long view of int pointer 3221224404
float pointer 0xbffffbd4
long view of float pointer 3221224404
int pointer 0xbffffbd4
long view of int pointer 3221224404

int value after casts:10
en de oude 32 bits FAR pointers op de 8086 zijn een goed voorbeel daarvan.
Hmmm, interesant :) (ik ben nooit veel met de 8086 bezig geweest, de pentium-II was mijn eerste PC). Maar ik heb inderdaad wel eens gehoord van het segmented memory model van de x86 (wat nog steeds bestaat, alleen kunnen de segmenten nu de hele address space bestrijken).
malloc is altijd aligned, en new char[] ook. Daarna wordt het lastig, maar dat is een probleem wat met de volgende standaard aangepakt wordt (alignof( ) o.i.d. )
Ik vraag me af wanneer en waarom een struct foo {int a,b;}; new foo; niet alligned zou zijn. Ik bedoel, new roept toch malloc aan? (iniedergeval in de meeste implementaties die ik bekeken heb)

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
MSalters schreef op 28 december 2003 @ 00:17:
LordLarry zit mis. Als je een object kopieert, dan worden sizeof(object) bytes gekopieerd.
Dat is natuurlijk afhankelijk van de copy-constructor van typeof(object) (kan typeof?). Er kan niks, minder, evenveel of meer gecopied worden.

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 31 december 2003 @ 12:43:
Als je ze in een 32bits register laadt. Maar wat gebeurt er met een 64bits register?
Ik geloof dat x86-64 de bovenste 32 bits ongewijzigd laat bij een 32 bits load. Als je ISA verandert, dan moet je opnieuw compileren. Dat geldt als je van x86 naar sparc gaat, en dat zou ook gelden als een x86-64 variant opeens extra bits gaat laden.
[waarom C++ pointers identiek aan CPU registers? ]

Om inline asm te kunnen mixen met c/c++ ?
Inline assembly is altijd compiler-specifiek. Als je in de compiler manual uitlegt wat de equivalente assembly is, dan kan dat ook gebruikt worden. In portable code is het geen issue, die bevat geen assembly.
Maar mischien dat ik er wel helemaal naast zit met mijn gedachten. Met gcc 3.1 op OS X 10.2.6 geeft het volgende aan dat int* en float* iniedergeval op een ppc met gcc wel degelijk gelijk zijn:

C++:
1
// reinterpret_cast< >s


Dit geeft de volgende output:

code:
1
// pointers identiek
OS X gebruikt een vrij simpele ISA, ja, en de binary interface is ook relatief eenvoudig. Ik ben benieuwd hoe dat gewijzigd wordt voor de G5 64-bit mode.
Hmmm, interesant :) (ik ben nooit veel met de 8086 bezig geweest, de pentium-II was mijn eerste PC). Maar ik heb inderdaad wel eens gehoord van het segmented memory model van de x86 (wat nog steeds bestaat, alleen kunnen de segmenten nu de hele address space bestrijken).
In theorie zou je inderdaad ook nu alle float pointers relatief ten opzichte van een ander segment register kunnen gebruiken. In dat geval zou een float* -> int* cast kunnen werken terwijle float* -> unsigned long -> int* niet werkt. Niet dat er een compiler is die dat doet; dat zou zinloze complexiteit zijn.
Ik vraag me af wanneer en waarom een struct foo {int a,b;}; new foo; niet alligned zou zijn. Ik bedoel, new roept toch malloc aan? (in ieder geval in de meeste implementaties die ik bekeken heb)
De pointer die "new foo" retourneert is aligned voor een foo, maar hoeft dat niet te zijn voor een double. new is namelijk een compiler keyword, en de compiler weet wat de alignment en grootte van foo is. Als de compiler om malloc()s te besparen een groot blok alloceert en dat onderverdeelt (gangbare techniek) dan is niet elke pointer die new retourneert gealigned zoals malloc dat doet.

Als malloc 16-byte alignment gebruikt is zo'n verdere onderverdeling best handig voor kleinere objecten. Kleinere objecten hebben namelijk altijd een kleine alignment. Een 2 byte object uitsluitend op 16-byte boundaries alloceren zou dan 7/8 vespilling zijn.
In zo'n geval zou ik als compiler schrijver een 2 byte size pool en 4/8/16 byte aligned pools maken. De eerste is voor alle objecten van 1 of 2 bytes, en daar sla ik geen grootte van op, dus 1 bit boekhouding; de andere pools zijn voor groottes < 1K, en daar kan ik met een byte grootte dus de boekhouding doen. Groter gaat recht naar malloc. Als je dus een new doet, dan bepaalt het type dus de alignment die je krijgt.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
Voor de zekerheid heb ik bovenstaande code nog met een andere compiler en 2 andere CPU architecturen geprobeerd, namelijk x86 en ultra sparc. Aan de code heb ik nog 2 types toegevoegd:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>

int main () {

    unsigned long x;
    int a = 10;
    struct foo {
        int a,b;
    };

    struct bar {
        char a;
    };

    std::cout << "int value before casts:" << a << std::endl << std::endl;
    
    int* b = &a;
    x = reinterpret_cast<unsigned long>(b);
    std::cout << "int pointer "<< b << std::endl;
    std::cout << "long view of int pointer " << x << std::endl;

    float* c = reinterpret_cast<float*>(b);
    x = reinterpret_cast<unsigned long>(c);
    std::cout << "float pointer " << c << std::endl;
    std::cout << "long view of float pointer "<< x << std::endl;

    foo* e = reinterpret_cast<foo*>(c);
    x = reinterpret_cast<unsigned long>(e);
    std::cout << "foo pointer " << e << std::endl;
    std::cout << "long view of foo pointer "<< x << std::endl;

    bar* f = reinterpret_cast<bar*>(e);
    x = reinterpret_cast<unsigned long>(f);
    std::cout << "bar pointer " << f << std::endl;
    std::cout << "long view of bar pointer "<< x << std::endl;
    
    int* d = reinterpret_cast<int*>(f);
    x = reinterpret_cast<unsigned long>(d);
    std::cout << "int pointer " << d << std::endl;
    std::cout << "long view of int pointer " << x << std::endl;

    std::cout << std::endl<< "int value after casts:" << *d << std::endl;
    

    return 0;
    
}


Het resultaat voor respectievelijk Linux RedHat 8 op x86, Solaris 8 op een ultra sparc, en NT 5.1 op een X86:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Linux rh8 - x86
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.96/specs
gcc version 2.96 20000731 (Red Hat Linux 7.3 2.96-113)

int value before casts:10

int pointer 0xbfffed50
long view of int pointer 3221220688
float pointer 0xbfffed50
long view of float pointer 3221220688
foo pointer 0xbfffed50
long view of foo pointer 3221220688
bar pointer 0xbfffed50
long view of bar pointer 3221220688
int pointer 0xbfffed50
long view of int pointer 3221220688

int value after casts:10

Solaris 8 - ultra sparc
Reading specs from /usr/local/lib/gcc-lib/sparc-sun-solaris2.8/2.95.3/specs
gcc version 2.95.3 20010315 (release)

int value before casts:10

int pointer 0xffbef850
long view of int pointer 4290705488
float pointer 0xffbef850
long view of float pointer 4290705488
foo pointer 0xffbef850
long view of foo pointer 4290705488
bar pointer 0xffbef850
long view of bar pointer 4290705488
int pointer 0xffbef850
long view of int pointer 4290705488

int value after casts:10

Windows NT 5 - x86 (msvc 7.0) - *debug*
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.00.9466 for 80x86
Copyright (C) Microsoft Corporation 1984-2001. All rights reserved.

int value before casts:10

int pointer 0012FED8
long view of int pointer 1244888
float pointer 0012FED8
long view of float pointer 1244888
foo pointer 0012FED8
long view of foo pointer 1244888
bar pointer 0012FED8
long view of bar pointer 1244888
int pointer 0012FED8
long view of int pointer 1244888

int value after casts:10

Windows NT 5 - x86 (msvc 7.0) - *release*
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.00.9466 for 80x86
Copyright (C) Microsoft Corporation 1984-2001. All rights reserved.

int value before casts:10

int pointer 0012FEE4
long view of int pointer 1244900
float pointer 0012FEE4
long view of float pointer 1244900
foo pointer 0012FEE4
long view of foo pointer 1244900
bar pointer 0012FEE4
long view of bar pointer 1244900
int pointer 0012FEE4
long view of int pointer 1244900

int value after casts:10


Het lijkt me dat de voorlopige conclusie is dat iniedergeval in de praktijk op de 4 meest gebruikte compilers/platformen de identity relatie geldt voor een cast van int* -> float* ->int*. We keken namelijk naar ppc, x86, en ultra sparc en de compilers cl.exe en gcc en op alle veranderden de pointer representaties niet gedurende de casts.

Zelfs het toevoegen van 2 willekeurige structs (waarbij 1 expres heel klein) in de cast chain veranderde hier nix aan. Natuurlijk kun je nog veel meer testen, long*, unsigned dingen, grote structs, arrays, enz... maar het ging boven eigenlijk voornamelijk om int* en float*.

[ Voor 31% gewijzigd door flowerp op 31-12-2003 15:45 ]

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
MSalters schreef op 31 december 2003 @ 14:19:
Ik geloof dat x86-64 de bovenste 32 bits ongewijzigd laat bij een 32 bits load. Als je ISA verandert, dan moet je opnieuw compileren. Dat geldt als je van x86 naar sparc gaat, en dat zou ook gelden als een x86-64 variant opeens extra bits gaat laden.
Ik geloof dat je wel gelijk hebt. Toch lijkt het mij dan een gok voor de compiler schrijvers om extra bits in een 64bit pointer voor eigen doeleinden te gebruiken. Het valt toch wel te voorzien dat ooit de meer bits gebruikt gaan worden. Hercompileren van code die overal al draait is nooit leuk.

Maar voorderest heb je natuurlijk absoluut gelijk. Een compiler kan achter elke pointer in het geheugen extra info zetten mbt tot die pointer. Bij een load krijgt de cpu alleen de eerste x aantal bits. Bij een 64bits pointer kunnen er mischien nog 64bits achter staan die type info of whatever geven. Iniedergeval wordt type info volgens de standaard bijgehouden in de vtable. Voor objecten die geen vtable hebben is het volgens de standaard gewoon pech. (niet echt netjes dus).

Als je voor een gegeven c++ implementatie een array van pointers aanmaakt, en dan byte voor byte door dat blok geheugen heenloopt kun je makkelijk zien of jouw implementatie dat doet of niet.
Als je in de compiler manual uitlegt wat de equivalente assembly is, dan kan dat ook gebruikt worden. In portable code is het geen issue, die bevat geen assembly.
Dat is natuurlijk waar. Een compiler manual zou dus kunnen zeggen:

Voor een pointer naar een type van categorie C1 moet je bits zus en zo weghalen voor je de echte pointer hebt, en voor categorie CX kun je de pointer zo gebruiken.

Ik ben dat echter nog nooit tegengekomen, maar ga er zeker eens naar op zoek. Heb je mischien een verwijzing naar een compiler manual waar zoiets als boven in staat?
OS X gebruikt een vrij simpele ISA, ja, en de binary interface is ook relatief eenvoudig. Ik ben benieuwd hoe dat gewijzigd wordt voor de G5 64-bit mode.
Je bedoelt natuurlijk de ppc heeft een vrij simpele ISA? Iniedergeval was de code die ik compilde voor een G3 gegenereerd. Naar de G5 ben ik ook wel benieuwd. Heb er nog weinig ervaring mee.
[pointers t.o.v. segment register hedendaags ... zinloze complexiteit zijn]
Naieve gedachte: waar het mischien wel voor gebruikt zou kunnen worden voor een x86 implementatie, is elk object of array een apart segment geven, om zo extra protection te geven. Ik weet niet of dat technisch mogelijk is, en of er wel genoeg segmenten kunnen bestaan om zoiets te realiseren.
De pointer die "new foo" retourneert is aligned voor een foo, maar hoeft dat niet te zijn voor een double.
Mee eens wat je zegt.
We hebben echter twee issues:

1) Een pointer naar type A die na een chain van reinterpret_casts weer teruggecast wordt naar A.

2) Het feit dat new (placement of niet) voor verschillende types verschillende pointers terug-geeft.

Van 1) ben ik nog steeds niet overtuigd waarom reinterpret_cast het bitpatroon van een pointer naar een basic type (of eventueel een simpele struct) zou veranderen na casten. (zelfs bij een reinterpret_cast van een derived naar zijn 2de base veranderd er bij de door mij geteste compilers niks).
Zoals we weten veranderd bij dynamic_cast natuurlijk wel de pointer bij het casten naar een 2de base. (de bekende base offset).

Van 2) ben ik zowieso overtuigd dat allignment erbij betrokken wordt (zelfs sizeof houdt hier rekening mee). Dat implementaties data achter pointers zetten en dat geheel als een type zien lijkt me ook heel goed mogelijk. Voorts kunnen er nog (op x86) segmenten bij betrokken worden. In theorie kan dit voor verschillende types verschillend gedaan worden.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
flowerp schreef op 31 december 2003 @ 17:03:
Ik geloof dat je wel gelijk hebt. Toch lijkt het mij dan een gok voor de compiler schrijvers om extra bits in een 64bit pointer voor eigen doeleinden te gebruiken. Het valt toch wel te voorzien dat ooit de meer bits gebruikt gaan worden. Hercompileren van code die overal al draait is nooit leuk.
Een 64-bit CPU die 32-bit code uitvoerd, die 32-bit pointers bevat, gaat niet ineens 64-bits laden voor een pointer. En voor 64-bit code moet toch gerecompiled worden.
Naieve gedachte: waar het mischien wel voor gebruikt zou kunnen worden voor een x86 implementatie, is elk object of array een apart segment geven, om zo extra protection te geven. Ik weet niet of dat technisch mogelijk is, en of er wel genoeg segmenten kunnen bestaan om zoiets te realiseren.
Nee, daar bestaan niet genoeg segmenten voor. Bovendien is het laden van segment selectors/descriptors niet 'goedkoop'.

[ Voor 29% gewijzigd door Olaf van der Spek op 31-12-2003 18:17 ]


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
Hoi Olaf,

Wat leuk een reactie van jou :) (was jij niet diegene die dacht dat Visual C++ 6.0 de C++98 standaard voor bijna 100% implementeerd? Op wat kleine dingetjes met templates na he? :+ )
OlafvdSpek schreef op 31 december 2003 @ 18:15:
Een 64-bit CPU die 32-bit code uitvoerd, die 32-bit pointers bevat, gaat niet ineens 64-bits laden voor een pointer. En voor 64-bit code moet toch gerecompiled worden.
Maar een 64-bit cpu die 64-bit code uitvoerd, die 64-bits pointers bevat, die momenteel niet helemaal gebruikt worden, kan zeer zeker wel 'opeens' 64-bits laden voor een pointer.
Nee, daar bestaan niet genoeg segmenten voor. Bovendien is het laden van segment selectors/descriptors niet 'goedkoop'.
Ik meen me te herinneren dat in A. Silberschatz, P.B. Galvin en G. Gagne, Operating system concepts, ook iets stond in deze richting. Als je dit boek niet kent raad ik je aan het eens te lezen. Ik moet het zelf weer eens overniew lezen want voor mij is het al jaren geleden.

[ Voor 4% gewijzigd door flowerp op 31-12-2003 20:58 ]

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
Op de blog van Herb Sutter (http://blogs.gotdotnet.com/hsutter/) kwam ik net een interesant stukje code tegen dat de bewering van .oisyn tegen lijkt te spreken dat het bitpatroon van een pointer zou veranderen bij een c-style of reinterpret cast:
[...]
Consider the following well-formed ISO C++ program
with well-defined semantics:
C++:
1
2
3
4
5
6
7
8
int* pi = new int(42); // line 1
pi = (int*)((int)pi ^ 0xaaaaaaaa);

// ... do other work ...      

pi = (int*)((int)pi ^ 0xaaaaaaaa);
cout << *pi; // perfectly ok, prints "42",         won't crash
delete pi; // ok 

[...]
Volgens Sutter is dit dus well-formed en standaard gedrag. Het is niet helemaal de case waar .oisyn op commente, maar wel erg close. (.oisyn stelde ongeveer dat het bit patroon zou veranderen bij een (reinterpret) cast van bv een int* naar een float*)

Is dit nou een tegenvoorbeeld? Of denken jullie dat naar int casten de uitzondering is? (omdat in C++ int per definitie het 'system word' is)

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
flowerp schreef op 31 december 2003 @ 20:54:
Hoi Olaf,

Wat leuk een reactie van jou :) (was jij niet diegene die dacht dat Visual C++ 6.0 de C++98 standaard voor bijna 100% implementeerd? Op wat kleine dingetjes met templates na he? :+ )
Ja, ik vroeg wat VC6 miste behalve templates.
Maar een 64-bit cpu die 64-bit code uitvoerd, die 64-bits pointers bevat, die momenteel niet helemaal gebruikt worden, kan zeer zeker wel 'opeens' 64-bits laden voor een pointer.
Natuurlijk, maar de documentatie van de architectuur zegt vast wel iets over die bits.
Is het trouwens zo dat IA64 of x86-64 geen 64-bit virtual addressing gebruiken?

Ik meen me te herinneren dat in A. Silberschatz, P.B. Galvin en G. Gagne, Operating system concepts, ook iets stond in deze richting. Als je dit boek niet kent raad ik je aan het eens te lezen. Ik moet het zelf weer eens overniew lezen want voor mij is het al jaren geleden.[/quote]
Ik heb het boek, heb het ook gelezen, maar kan zo echt even niet voor de dag halen waar je precies op doelt.

  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
OlafvdSpek schreef op 01 januari 2004 @ 14:24:
Ja, ik vroeg wat VC6 miste behalve templates.
:) Je zou er bijna intrappen als leek zijnde ;)
Natuurlijk, maar de documentatie van de architectuur zegt vast wel iets over die bits.
Is het trouwens zo dat IA64 of x86-64 geen 64-bit virtual addressing gebruiken?
Het korte antwoord: nee. De AMDs gebruiken 40bit, de G5 42bit, IA64 weet ik niet.

Lange antwoord: zie google. voor G5 bv http://www.apple.com/g5processor/architecture.html
[A. Silberschatz, P.B. Galvin en G. Gagne, Operating system concepts]
Ik heb het boek, heb het ook gelezen, maar kan zo echt even niet voor de dag halen waar je precies op doelt.
Ok, uit mijn hoofd, volgens mij was het het stuk over paging. Op een gegeven moment wordt er de opmerking gemaakt dat pages technisch een bijzonder geval van segments zijn (namelijk fixed size). Daarna komt er de opmerking dat segments precies op data-structuren kunnen worden gezet om zo die van protection te voorzien.

Pin me er echter niet op vast :) Dit is uit mijn hoofd van iets wat ik 5 jaar terug (ofzo) gelezen heb. Ik zal het binnenkort eens opzoeken.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 31 december 2003 @ 17:03:
Mee eens wat je zegt.
We hebben echter twee issues:

1) Een pointer naar type A die na een chain van reinterpret_casts weer teruggecast wordt naar A.
Werkt, indien de tussenliggende type geschikt zijn. Als je via een char cast kun je veilig gokken dat het fout kan gaan. Voor void* als tussenliggend type is het zelfs gegarandeerd.
2) Het feit dat new (placement of niet) voor verschillende types verschillende pointers terug-geeft.
...
Van 2) ben ik zowieso overtuigd dat allignment erbij betrokken wordt (zelfs sizeof houdt hier rekening mee).
Ik weet vrij zeker dat er compilers zijn die bij placement new blindelings aannemen dat het geheugen al aligned is. Placement new is op die compilers namelijk niet meer dan een constructor call, waarbij this al gezet is.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
MSalters schreef op 01 januari 2004 @ 17:12:
Werkt, indien de tussenliggende type geschikt zijn. Als je via een char cast kun je veilig gokken dat het fout kan gaan. Voor void* als tussenliggend type is het zelfs gegarandeerd.
Kun je misschien een voorbeeld geven waarin het wel fout gaat?

  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
flowerp schreef op 01 januari 2004 @ 16:58:
Het korte antwoord: nee. De AMDs gebruiken 40bit, de G5 42bit, IA64 weet ik niet.

Lange antwoord: zie google. voor G5 bv http://www.apple.com/g5processor/architecture.html
Volgens AMD is het 48-bit.
AMD Athlon™ 64 FX Processor Data Sheet

64-bit integer registers, 48-bit virtual addresses, 40-bit physical addresses
Wat je met die andere bits mag doen weet ik niet. Volgens mij is dit alleen een limiet van de hardware en niet van de x86-64 instructieset zelf.

[ Voor 9% gewijzigd door Olaf van der Spek op 01-01-2004 20:55 ]


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
Maar dus wel 40-bit physical.
Wat je met die andere bits mag doen weet ik niet.
Volgens MSalters kun je hier het beste rare bits in stoppen zodat je code crasht als er toch meer bits gebruikt gaan worden zodat je weer lekker kunt her-compileren. :+

Nee, serieus...

MSalters denkt dat compiler schrijvers dit waarschijnlijk kunnen doen (extra bits voor prive doel-einden gebruiken), en dat je daarom dus je 64bit pointer niet als een 64bit adres kunt zien. Hoewel ik normaal eigenlijk niet aan de authoriteit van MSalters twijfel, vind ik het toch moeilijk om voor te stellen dat compiler schrijvers dit risico nemen. Zelf weet ik er te weinig van af om dit goed te kunnen beoordelen.
Volgens mij is dit alleen een limiet van de hardware en niet van de x86-64 instructieset zelf.
Op dit moment is er natuurlijk maar 1 implementatie van de x86-64 ISA. Maar het lijkt me ook geen limiet van de ISA. M.a.w. elke volgende implementatie ervan zou meer bits kunnen gaan gebruiken, wat me weer doet twijfelen aan de uitspraken van MSalters. Toch... MSalters is geen domme jongen zoals we allemaal hier weten, dus ik vind dit erg verwarrend...

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
flowerp schreef op 01 januari 2004 @ 22:41:
Maar dus wel 40-bit physical.
Maar dat vroeg ik niet. En het is voor applicatie code ook niet relevant.

  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
OlafvdSpek schreef op 01 januari 2004 @ 22:47:
[...]

Maar dat vroeg ik niet. En het is voor applicatie code ook niet relevant.
Het was ook geen antwoord, want de info was al boven tafel. Het was slechts een hint dat mijn uitingen niet helemaal nergens op sloegen. 8)

Overigens maakt het verschil physical/virtual nu toch nog niks uit, aangezien de *daadwerkelijke* physical limiet nu eerder op 33bits ligt geloof ik (slechts 1 bitje meer dus, voor een totaal van 8gig in een machine). De bruikbare virtual limiet ligt voorlopig ook nog op 31bit/32bit, totdat er bruikbare versies van 64bit operating systemen komen voor AMD's x86-64. Overigens is de situatie voor de G5 al niet veel beter voor het meest gebruikte OS op die CPU. Daar ligt de limiet per process nog steeds op 32bit, terwijl voor het OS 33bit geldt. wow!

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
flowerp schreef op 01 januari 2004 @ 23:05:
Overigens maakt het verschil physical/virtual nu toch nog niks uit, aangezien de *daadwerkelijke* physical limiet nu eerder op 33bits ligt geloof ik (slechts 1 bitje meer dus, voor een totaal van 8gig in een machine). De bruikbare virtual limiet ligt voorlopig ook nog op 31bit/32bit, totdat er bruikbare versies van 64bit operating systemen komen voor AMD's x86-64. Overigens is de situatie voor de G5 al niet veel beter voor het meest gebruikte OS op die CPU. Daar ligt de limiet per process nog steeds op 32bit, terwijl voor het OS 33bit geldt. wow!
Is er geen bruikbare 64-bit versie van Linux van Suse dan?

Die een of twee bitjes maken trouwens wel een redelijk groot verschil. Ik mocht wensen dat mijn RAM twee bit extra nodig had om geadresseerd te kunnen worden. ;->

[ Voor 11% gewijzigd door Olaf van der Spek op 02-01-2004 00:27 ]


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

flowerp schreef op 01 januari 2004 @ 13:51:
Op de blog van Herb Sutter (http://blogs.gotdotnet.com/hsutter/) kwam ik net een interesant stukje code tegen dat de bewering van .oisyn tegen lijkt te spreken dat het bitpatroon van een pointer zou veranderen bij een c-style of reinterpret cast:
uhm pardon? kun je het precieze stuk aanwijzen waaruit blijkt dat ik dat heb gezegd?

Het enige wat ik beweerde is dat een B * -> void * -> A * serie van casts foute code is, niet dat die A * dan ineens een ander adres krijgt dan waar B * eigenlijk naar wees. Iets waar je overigens verder niet meer op bent ingegaan. Overigens zijn dit static_casts, geen reinterpret_casts. Over reinterpret_cast heb ik het al helemaal niet gehad

[ Voor 8% gewijzigd door .oisyn op 02-01-2004 15:46 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
OlafvdSpek schreef op 01 januari 2004 @ 19:47:
[...]

Kun je misschien een voorbeeld geven waarin het wel fout gaat?
int*->char->int* op alle gangbare desktop architecturen.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 01 januari 2004 @ 22:41:
[...]


Maar dus wel 40-bit physical.


[...]


Volgens MSalters kun je hier het beste rare bits in stoppen zodat je code crasht als er toch meer bits gebruikt gaan worden zodat je weer lekker kunt her-compileren. :+
Ik geloof dat er hier fysieke en virtuele adressen door elkaar worden gehaald. In de huidige Pentium Pro ISA is de fysieke adresruimte al 36 bits, maar per proces kun je maximaal 4Gb/32 bits virtueel geheugen begruiken. Op Windows kun je met AWE meer dan 4Gb gebruiken, maar niet tegelijk.
Nee, serieus...

MSalters denkt dat compiler schrijvers dit waarschijnlijk kunnen doen (extra bits voor prive doel-einden gebruiken), en dat je daarom dus je 64bit pointer niet als een 64bit adres kunt zien. Hoewel ik normaal eigenlijk niet aan de authoriteit van MSalters twijfel, vind ik het toch moeilijk om voor te stellen dat compiler schrijvers dit risico nemen. Zelf weet ik er te weinig van af om dit goed te kunnen beoordelen.
* MSalters hoopt over nietal te lange tijd officieel compiler schrijver te zijn :)
Het lijkt een gok, 20 van de extra 32 bits kapen voor pointer misbruik. Ik denk dat dit geen groot risico is, omdat (1) het in het virtuele geheugengebied is, (2) het nog steeds 44 bits = 16384Gb virtueel geheugen per proces overlaat, en (3) de extra bits bij de dereference eraf gestript worden (HW of SW, hangt van proc af).
Nogmaals, ik weet niet of het economisch rendabel is om zoiets in je compiler toe te voegen, maar ik wijs er alleen op dat het volgens de standaard en met de huidige HW kan.
[...]
Op dit moment is er natuurlijk maar 1 implementatie van de x86-64 ISA. Maar het lijkt me ook geen limiet van de ISA. M.a.w. elke volgende implementatie ervan zou meer bits kunnen gaan gebruiken, wat me weer doet twijfelen aan de uitspraken van MSalters. Toch... MSalters is geen domme jongen zoals we allemaal hier weten, dus ik vind dit erg verwarrend...
Je hebt niet zo gek veel mogelijkheden om een ISA zomaar te veranderen. Een instructie die gedocumenteerd is als een 32 bits load wordt niet opeens een 64 bits load, al was het alleen maar omdat je dan bij page grenzen een page fault zou krijgen als die extra 32 bits buiten je virtueel geheugen vallen.

Bovendien is de mapping van virtual naar fysiek geheugen geen eigenschap van de ISA, maar van het OS (eventueel in combinatie met het programma, Windows zet(te) die mapping verschillend op afhankelijk van het programma type [DOS/Win16/Win32/Win64]. Sowieso heeft elk programma zijn eigen mapping. Om te voorkomen dat ze in het fysieke geheugen van een ander proces schrijven wordt dat deel van het fysieke geheugen niet gemapt. Als je dus een 2^44 bytes segment opzet, dan krijg je een AV als je vergeet de extra bits te strippen. Dat is een recoverable exception in de x86 architectuur, dus het OS kan dat desnoods voor je doen (of je eigen exception handler)

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
MSalters schreef op 02 januari 2004 @ 17:20:
int*->char->int* op alle gangbare desktop architecturen.
Met een reinterpret_cast kun je toch niet van int* naar char casten?
Ik dacht dat je bedoelde dat het tijdens run-time fout zijn gaan, net tijdens compile-time.
Of ligt dit aan mijn rotcompiler?

  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
MSalters schreef op 02 januari 2004 @ 17:38:
Ik geloof dat er hier fysieke en virtuele adressen door elkaar worden gehaald. In de huidige Pentium Pro ISA is de fysieke adresruimte al 36 bits, maar per proces kun je maximaal 4Gb/32 bits virtueel geheugen begruiken. Op Windows kun je met AWE meer dan 4Gb gebruiken, maar niet tegelijk.
Zelfs dat is niet 100% correct (toch?). De ISA stelt 4+ segment registers ter beschikking, dus dat zou toch minimaal 4 * 4 gb zijn?
MSalters schreef op 02 januari 2004 @ 17:38:
* MSalters hoopt over nietal te lange tijd officieel compiler schrijver te zijn :)
Het lijkt een gok, 20 van de extra 32 bits kapen voor pointer misbruik. Ik denk dat dit geen groot risico is, omdat (1) het in het virtuele geheugengebied is, (2) het nog steeds 44 bits = 16384Gb virtueel geheugen per proces overlaat, en (3) de extra bits bij de dereference eraf gestript worden (HW of SW, hangt van proc af).
Nogmaals, ik weet niet of het economisch rendabel is om zoiets in je compiler toe te voegen, maar ik wijs er alleen op dat het volgens de standaard en met de huidige HW kan.
Dat hangt wel van het OS af natuurlijk. Als je van het OS een pointer terugkrijgt boven die 16 tb en jij gaat er bits vanaf strippen, dan ....

[ Voor 45% gewijzigd door Olaf van der Spek op 02-01-2004 21:05 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
OlafvdSpek schreef op 02 januari 2004 @ 21:01:
[...]
Zelfs dat is niet 100% correct (toch?). De ISA stelt 4+ segment registers ter beschikking, dus dat zou toch minimaal 4 * 4 gb zijn?
Subtiel. Of dat zou werken hangt af van de implementatie van die segment registers. Ik ga even zoeken of die in virtual danwel fysiek geheugen segmenten definieren.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
MSalters schreef op 02 januari 2004 @ 22:04:
Subtiel. Of dat zou werken hangt af van de implementatie van die segment registers. Ik ga even zoeken of die in virtual danwel fysiek geheugen segmenten definieren.
Virtual. Anders zou paging nauwelijks werken.

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Yep, virtual. Evengoed is beide in theorie mogelijk; als je aliasing gebruikt dan weet het OS dat, en die is ook verantwoordelijk voor paging. Paging is in feite alleen maar een exception handler voor Page Faults.

Volgens de IA32 Software Development Manual zit je toch vast aan 4Gb per thread, ook al heb je 6 segment registers. (Volume 1, para 3.2 Address Space. ). Zelfs als je 48 bits far pointers gebruikt (ook pas net ontdekt dat die dingen nog steeds bestaan) zijn de bovenste 16 bits alleen maar segment selectors. Het segment register samen met de 32 bits base vormt een 32 bits lineair adres. Helaas, dus geen 6x4 Gb geheugen. Het probleem op hardware nivo is dat je de cache opnieuw moet initialiseren (de TLBs, om precies te zijn) om het huidige stuk van 4Gb lineair te selecteren.

De segmenten kun je overigens ook gebruiken om C++ const-ness te enforcen. Dat zou als consequentie kunnen hebben dat zelfs een reinterpret_cast de bipatterns verandert. Als je bijvoorbeeld stelt dat de 33e bit van zo'n 48 bits pointer aangeeft of een pointer const is (dwz segment descriptors zijn pairwise aliased, R/O en R/W) en je cast een __int64, dan mag een compiler dat bit veranderen om const-ness af te dwingen.

De situatie wordt nog complexer met PAE of PSE, Intel features om 64Gb te gebruiken.
Het wordt erg lastig om daar diep op in te gaan, omdat er drie verschillende definities van pointers zijn (CPU, OS en C++). Kort gezegd is een deel van het lineair adres (na segment registers) een entry in de page table, en de page table was een mapping tussen 4Gb lineair address space en 4Gb fysiek geheugen. Met PAE/PSE worden die tables anders door het OS opgezet, zodat je een 4Gb lineair adres selectie hebt uit 64Gb fysiek geheugen.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
MSalters schreef op 29 december 2003 @ 20:48:
Het gebruik van een pointer heet toch echt by-reference.
Ik had deze eerder over het hoofd gezien, maar ik ben het hier niet helemaal (of helemaal niet :) ) mee eens.

Een pointer doorgeven is wel degelijk call-by-value. Als er sprake is van een pointer variable (een lvalue) dan is die pointer zelf voor het passing mechanisme niet meer of minder dan een aantal bits (waarbij we even in het midden laten of die wel of niet direct met een address corresponderen). In geval van deze pointer variable staan deze bits ergens in het geheugen, namelijk precies op het address van de variable.

By call-by-value worden de bits (een copy) waaruit de pointer bestaat doorgegeven aan de functie.

By call-by-reference wordt het address waar de bits staan waaruit de pointer bestaat doorgegeven aan de functie.

Bv:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void takePtr(int* prt) {
 *ptr = 6; // works 
  ptr = new int(7); // ain't gonna work
}

void takePtrRef(int* &ptr) {
  *ptr = 6; // works 
  ptr = new int(7); // also works
}

int main() {
  int* intPtr = new int(5);
  
  takePtr( intPtr ); // pointer passed call-by-value
  takePtrRef ( intPtr ); // pointer passed call-by-reference

  return 0;
}


Ik weet dat de terminology vaak door elkaar wordt gehaald. Bv Sun gebruikt ook consequent de term reference semantics voor Java, terwijl Java juist alleen call-by-value kent (voor remote objecten ook nog call-by-deep-copy). Bij Sun is het wel te verklaren omdat ze net willen doen alsof Java geen pointers kent, terwijl het juist alleen pointers zijn voor objecten in Java.

Als je pointer geen lvalue is kun je zelfs niet eens call-by-reference gebruiken. Bv in bovenstaande main():

C++:
1
2
int a = 4;
takePtrRef ( &a ); // doesn't work, no lvalue


Het is wel oppassen met temporary objects. Tegenwoordig mogen die alleen een const reference initialiseren, maar sommige compilers (zelfs redelijke recente) negeren die regel nog. Met gcc 3.1 compileert het volgende bv:

C++:
1
2
3
4
5
takePtrRef ( new int(5) ); // surprizing?

//also works with gcc 3.1
int** odd = &(new int(5));
std::cout << **odd << std::endl; 
De pointer zelf wordt niet als een pointer doorgegeven.
Maar dan is het toch de value, en zijn we het toch gewoon eens?
De passTest* pointer is zelf niet het relevante object, het relevante object is wat die pointer referenced.
In het geval van een pointer variable is de pointer zelf wel zeer degelijk het relevante object. Voor de compiler is het gewoon net zoals een integer die by-value op de call stack wordt gezet. ( op eventuele magic prive bits na dan mischien)

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
OlafvdSpek schreef op 02 januari 2004 @ 21:00:
[...]

Met een reinterpret_cast kun je toch niet van int* naar char casten?
Ik dacht dat je bedoelde dat het tijdens run-time fout zijn gaan, net tijdens compile-time.
Of ligt dit aan mijn rotcompiler?
Je verliest iniedergeval precisie. Volgens sommige compilers (MS cl.exe, aka MSVC) is dat een error, volgens anderen (gcc) is het een warning.

Andere voorbeelden die niet compilen zijn een pointer naar float of double willen casten. Dat pakt (volgens mij) ook geen enkele compiler.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
[b]MSalters schreef op 02 januari 2004 @ 17:38

* MSalters hoopt over nietal te lange tijd officieel compiler schrijver te zijn :)
Gaaf. Mee helpen aan een bestaande compiler of een geheel nieuwe?
Je hebt niet zo gek veel mogelijkheden om een ISA zomaar te veranderen. Een instructie die gedocumenteerd is als een 32 bits load wordt niet opeens een 64 bits load, al was het alleen maar omdat je dan bij page grenzen een page fault zou krijgen als die extra 32 bits buiten je virtueel geheugen vallen.
Maar dan staat er toch gewoon in de (huidige) spec dat je de bovenste 32bits 0 moeten laten zijn? Zoiets heb ik veel vaker gezien. Als iedereen zich daaraan houdt, dan kan de load in de toekomst aangepast worden, zonder dat bestaande code her-compiled hoeft te worden.
Bovendien is de mapping van virtual naar fysiek geheugen geen eigenschap van de ISA,maar van het OS
Klopt, hoewel de CPU natuurlijk wel de resultaten cached in de tlb.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 12-05 22:23

curry684

left part of the evil twins

flowerp schreef op 03 januari 2004 @ 12:41:
[...]
Je verliest iniedergeval precisie. Volgens sommige compilers (MS cl.exe, aka MSVC) is dat een error, volgens anderen (gcc) is het een warning.
Ik krijg in VC7 anders geen error noch warning hierop hoor:
C++:
1
2
3
    int     Int     = 684;
    int*    IntPtr  = 
    char    Char    = reinterpret_cast<char>(IntPtr);

Zie hieronder de reden :)
Andere voorbeelden die niet compilen zijn een pointer naar float of double willen casten. Dat pakt (volgens mij) ook geen enkele compiler.
The reinterpret_cast operator allows any pointer to be converted into any other pointer type. It also allows any integral type to be converted into any pointer type and vice versa.

Laatste keer dat ik checkte was een float of double geen integral type, en een char wel :Y)

[ Voor 7% gewijzigd door curry684 op 03-01-2004 13:02 ]

Professionele website nodig?


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
.oisyn schreef op 02 januari 2004 @ 15:45:

Het enige wat ik beweerde is dat een B * -> void * -> A * serie van casts foute code is,
Ah, sorry! Dan had ik het verkeerd gelezen en verkeerd begrepen.
niet dat die A * dan ineens een ander adres krijgt dan waar B * eigenlijk naar wees.
Ik dacht echt dat je dat toch wel bedoelde, zeker na de opmerkingen over segment registers waarbij bv float via een ander segment zou gaan als int.

Ik heb iniedergeval toch even getest wat B* -> void* A* -> void* B* in de praktijk doet mbv de volgend code:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>


unsigned long x;
int a = 10;
struct foo {
    int a,b;
    char x[909];
};

struct bar {
    char a;
};

template <typename T, typename U>
T staticvoid_cast(U sourceType) {
    void* x = static_cast<void*>(sourceType);
    return static_cast<T>(x);
}

int main () { 

std::cout << "int value before casts:" << a << std::endl << std::endl;

    int* b = &a;
    x = reinterpret_cast<unsigned long>(b);
    std::cout << "int pointer "<< b << std::endl;
    std::cout << "long view of int pointer " << x << std::endl;

    float* c = staticvoid_cast<float*>(b);
    x = reinterpret_cast<unsigned long>(c);
    std::cout << "float pointer " << c << std::endl;
    std::cout << "long view of float pointer "<< x << std::endl;

    foo* e = staticvoid_cast<foo*>(c);
    x = reinterpret_cast<unsigned long>(e);
    std::cout << "foo pointer " << e << std::endl;
    std::cout << "long view of foo pointer "<< x << std::endl;

    bar* f = staticvoid_cast<bar*>(e);
    x = reinterpret_cast<unsigned long>(f);
    std::cout << "bar pointer " << f << std::endl;
    std::cout << "long view of bar pointer "<< x << std::endl;

    long* g = staticvoid_cast<long*>(f);
    x = reinterpret_cast<unsigned long>(g);
    std::cout << "long pointer " << g << std::endl;
    std::cout << "long view of long pointer "<< x << std::endl;

    unsigned long* h = staticvoid_cast<unsigned long*>(g);
    x = reinterpret_cast<unsigned long>(h);
    std::cout << "ulong pointer " << h << std::endl;
    std::cout << "long view of ulong pointer "<< x << std::endl;

    int* d = staticvoid_cast<int*>(f);
    x = reinterpret_cast<unsigned long>(d);
    std::cout << "int pointer " << d << std::endl;
    std::cout << "long view of int pointer " << x << std::endl;

    std::cout << std::endl<< "int value after casts:" << *d << std::endl;


    return 0;

}


Op 2 compilers (gcc en cl.exe) en 2 cpus (ppc en x86) geeft dit dezelfde uitkomst als bij de reinterpret_cast code die ik al eerder gaf.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
MSalters schreef op 03 januari 2004 @ 00:37:
Yep, virtual. Evengoed is beide in theorie mogelijk; als je aliasing gebruikt dan weet het OS dat, en die is ook verantwoordelijk voor paging. Paging is in feite alleen maar een exception handler voor Page Faults.
Maar dan heb je per-process local paging en niet global paging.
Volgens de IA32 Software Development Manual zit je toch vast aan 4Gb per thread, ook al heb je 6 segment registers. (Volume 1, para 3.2 Address Space. ). Zelfs als je 48 bits far pointers gebruikt (ook pas net ontdekt dat die dingen nog steeds bestaan) zijn de bovenste 16 bits alleen maar segment selectors. Het segment register samen met de 32 bits base vormt een 32 bits lineair adres. Helaas, dus geen 6x4 Gb geheugen. Het probleem op hardware nivo is dat je de cache opnieuw moet initialiseren (de TLBs, om precies te zijn) om het huidige stuk van 4Gb lineair te selecteren.
Sectie 3.3.3: Extended Physical Addressing zegt:
A program can switch between linear address spaces within this 64-gbyte physical address space by changing segment selectors in the segment registers.
Veranderingen in de page tables (en dus TLB flushes) zijn blijkbaar niet nodig en met meerdere segment registers kun je IMO dus meerdere 4 gb ranges benaderen.

  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
curry684 schreef op 03 januari 2004 @ 13:00:

Ik krijg in VC7 anders geen error noch warning hierop hoor:
C++:
1
2
3
    int     Int     = 684;
    int*    IntPtr  = &Int;
    char    Char    = reinterpret_cast<char>(IntPtr);

Zie hieronder de reden :)
Bij mij (met de compiler van VC7.0, aka VS 2002 .NET)

code:
1
2
error C2440: 'reinterpret_cast' : cannot convert from 'int *' to 'char'
        The target is not large enough
Laatste keer dat ik checkte was een float of double geen integral type, en een char wel :Y)
Het was laat gisteravond :)

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 03 januari 2004 @ 12:50:
[...]
Gaaf. Mee helpen aan een bestaande compiler of een geheel nieuwe?
De nieuwe MSVC++ , als het gesprek goed gaat :)
[ ISA verandering]

Maar dan staat er toch gewoon in de (huidige) spec dat je de bovenste 32bits 0 moeten laten zijn? Zoiets heb ik veel vaker gezien. Als iedereen zich daaraan houdt, dan kan de load in de toekomst aangepast worden, zonder dat bestaande code her-compiled hoeft te worden.
De huidige x86 ISA laadt gewoon 32 bits pointers zonder aan omliggend geheugen te zitten. In een array van pointers zitten de pointers naast elkaar. Er is geen enkele mogelijkheid waarop de ISA veranderd kan worden naar 64 bits pointers, terwijl de huidige code ongewijzigd blijft draaien. Wat wel kan zijn extensies, die gebruik maken van code sequences die in de huidige code niet voorkomen.

Er is een andere mogelijkheid, en dat zijn de eerder aangehaalde far pointers met segment registers. Je zou daar de 4Gb limiet weg kunnen halen. Dat heeft consequenties voor het Operating System, die de page tables moet beheren. Nu zitten daar nog niet genoeg bits in, maar je kunt dat wijzigen zonder dat een programma het door heeft. Er is dan een tweede OS-level API nodig, om die 48 bits pointers te gebruiken, maar bestaande code blijft 100% bruikbaar omdat het gewoon een enkel segment blijft gebruiken.

Dit werkt, omdat de x86 architectuur nog steeds de segment registers heeft, en de bijbehorende instructies.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
OlafvdSpek schreef op 03 januari 2004 @ 13:31:
[...]
Sectie 3.3.3: Extended Physical Addressing zegt:

[A program can switch between linear address spaces within this 64-gbyte physical address space by changing segment selectors in the segment registers.]

Veranderingen in de page tables (en dus TLB flushes) zijn blijkbaar niet nodig en met meerdere segment registers kun je IMO dus meerdere 4 gb ranges benaderen.
Voor alle duidelijkheid, 3.3.3 in volume 1. Volume 3 heeft geen 3.3.3, maar 3.3 daarin gaat ook over adressen. Dat bevat toevallig wel pointers :) naar 3.8 Page Page Address Extension en 3.9 Page Size Extension (beide ook in volume 3)

PAE is de eerste implementatie waarmee 32 bits pointers en 36-bits fysiek geheugen gemengd worden. De eerste vertaling blijft de virtual->lineair address mapping die 32->32 is. Hierbij worden de 6 segment registers gebruikt. Pas daarna komt de lineair->physical mapping. Hierbij wordt maar één register gebruikt om de page tables te vinden, en dat is CR3/PDPTR (Page Directory PoinTeR). Dit bevat een fysiek adres (en is dus zelf niet gemapt op enigerlei wijze)

PSE is de opvolger van PAE, wat de Pentium Pro methode was om 64Gb te adresseren. Daarin werd de page table data structuur erg complex; PSE is weer wat normaler. Daarin wordt een 32 bits lineair address (uit de combinatie segment register-virtueel adres) gesplitst in een 10 bits page table selector en een 22 bits page offset (met dus 1024 * 4Mb pages, 4Gb ). De 10 bits van de page table selector leveren een 14 bits fysiek adres op, wat met 22 nullen het page base address geeft. Op die manier wordt een fysieke range van 36 bits bereikt.

Omdat je in deze opzet altijd vanaf een 32 bits lineair address werkt, is het nooit zo dat je meer dan 4Gb kunt aanspreken. Anders gezegd, volume 3 is expliciet: De lineair address space blijft 32 bits; PAE en PSE veranderen alleen de structuur van de page tables entries (PTEs, zie 3.6.2) en niet de segment descriptors (zie 3.5.1)

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 12-05 22:23

curry684

left part of the evil twins

flowerp schreef op 03 januari 2004 @ 13:49:
[...]
Bij mij (met de compiler van VC7.0, aka VS 2002 .NET)

code:
1
2
error C2440: 'reinterpret_cast' : cannot convert from 'int *' to 'char'
        The target is not large enough
Wazig... ik heb net nog eens dubbel gechecked met mijn 7.0, maar ik krijg zelfs met /W4 geeneens een warning :?

Professionele website nodig?


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
curry684 schreef op 04 januari 2004 @ 00:02:
[...]

Wazig... ik heb net nog eens dubbel gechecked met mijn 7.0, maar ik krijg zelfs met /W4 geeneens een warning :?
Inderdaad. Heb je language extensions aanstaan? Wat is je compile target (CLI of x86)?

Als je in 7.0 een C++ console app begint staan language extentions default aan en is de compile target CLI. Deze moet je altijd eerst even met de hand terug zetten om iets 'zinvols'* te kunnen doen.

(ik neem aan dat ze dat in 7.1 wel gefixed hebben, want is heel irritant om telkens te moeten doen)

*- zinvols mbt standaard C++ werk

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
MSalters schreef op 03 januari 2004 @ 22:38:
[...]

De nieuwe MSVC++ , als het gesprek goed gaat :)
Wow... who wants some :-) Dat wordt dan wel verhuizen naar Amerika lijkt mij. Ik geloof niet dat MS development buiten de USA doet.

Ik geloof dat het VC team nu heel druk bezig is met de ECMA CLI/C++ binding en de nieuwe extension keywords (ref class, gcnew, de nieuwe 'pointer' ^ en address of % enz). Daar val je dan mischien midden in.

Ik weet iniedergeval wel dat mocht het voor je doorgaan, ik heel goed ga kijken of er geen rare fratsen met pointers zijn uitgehaald als ik nieuwe MSVC in de toekomst gekocht heb. Zoja, dan weet ik dan wie er waarschijnlijk verandwoordelijk voor was! 8)
De huidige x86 ISA laadt gewoon 32 bits pointers zonder aan omliggend geheugen te zitten. In een array van pointers zitten de pointers naast elkaar. Er is geen enkele mogelijkheid waarop de ISA veranderd kan worden naar 64 bits pointers, terwijl de huidige code ongewijzigd blijft draaien.
Tuurlijk, maar dat weet iedereen. Het punt waar het om gaat is x86-64. Die zou 64bits pointers gebruiken terwijl er maar 48bits van in gebruik zijn. Ik weet niet wat de implementatie gaat doen, 48bits pointers aanmaken? Kan wel, zijn precies 6 bytes en dan valt er weinig te discuseren meer. Maar... het zou ook kunnen dat de implementatie meteen 64bits pointers gebruikt en de bovenste 18 reserveert (dwz, moeten 0 zijn).

Er zit al veel beta 64bit code in VC7.0 (en nog meer in 7.1 lijkt me), maar ik heb daar nog niet zoveel naar gekeken. Uberhaupt heeft dat weinig zin, omdat die natuurlijk speciaal voor IA64 (itanium) zijn waar (bijna) niemand wat aan heeft.

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
flowerp schreef op 04 januari 2004 @ 10:41:
[...]


Wow... who wants some :-) Dat wordt dan wel verhuizen naar Amerika lijkt mij. Ik geloof niet dat MS development buiten de USA doet.

Ik geloof dat het VC team nu heel druk bezig is met de ECMA CLI/C++ binding en de nieuwe extension keywords (ref class, gcnew, de nieuwe 'pointer' ^ en address of % enz). Daar val je dan mischien midden in.
Niet misschien, 't is een van de redenen dat ze meer mensen nodig hebben (maar ook nog 64 bits systemen, C++0x en compiler-enforced security ). En ja, dat gebeurt inderdaad niet hier.
Ik weet iniedergeval wel dat mocht het voor je doorgaan, ik heel goed ga kijken of er geen rare fratsen met pointers zijn uitgehaald als ik nieuwe MSVC in de toekomst gekocht heb. Zoja, dan weet ik dan wie er waarschijnlijk verandwoordelijk voor was! 8)
Sowieso wil je geen vreemde grappen uithalen met pointers, als je de CLI target. Dat is een VM waarbij het concept pointer niet (altijd) overeenkomt met de CPU; daarom heet het ook een Virtual Machine.
[...]
Tuurlijk, maar dat weet iedereen. Het punt waar het om gaat is x86-64. Die zou 64bits pointers gebruiken terwijl er maar 48bits van in gebruik zijn. Ik weet niet wat de implementatie gaat doen, 48bits pointers aanmaken? Kan wel, zijn precies 6 bytes en dan valt er weinig te discuseren meer. Maar... het zou ook kunnen dat de implementatie meteen 64bits pointers gebruikt en de bovenste 18 reserveert (dwz, moeten 0 zijn).
AMD64 Architecture Programmers manual vol1, para 2.2.2:
-------------------------------------------------------
Long mode defines 64 bits of virtual address, but
implementations of the AMD64 architecture may support fewer
bits of virtual address. Although implementations might not
use all 64 bits of the virtual address, they check bits 63 through
the most-significant implemented bit to see if those bits are all
zeros or all ones. An address that complies with this property is
said to be in canonical address form. If a virtual-memory
reference is not in canonical form, the implementation causes a
general-protection exception or stack fault.
-------------------------------------------------------
De bits boven je feitelijke sign bit moeten dus sign extended zijn.
Aan de andere kant; zo'n fault is restartable, en je kunt het de fault handler dus gebruiken om de type check mee te implementeren. het maakt de zaak niet sneller, maar voor een debug mode misschien nog acceptabel. Tenslotte kun je per pointer wel of niet die extra bits toevoegen. Het grote nadeel van deze truc is wat we eerder al bespraken; je krijgt geen GPF of stack fault als een implementatie wel aal 64 bits gebruikt.

Wat je kunt blijven doen is een page fault triggeren als de bovenste bits gezet. Of een page namelijk aanwezig is is nog steeds een beslissing van het OS. Die page fault kun je gebruiken om een disk load mee te starten, maar natuurlijk ook een type check.

In 2.2.3 staat overigens nog dat in 32-bits compatibility mode er een prefix 0x00000000 wordt gebruikt voordat het adres naar een fysiek adres vertaald wordt. Dat betekent dus dat elke compatibility taken in theorie ook fysiek geheugen boven de 4Gb kunnen gebruiken, maar je blijft gebonden aan 4Gb per task.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Verwijderd

Ik heb de code staticvoid_cast ook even getest op Visual C++ 7.1 als win32 console app, met uitgeschakelde language extension. Tot mijn verbazing kreeg ik de volgende error (die we al eerder zagen in deze thread, maar toen bij char)

error C2440: 'reinterpret_cast' : cannot convert from 'int *' to 'unsigned long'
The target is not large enough

Als ik de language extensions aan zet krijg ik ongeveer het zelfde maar dan als warning:

warning C4311: 'reinterpret_cast' : pointer truncation from 'int *' to 'unsigned long'
A 64-bit pointer was truncated to a 32-bit int or 32-bit long.

Als ik detect 64-bit portability issues uitzet compiled ie ook.

M.a.w. in de documentatie van reinterpret_cast is MS dus niet helemaal duidelijk, of heeft de betrefende documentatie nog niet aangepast aan de nieuwe 64 bit situatie. Het is wel vreemd deze error, want wat moet je nu doen op een echt 64 bit systeem??? Als op een echt 64 bit systeem een long wel scaled naar 64 bit dan slaat de '64-bit portability' check helemaal nergens, en als ie niet scaled (en dus 32 bits blijft op VC7.1) dan is er dus geen standaard c++ manier om een pointer naar welk integral type dan ook te casten.

Iniedergeval als ie wel compiled geeft het de volgende output:

Visual C++ 7.1 - release

int value before casts:10

int pointer 00415060
long view of int pointer 4280416
float pointer 00415060
long view of float pointer 4280416
foo pointer 00415060
long view of foo pointer 4280416
bar pointer 00415060
long view of bar pointer 4280416
long pointer 00415060
long view of long pointer 4280416
ulong pointer 00415060
long view of ulong pointer 4280416
int pointer 00415060
long view of int pointer 4280416

int value after casts:10

De versie met reinterpret_cast geeft:

int value before casts:10

int pointer 0012FEE4
long view of int pointer 1244900
float pointer 0012FEE4
long view of float pointer 1244900
foo pointer 0012FEE4
long view of foo pointer 1244900
bar pointer 0012FEE4
long view of bar pointer 1244900
int pointer 0012FEE4
long view of int pointer 1244900

int value after casts:10

Tevens heb ik getest op Red Hat Linux 9 met g++ 3.2.2 en die gaf vergelijkbare output. Ik heb nog een oude indy staan met een mips 4600, mischien dat ik die nog van de week probeer, hoewel ik vermoed dat de resultaten toch wel weer hetzelfde zullen zijn.

conclusie
We hebben nu al veel voorbeelden voorbij zien komen, maar tot op heden bleven de pointers steeds hetzelde.

[ Voor 3% gewijzigd door Verwijderd op 13-01-2004 12:38 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Verwijderd schreef op 09 januari 2004 @ 23:05:
Ik heb de code staticvoid_cast ook even getest op Visual C++ 7.1 als win32 console app, met uitgeschakelde language extension. Tot mijn verbazing kreeg ik de volgende error (die we al eerder zagen in deze thread, maar toen bij char)

error C2440: 'reinterpret_cast' : cannot convert from 'int *' to 'unsigned long'
The target is not large enough

Als ik de language extensions aan zet krijg ik ongeveer het zelfde maar dan als warning:

warning C4311: 'reinterpret_cast' : pointer truncation from 'int *' to 'unsigned long'
A 64-bit pointer was truncated to a 32-bit int or 32-bit long.

Als ik detect 64-bit portability issues uitzet compiled ie ook.

M.a.w. in de documentatie van reinterpret_cast is MS dus niet helemaal duidelijk, of heeft de betrefende documentatie nog niet aangepast aan de nieuwe 64 bit situatie. Het is wel vreemd deze error, want wat moet je nu doen op een echt 64 bit systeem??? Als op een echt 64 bit systeem een long wel scaled naar 64 bit dan slaat de '64-bit portability' check helemaal nergens, en als ie niet scaled (en dus 32 bits blijft op VC7.1) dan is er dus geen standaard c++ manier om een pointer naar welk integral type dan ook te casten.
Niet helemaal waar; als ik me goed herinner is er een type DWORD_PTR in de nieuwere Windows SDKs, wat altijd een integral type is waar een pointer in past.
Je hebt wel gelijk als je zegt dat er geen standaard integral type is, waar een pointer naar toe te casten is. Dat was al bekend, en dat wordt niet gezien als een defect.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

Beetje late reactie, maar
Verwijderd schreef op 09 januari 2004 @ 23:05:
M.a.w. in de documentatie van reinterpret_cast is MS dus niet helemaal duidelijk, of heeft de betrefende documentatie nog niet aangepast aan de nieuwe 64 bit situatie. Het is wel vreemd deze error, want wat moet je nu doen op een echt 64 bit systeem??? Als op een echt 64 bit systeem een long wel scaled naar 64 bit dan slaat de '64-bit portability' check helemaal nergens, en als ie niet scaled (en dus 32 bits blijft op VC7.1) dan is er dus geen standaard c++ manier om een pointer naar welk integral type dan ook te casten.
Als je met VC++ compilet voor 64-bits windows dan zijn de ints en longs nog steeds 32 bit, de error die je krijgt is dan ook logisch. Als je 64 bits wilt gebruiken dan moet je long long of __int64 nemen (de een is een alias voor de ander)

Is idd niet standaard, maar ik geloof ergens gelezen te hebben dat long long in de volgende C++ update erbij gaat komen, MSalters?
(zijn er trouwens goede sites waar je dit soort dingen kunt nalezen?)
MSalters schreef op 10 januari 2004 @ 16:43:
[...]

Niet helemaal waar; als ik me goed herinner is er een type DWORD_PTR in de nieuwere Windows SDKs, wat altijd een integral type is waar een pointer in past.
Je hebt wel gelijk als je zegt dat er geen standaard integral type is, waar een pointer naar toe te casten is. Dat was al bekend, en dat wordt niet gezien als een defect.
Maar daar heb je toch ptrdiff_t voor? Die mapt misschien niet naar een standaard primitive, maar het is wel een portable type. size_t, time_t en ptrdiff_t zijn iig 64 bits onder VC++ 7.1 in 64 bits mode

[ Voor 34% gewijzigd door .oisyn op 14-01-2004 02:42 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Zoals wij in het westen zeggen; beter laat dan nooit :)
Als je met VC++ compilet voor 64-bits windows dan zijn de ints en longs nog steeds 32 bit, de error die je krijgt is dan ook logisch. Als je 64 bits wilt gebruiken dan moet je long long of __int64 nemen (de een is een alias voor de ander)
Klopt ja, de __int64 had ik al gezien, alsmede de __int8, __int16 en __int32. Toch begrijp ik dit niet helemaal. MS stelt dat de laatste synoniem zijn voor resp. char, short en int. Maar de C++ standaard defineert helemaal geen absolute grotes voor deze types, alleen relatieve.

Het is in standaard C++ legaal om een pointer naar een int te casten. M.a.w. een int alsmede een long moeten dus tenminste zo groot zijn als een pointer. Dit is met VC7.1 in 64 bit modus niet meer het geval. Omdat het VC team (iniedergeval uit monde van Sutter) voor de 100% standard conformance wil gaan, kan het bijna niet anders zijn dat de gewone int in een komende VC wel 64 bits wordt.
Is idd niet standaard, maar ik geloof ergens gelezen te hebben dat long long in de volgende C++ update erbij gaat komen, MSalters?
Zowieso is het de bedoeling om in de C++0x revisie een groot aantal C99 dingen over te nemen, zodat C weer 'bijna' een echte subset van C++ wordt. Overigens ken ik nog niet al te veel mensen die in C99 programmeren, en ook niet al te veel compilers die C99 helemaal implementeren. Dacht alleen EDG tot nu toe.
Maar daar heb je toch ptrdiff_t voor? Die mapt misschien niet naar een standaard primitive, maar het is wel een portable type. size_t, time_t en ptrdiff_t zijn iig 64 bits onder VC++ 7.1 in 64 bits mode
Een aantal constructies in VC7.1 komen een beetje complex over.

Bv neem het type NMHDR in MFC7. Deze komt bv voor als 2de parameter in CDialog::OnToolTipNotify.

Het attribuut idFrom staat gespecificeerd als een UINT in richedit.h en UINT_PTR in winuser.h.

In vorige versies was het altijd een UINT (als ik me niet vergis). Een toekenning van dit attribuut aan een UINT variable geeft dan een warning C4244 (possibe loss of data). Maar, zowel een UINT als een UINT_PTR zijn een typedef voor een unsigned int. Waarbij dan de UINT_PTR typedef nog het MS keyword __w64 heeft staan in 32 bit modus, dat opzich nix doet maar alleen als een warning marker voor de compiler geldt, namelijk om typedefs te marken waarvan de grote veranderd voor een 32 of 64 bits compiler.

Namelijk, in de 64 bit modus bevat de typedef voor UINT_PTR het keyword __int64. Als echter een int gewoon mee zou groeien, dan zou deze ietswat complexe materie niet nodig zijn. De C(++) standaard heeft niet voor niks relatieve grotes gespecificeerd ipv absolute. Of zou het gewoon zijn dat dit een historische fout is in de standaard?

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op 15 januari 2004 @ 17:05:
Klopt ja, de __int64 had ik al gezien, alsmede de __int8, __int16 en __int32. Toch begrijp ik dit niet helemaal. MS stelt dat de laatste synoniem zijn voor resp. char, short en int. Maar de C++ standaard defineert helemaal geen absolute grotes voor deze types, alleen relatieve.
het zijn compiler-specific types. Onder VC++ is een __int8 altijd 8 bits. Volgens de standaard hoeft dat idd niet, maar die is ook ontworpen voor alle mogelijke systeemconfiguraties. VC++ compileert in principe alleen maar voor 32 en 64 bits windows :)
Het is in standaard C++ legaal om een pointer naar een int te casten.
Niet helemaal, 5.2.10
-4- A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is implementation-defined [Note: it is intended to be unsurprising to those who know the addressing structure of the underlying machine. ]

-5- A value of integral type or enumeration type can be explicitly converted to a pointer.*

[Footnote: Converting an integral constant expression (expr.const) with value zero always yields a null pointer (conv.ptr), but converting other expressions that happen to have value zero need not yield a null pointer. --- end foonote]

A pointer converted to an integer of sufficient size (if any such exists on the implementation) and back to the same pointer type will have its original value; mappings between pointers and integers are otherwise implementation-defined.
Er wordt dus helemaal niets gezegd over dat int altijd groot genoeg is om een pointer te bevatten.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Waarom definieert de standaard eigenlijk geen eventueel optionele types met een vaste grootte?
Dat niet alle platformen een bepaalde grootte ondersteunen is logisch, maar voor de platformen die bepaalde groottes wel ondersteunen zou het erg handig zijn om daar in ieder geval een type voor te hebben zonder geklooi met platform-afhankelijke typedefs.
Volgens mij is er namelijk heel veel code die er toch vanuit gaat dat bijvoorbeeld een int 32-bit is.

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

Heel veel ja, maar zoals ik al zei, de standaard is erop ontworpen dat het werkt op elke mogelijke systeemconfigutatie. Als de standaard definieert dat er een type is dat 32 bits is, en jij werkt op een systeem waar CHAR_BITS == 10 en sizeof (int) == 3, dan ben je mooi de pineut

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Daarom zei ik, optionele types. Als het platform ze ondersteund zijn ze beschikbaar. Zo niet -> pech gehad.

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

En dus per definitie al niet meer portable. Dat druist een beetje tegen het idee van standaarden in imho :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Een beetje. Maar dat betekent dus eigenlijk dat je behalve chars geen ander type kunt gebruiken in binaire IO en dat je andere types dus 'met de hand' moet opbouwen.

En hoezo niet portable? Het is portable naar alle platforms die ook die types ondersteunen.
Of bedoelde je niet portable naar alle bestaande platforms?

[ Voor 37% gewijzigd door Olaf van der Spek op 15-01-2004 18:08 ]


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

Uhm ja, maar dat is ook het idee van chars. Een char is de kleinst addresseerbare unit in C/C++, en sizeof (char) = 1 (:P <-- inside joke, don't bother ;)). Binaire IO werkt altijd met de kleinst addresseerbare unit, en dus een char. Halve chars gaat namelijk een beetje moeilijk :)

Wat bedoel je met 'met de hand opbouwen' :?

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
C++:
1
2
unsigned char* r;
int a = r[0] << 24 | r[1] << 16 | r[2] << 8 | r[3];


Dat is een stuk 'lastiger' dan de volgende code.
C++:
1
2
unsigned char* r;
int a = ntohl(*reinterpret_cast<int*>(r));

[ Voor 13% gewijzigd door Olaf van der Spek op 15-01-2004 18:19 ]


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

Daar kun je sowieso geen zinnig woord over zeggen, zie het stukje uit de standaard wat ik een paar posts hierboven quotte.

Bovendien zit je ook nog met endianness... jouw code klopt op een big-endian machine zoals een motorola chip, op intels gaat het fout. Je kunt ook gewoon keihard een access violation oid krijgen als de char * niet op een int boundary gealigned is. Maar dat is dus allemaal heel erg implementation defined. Als je portable code wilt schrijven moet je dat soort dingen gewoon niet toepassen :)

[ Voor 34% gewijzigd door .oisyn op 15-01-2004 18:21 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Weet ik, daar had ik al aan gedacht (en ik had het net verbeterd voordat ik jouw code las). Maar welk stukje bedoel je dan?
Dat er niks gezegd wordt over de grootte van een int?

Maar hoe bepaal je dan eigenlijk welk type je moet gebruiken?

BTW, niet alle Intels zijn little-endian volgens mij.

[ Voor 27% gewijzigd door Olaf van der Spek op 15-01-2004 18:23 ]


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 27-05 23:27

Creepy

Tactical Espionage Splatterer

OlafvdSpek schreef op 15 januari 2004 @ 18:16:
C++:
1
2
unsigned char* r;
int a = r[0] << 24 | r[1] << 16 | r[2] << 8 | r[3];
Hier ga je er vanuit dat een char altijd 8 bits is, wat (helaas) niet altijd het geval is.

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Creepy schreef op 15 januari 2004 @ 18:23:
Hier ga je er vanuit dat een char altijd 8 bits is, wat (helaas) niet altijd het geval is.
Ook dat nog. Hoe los je dat op in portable code?

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

OlafvdSpek schreef op 15 januari 2004 @ 18:21:
Maar welk stukje bedoel je dan? Dat er niks gezegd wordt over de grootte van een int?
nee, mijn fout. Ik las je code aanvankelijk verkeerd, dus de eerste alinea van die post kun je vergeten
Maar hoe bepaal je dan eigenlijk welk type je moet gebruiken?
Docs lezen van je compiler ;) Je bent hier sowieso bezig met implementatie-specifieke code, dus dan kun je ook aannames doen over de grootte van de types

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

OlafvdSpek schreef op 15 januari 2004 @ 18:24:

Ook dat nog. Hoe los je dat op in portable code?
In feite kan het niet, aangezien zoals aangetoond reinterpret_cast niet per se een logische mapping hoeft te geven tussen pointers, maar laten we er even van uitgaan dat dat wel zo is

Dit is portable code:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <fstream>

int main ()
{
    int i = 1234;

    {
        std::ofstream f ("file", std::ios::binary);
        f.write (reinterpret_cast<char *> (&i), sizeof (int));
    }

    {
        std::ifstream f ("file", std::ios::binary);
        f.read (reinterpret_cast<char *> (&j), sizeof (int));
    }

    if (i == j)
        std::cout << "ok!" << std::endl;
}


Code is portable, werkt overal prima. Je schrijft een int weg, en daarna lees je 'm weer uit. En de uitgelezen int is hetzelfde als die je hebt weggeschreven.

Echter, de bestanden op verschillende systemen hoeven niet hetzelfde te zijn.

[ Voor 5% gewijzigd door .oisyn op 15-01-2004 18:37 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

.oisyn schreef op 15 januari 2004 @ 17:38:
[...]
het zijn compiler-specific types. Onder VC++ is een __int8 altijd 8 bits. Volgens de standaard hoeft dat idd niet,
De MSDN documentatie zegt het volgende:
The types __int8, __int16, and __int32 are synonyms for the ANSI types that have the same size, and are useful for writing portable code that behaves identically across multiple platforms. The __int8 data type is synonymous with type char, __int16 is synonymous with type short, and __int32 is synonymous with type int. The __int64 type has no ANSI equivalent.
Dus oa dat __int8 precies een char is. Maar de standaard zegt alleen dat een char tenminste 8 bits is. Zelfde verhaal voor __int16 en short. __int32 is raar, want de standaard garandeerd alleen dat een long tenminste 32 bits is. (of men (in MSDN) bedoelt hier natuurlijk long int).
maar die is ook ontworpen voor alle mogelijke systeemconfiguraties. VC++ compileert in principe alleen maar voor 32 en 64 bits windows :)
Vroeger toch ook voor de Mac? Tenminste in de MFC source code zie ik nog steeds veel verwijzingen naar de Mac. Het kan natuurlijk zijn dat de MFC zelf, en dus niet VC ooit als crossplatform was opgezet.
Niet helemaal, 5.2.10


Er wordt dus helemaal niets gezegd over dat int altijd groot genoeg is om een pointer te bevatten.
Ja... het staat er inderdaad.

Dan vraag ik me toch af waarom iemand als Herb Sutter (zie een quote van flowerp boven) dan beweert dat een pointer naar een integer casten standaard C++ is met 'well defined semantics'.
Herb Sutter is toch een van de meer kundige mensen op standaard C++ gebied...

Verwijderd

OlafvdSpek schreef op 15 januari 2004 @ 18:24:
[...]

Ook dat nog. Hoe los je dat op in portable code?
Mischien door van elke char eerst alleen de laagste 8 bits te pakken. Namelijk, een grote van tenminste 8 bits is wel gegarandeerd. Een short int is tenminste 16 bits en een long int in tenminste 32 bits.

Omdat alle groote aannamens <= zijn, kan volgens die regels een char dus makkelijk 32 bits zijn op een bepaald platform.

Hoewel mischien in dit geval niet erg effecient, kun je in C++ natuurlijk wel types met een bepaald aantal bits bouwen door bitfields te gebruiken. Nadeel daarvan is wel dat de meeste compilers op de meeste machines hier niet erg efficiente code voor kunnen genereren. Er is meer assembly code voor nodig die ook nog eens langzamer werkt vaak.

  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Verwijderd schreef op 15 januari 2004 @ 19:02:
Mischien door van elke char eerst alleen de laagste 8 bits te pakken. Namelijk, een grote van tenminste 8 bits is wel gegarandeerd. Een short int is tenminste 16 bits en een long int in tenminste 32 bits.
Ik dacht dat alleen short gegarandeerd minimaal 16-bits was en dat voor int en long (en ik gok ook long long) alleen <= geldt.
Omdat alle groote aannamens <= zijn, kan volgens die regels een char dus makkelijk 32 bits zijn op een bepaald platform.

Hoewel mischien in dit geval niet erg effecient, kun je in C++ natuurlijk wel types met een bepaald aantal bits bouwen door bitfields te gebruiken. Nadeel daarvan is wel dat de meeste compilers op de meeste machines hier niet erg efficiente code voor kunnen genereren. Er is meer assembly code voor nodig die ook nog eens langzamer werkt vaak.
Als je alleen de onderste 8-bits pakt, ga je er wel vanuit dat 'bytes' gezeroextend worden. Dat hoeft ook niet zo te zijn.

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op 15 januari 2004 @ 18:48:
Dus oa dat __int8 precies een char is.
euh ja, en laat een char nou altijd precies 8 bits zijn met VC++. Ik snap je punt niet helemaal
Vroeger toch ook voor de Mac? Tenminste in de MFC source code zie ik nog steeds veel verwijzingen naar de Mac. Het kan natuurlijk zijn dat de MFC zelf, en dus niet VC ooit als crossplatform was opgezet.
oh, dat weet ik verder niet, maar volgens mij is de byte-grootte en -ordering op een mac niet anders.
Verwijderd schreef op 15 januari 2004 @ 19:02:

Mischien door van elke char eerst alleen de laagste 8 bits te pakken. Namelijk, een grote van tenminste 8 bits is wel gegarandeerd.
Is dat zo? Kan het zo snel niet uit de standaard halen eigenlijk
Een short int is tenminste 16 bits en een long int in tenminste 32 bits
Nee, 1 = sizeof (char) <= sizeof (short) <= sizeof (int) <= sizeof (long)
There are four signed integer types: ``signed char'', ``short int'', ``int'', and ``long int.'' In this list, each type provides at least as much storage as those preceding it in the list. Plain ints have the natural size suggested by the architecture of the execution environment* ;

[Footnote: that is, large enough to contain any value in the range of INT_MIN and INT_MAX, as defined in the header . --- end foonote]
En hetzelfde een stukje verderop nog een keer herhaald voor de unsigned varianten
Hoewel mischien in dit geval niet erg effecient, kun je in C++ natuurlijk wel types met een bepaald aantal bits bouwen door bitfields te gebruiken.
dat werkt alleen in structures, niet als losse typen. En als je een enkele bitfield in een structure codeert dan zal sizeof (die struct) gelijk zijn als sizeof (het integral type dat je gebruikt hebt voor de bitfield) voor de meeste compilers

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
.oisyn schreef op 15 januari 2004 @ 18:29:
Docs lezen van je compiler ;) Je bent hier sowieso bezig met implementatie-specifieke code, dus dan kun je ook aannames doen over de grootte van de types
Ik snap niet wat het voordeel is van het verplaatsen van die taak van de compiler naar de programmeur.
Volgens mij zou heel wat code een stuk simpeler zijn als iets als __int32 en __int64 wel standaard waren.

Jouw code is portable (compiled nergens, je bent j vergeten te defineren ;->), maar niet echt functioneel als het ook in- en uitvoer van andere systemen moet kunnen verwerken.

[ Voor 17% gewijzigd door Olaf van der Spek op 15-01-2004 19:24 ]


Verwijderd

.oisyn schreef op 15 januari 2004 @ 19:19:

euh ja, en laat een char nou altijd precies 8 bits zijn met VC++. Ik snap je punt niet helemaal
Ja natuurlijk :) Je hebt gelijk (duurde even voor het kwartje viel). Ik zat te denken aan de opmerking gelijk aan een ANSI char, terwijl ze natuurlijk bedoelen een VC++ char omdat __int8 natuurlijk alleen op VC++ voorkomt.
oh, dat weet ik verder niet, maar volgens mij is de byte-grootte en -ordering op een mac niet anders.
De mac is precies andersom kwa endiannes als Intel x86. Er zijn overigens 2 Mac versies geweest waar je 'echt' de endiannes kon veranderen (de g3 en de g4, de oudere PPC's konden het wel in theorie maar niet effecient in de praktijk, met de g5 zijn ze er weer mee gestopt).
Is dat zo? Kan het zo snel niet uit de standaard halen eigenlijk
Volgens Bjarne Stroustrup, the C++ programming language, third edition, blz 75:

... In addition, it is guaranteed that a char has at least 8 bits, a short at least 16 bits, and a long at least 32 bits. A char can hold a character of the machine's character set.

  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Verwijderd schreef op 15 januari 2004 @ 19:42:
... In addition, is is guaranteed that a char has at least 8 bits, a short at least 16 bits, and a long at least 32 bits. A char can hold a character of the machine's character set.
Zit die typo ook in die edition of heb je die zelf toegevoegd? ;->
En weet je misschien of die garantie voor longs veranderd is (en zo ja, wanneer) of dat het altijd al zo was?

Verwijderd

OlafvdSpek schreef op 15 januari 2004 @ 19:50:
[...]

Zit die typo ook in die edition of heb je die zelf toegevoegd? ;->
Welke typo? :+
En weet je misschien of die garantie voor longs veranderd is (en zo ja, wanneer) of dat het altijd al zo was?
Eigenlijk niet. Het lijkt mischien een moeilijke garantie in de tijd dat machines nog 16 bits waren. Aan de andere kant, the 3rd edition is natuurlijk wel herzien voor de c++98 standaard, dus wellicht sinds dien? (weet ik niet zeker)

  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Verwijderd schreef op 15 januari 2004 @ 19:42:
... In addition, is is guaranteed that a char has at least 8 bits, a short at least 16 bits, and a long at least 32 bits. A char can hold a character of the machine's character set.

[ Voor 18% gewijzigd door Olaf van der Spek op 15-01-2004 20:35 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
.oisyn schreef op 14 januari 2004 @ 01:48:
[64 bits ints]

Maar daar heb je toch ptrdiff_t voor? Die mapt misschien niet naar een standaard primitive, maar het is wel een portable type. size_t, time_t en ptrdiff_t zijn iig 64 bits onder VC++ 7.1 in 64 bits mode
ptrdiff_t en size_t zijn gerelateerd aan object groottes; een 64 bits compiler die geen losse object >4Gb ondersteunt mag best een 32 bits ptrdiff_t hebben. De reden is dat ptrdiff_t het verschil is tussen twee pointers, en je mag alleen het verschil berekenen tussen twee pointers in een array (=object)

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
OlafvdSpek schreef op 15 januari 2004 @ 19:20:
[...]
Volgens mij zou heel wat code een stuk simpeler zijn als iets als __int32 en __int64 wel standaard waren.
Dat ben ik met je eens. Vergeet echter niet dat wat nu in 2004 triviaal lijkt, destijds mischien helemaal niet zo was. Bv een while loop of een class lijkt nu zo triviaal maar moest eerst toch nog echt worden uitgevonden. Men is toendertijd gewoon niet op het idee gekomen iets als int<n> te maken met n een macht van 2, mischien met een min en max voor n per C++ revisie.

Overigens heeft Java wel vaste grootes. Het idee was dan ook dat Java tot in de eeuwigheid vast zou zitten aan zijn 32bits int, en dat C++ veel beter af was omdat oa de int transparant mee kon groeien met de machine woord lengte. Helaas dat MS en consorten de boel een beetje lopen te verzieken door de int gewoon op 32bits te houden en een aparte __int64 te introduceren.

Iedereen die logisch nadenkt weet dat dat of dom is, of dat er een hele goede reden voor moet zijn. Namelijk, als het 'meegroeien' van een int niet in de geest van de C++ standaard was, dan hadden ze dat ding toch gewoon meteen wel op 32bits gespecificeerd? 8)7 |:(

@henkie
Goed argument ja, die Sutter is zo gek toch nog niet. Clashed een beetje met wat in de standaard staat...

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
OlafvdSpek schreef op 15 januari 2004 @ 20:34:
henk_DE_man schreef op 15 januari 2004 @ 19:42:
... In addition, is is guaranteed that a char has at least 8 bits, a short at least 16 bits, and a long at least 32 bits. A char can hold a character of the machine's character set.
Hmmm, heb je dat nu zelf verkeerd overgetyped? In het bericht waar je naar verwijst staat helemaal geen "is is", en er staat ook niet bij dat het bericht gewijzigt is. :?

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
flowerp schreef op 17 januari 2004 @ 00:14:
Hmmm, heb je dat nu zelf verkeerd overgetyped? In het bericht waar je naar verwijst staat helemaal geen "is is", en er staat ook niet bij dat het bericht gewijzigt is. :?
Het is toch echt geedit. Waarom die melding er niet bij staat weet ik niet.

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

flowerp schreef op 17 januari 2004 @ 00:10:
Iedereen die logisch nadenkt weet dat dat of dom is, of dat er een hele goede reden voor moet zijn. Namelijk, als het 'meegroeien' van een int niet in de geest van de C++ standaard was, dan hadden ze dat ding toch gewoon meteen wel op 32bits gespecificeerd? 8)7 |:(
Ja maar stel je laat de int meegroeien, wat moet dan het 32-bits type worden? Dat kan niet long worden, want die moet minstens zo groot zijn als een int. Als je daar short voor gebruikt ben je je 16-bits type kwijt (overigens behandeld vc++ wchar_t als een alias voor short, dit kun je uitzetten, maar als een short dan 32 bits wordt dan moet wchar_t wel een eigen type zijn)

En dit zorgt er dan weer voor dat je geen oude code kunt gebruiken, en een naadloze portability tussen 32 en 64 bits staat toch wel hoog op het vaandel.


Offtopic: als je minder dan 2% edit, dan komt het editbericht er niet bij te staan ;)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
.oisyn schreef op 18 januari 2004 @ 05:25:
Ja maar stel je laat de int meegroeien, wat moet dan het 32-bits type worden? Dat kan niet long worden, want die moet minstens zo groot zijn als een int. Als je daar short voor gebruikt ben je je 16-bits type kwijt (overigens behandeld vc++ wchar_t als een alias voor short, dit kun je uitzetten, maar als een short dan 32 bits wordt dan moet wchar_t wel een eigen type zijn)

En dit zorgt er dan weer voor dat je geen oude code kunt gebruiken, en een naadloze portability tussen 32 en 64 bits staat toch wel hoog op het vaandel.


Offtopic: als je minder dan 2% edit, dan komt het editbericht er niet bij te staan ;)
Volgens mij is dit weer een perfect voorbeeld waar __int## makkelijker was geweest.
Code die een type dat precies ## bits groot is nodig heeft, gebruikt __int## en andere code gebruikt gewoon int.

  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
.oisyn schreef op 18 januari 2004 @ 05:25:
[...]

Ja maar stel je laat de int meegroeien, wat moet dan het 32-bits type worden? Dat kan niet long worden, want die moet minstens zo groot zijn als een int.
Het is nu vaak zo dat:
short int = 16 bits
int = 32 bits
long int = 32 bits

Maar dit is alleen maar een toevalligheid. Er staat nergens dat er perse een 16 bits type nodig is. Waarom het zo gegroeid is dat int en long int in de praktijk hetzelfde zijn weet ik ook niet. In mijn totale naiviteit zou ik zeggen dat je bij mee groeien zou doen:

short int = 32 bits
int = 64 bits
long int = 128 bits

De long int zou dan geen directe hardware support hebben op 64 bits systemen. De 'plain' int geeft door zijn 'plainness' dan aan dat het gaat om het gewone 'systeem' woord. De short is dan iets minder als het systeem woord en de long iets meer (dus potentieel langzamer). Voor het gevoel zou dit meer kloppen. (IMHO)

Als dit echt ongewenst is voor een basic type zou je kunnen zeggen:

short int = 16 bits
int = 32 bits
long int = 64 bits

Dan is je int inderdaad niet meegegroeid (in de praktijk), maar de long wel. In beiden gevallen ben je van die stomme praktijk situatie af dat long en int evengroot zijn. Dat slaat gewoon helemaal nergens op.
En dit zorgt er dan weer voor dat je geen oude code kunt gebruiken, en een naadloze portability tussen 32 en 64 bits staat toch wel hoog op het vaandel.
Maar waarom zijn die types dan niet ooit voor een bepaald aantal bits gespecificeerd, als dus nu blijkt dat dat 'meegroeien' helemaal niet gebeurt en men het blijkbaar altijd zo wil dat de short int 16 bit is en de 'plain' int 32 bit. Dan mogen ze van mij dat "short int <= int <= long int" verhaaltje vandaag nog uit de standaard schrappen, want dat heeft dan geen enkel practisch nut.

Offtopic: als je minder dan 2% edit, dan komt het editbericht er niet bij te staan ;)
Aha. Dus toch :-)

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Samengevat: Waarom specificeert de standaard geen bitgroottes, terwijl de verschillende generaties x86 van 16-16-32 via 16-32-32 naar 16-32-32-64 gaan?

Antwoord: Omdat niet alles een x86 is?

Als ik het me goed herinner hebben courante Crays een behoorlijk goede C++ compiler met 43 bits longs, met 21 bits padding. Als ik het me goed herinner was de reden dat die 43 bits dan overeenkwamen met dezelfde 43 bits van een floating point getal met dezelfde waarde, en je kon dus met simpele bitops de long->float cast doen.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
x86 is niet het enige platform waar types met een vast aantal bits handig zouden zijn.

  • flowerp
  • Registratie: September 2003
  • Laatst online: 04-02 02:01
MSalters schreef op 18 januari 2004 @ 14:59:
Samengevat: Waarom specificeert de standaard geen bitgroottes, terwijl de verschillende generaties x86 van 16-16-32 via 16-32-32 naar 16-32-32-64 gaan?
Volgens mij is het meer:


Samengevat:

Waarom specificeert de standaard geen bitgroottes, terwijl de verschillende compilers in hun evolutie nu opeens wel aan de huidge, historische groottes -moeten- vasthouden.



Standaard C++ zou op ieder systeem moeten compileren. Aangezien de bitgroottes niet vast staan, en bv gray dus ook al iets groter dan 32 bits heeft, snap ik nog steeds niet waarom de bestaande types niet gewoon kunnen doorgroeien, ipv dat er nu nieuwe (C99:long long, MSC++:__int64, enz) types geintroduceerd -moeten- worden.

Als dat alleen is voor compatibiliteit, dan zou standaard C++ code dus niet op de Gray kunnen compileren wegens die 43 bits? Als dat wel kan, waarom zou het dan voor 64 bits niet kunnen? Blijkbaar klopt er iets gewoon niet. C99 doet ook al niet aan doorgroeien (en kwam dus met long long). Als een C++ implementatie in het verleden van 16-16-32 naar 16-32-32 kon gaan, waarom is dan nu opeens een 4de type nodig?


Dit staat overigens los van het feit, waarop Olaf al meerdere malen terecht op hamert, dat een optionele set van types, die wel fixed bit zijn, op elk platform, voor elke compiler handig zouden zijn. Het 'de hele wereld is geen x86' doet niet echt ter zake, omdat bv ook de Apple platformen met de G4->G5 transitie met hetzelfde probleem zitten.

Ik weet niet hoe de Apple compiler (iets aangepaste gcc) dit gaat oplossen, of al heeft opgelost. Mischien wel een __integer_64, of wie weet wat voor leuks, of mischien wel gewoon de standaard int of long int, of alvast de long long...

It's shocking to find how many people do not believe they can learn, and how many more believe learning to be difficult.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
C99 heeft fixed-width types.
Overigens is er nog een reden voor long long die ik hier nog niet gezien heb. Die reden is dat C99 types wil definieren met tenminste 16,32 en 64 bits. Dat zijn natuurlijk de types short, long en long long.
int mag om historische redenen niet langer dan long zijn, dus als je 64 bits int's had gewild had je medium int moeten invoeren voor 32 bits, en long moeten optrekken naar 64 bits. long long is wat dat betreft een kleinere wijziging; het is een nieuw type met nieuwe eigenschappen die de bestaande types ongemoeid laat.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Dus toch. En zelfs required in plaats van optional. Maar zal dit ook worden toegevoegd aan de C++ standaard?
C++:
1
2
3
4
8-bit:  int8_t   uint8_t
16-bit: int16_t  uint16_t
32-bit: int32_t  uint32_t
64-bit: int64_t  uint64_t

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

[nohtml]
flowerp schreef op 18 januari 2004 @ 11:56:
Maar dit is alleen maar een toevalligheid. Er staat nergens dat er perse een 16 bits type nodig is.
weet ik, dat zei ik eerder al met een quote uit de standaard ;)
Waarom het zo gegroeid is dat int en long int in de praktijk hetzelfde zijn weet ik ook niet.
Waarschijnlijk omdat de 16-bits versies een int 16 bits was en een long 32 bits. Short en long zijn hetzelfde gebleven en de int is meegeschaald met het platform (wat je eigenlijk ook zou willen, omdat je als je een int gebruikt dan altijd het native type van je platform aanhoudt, want meestal zijn de berekeningen met dat type het snelst)
In mijn totale naiviteit zou ik zeggen dat je bij mee groeien zou doen:

short int = 32 bits
int = 64 bits
long int = 128 bits
Zoals ik al zei, dan ben je je 16 bits type kwijt
De long int zou dan geen directe hardware support hebben op 64 bits systemen.
waarom niet? De 64 bits int heeft dat op 32 bits systemen meestal ook.

Maar ik zou dan wel voor bovenstaande oplossing gaan, en dan __int16 gebruiken in geval van het 16 bits type, dat dan niet mapt naar een native type.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
OlafvdSpek schreef op 19 januari 2004 @ 09:34:
Dus toch. En zelfs required in plaats van optional. Maar zal dit ook worden toegevoegd aan de C++ standaard?
C++:
1
2
3
4
8-bit:  int8_t   uint8_t
16-bit: int16_t  uint16_t
32-bit: int32_t  uint32_t
64-bit: int64_t  uint64_t
Misschien, als je een compatibility-header toevoegd, maar er is behoorlijk wat kritiek binnen WG21(C++ commissie) op die types. In het bijzonder worden ze als te onduidelijk beschouwd, met te korte namen. In C++ is het waarschijnlijker dat de types int_fastest<8> of int_precisely<8> etcetera worden genoemd (of een variant daarvan). Voor C99 compatibility mogen int8_t en dergelijke dan typedefs zijn.
Het doel is dat mensen alleen voor zo'n type kiezen als er een goede reden voor is, en dat ze dan ook meteen het type kiezen wat ze bedoelen. Wat we dus niet willen is dat mensen uint32_t kiezen als ze alleen maar een unsigned type van tenminste 32 bits willen.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 22-05 16:53
Voor dedicated spul is het wel handig. Tot nog toe moest je telkens zelf opzoeken welke types een compiler ondersteunde en van daaruit de juiste types definieren.

Dit scheelt weer wat zoekwerk. ( Maar niet echt heel veel )
Groter voordeel is de eenduidige notatie denk ik.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Verwijderd

OlafvdSpek schreef op 19 januari 2004 @ 09:34:
Dus toch. En zelfs required in plaats van optional. Maar zal dit ook worden toegevoegd aan de C++ standaard?
Ik had begrepen dat een flink aantal C99 dingen wel toegevoegd gaan worden.
Hopelijk alles of bijna alles zodat het eigenlijk 1 taal blijft met C het procuderele gedeelte en C++ het OO stuk. (hoewel C++ natuurlijk ook veel toevoegingen heeft boven C die helemaal niet OO zijn).
C++:
1
2
3
4
8-bit:  int8_t   uint8_t
16-bit: int16_t  uint16_t
32-bit: int32_t  uint32_t
64-bit: int64_t  uint64_t
Kewl! Ik moet me toch maar eens wat dieper in C99 gaan verdiepen. Zag bv al wel dat ze hele rare initialisers hadden voor structs. Die lijken me mischien in C++ een beetje overbodig omdat die al constructors heeft.

Verwijderd

.oisyn schreef op 19 januari 2004 @ 17:38:
[nohtml]

Waarschijnlijk omdat de 16-bits versies een int 16 bits was en een long 32 bits. Short en long zijn hetzelfde gebleven en de int is meegeschaald met het platform (wat je eigenlijk ook zou willen, omdat je als je een int gebruikt dan altijd het native type van je platform aanhoudt, want meestal zijn de berekeningen met dat type het snelst)
Inderdaad. 100% mee eens. Ik weet niet waar ik het ooit heb gelezen, maar ik had ook altijd geleerd dat in c/c++ een int altijd precies het aantal bits van het native platform type is. Daarom begrijp dus ook ik niet de move van MS en co. om op een 64 bits platform de int 32 bits te maken en een apart type voor 64 bits te introduceren.

Ik denk echt dat op een 64 bits platform de short 32 bits zou moeten zijn, de int 64 en de long 128.
Zoals ik al zei, dan ben je je 16 bits type kwijt
En is dat dan zo erg?

Waar mischien wel wat problemen te verwachten waren en mischien dat dat de reden van MS is om nu aan de grootes vast te houden, is dat in win32 veel van de types WPARAM en LPARAM gebruikt wordt. Ik meen me herinneren dat LPARAM voor sommige functies 2 kleineren types bevatte, dat het een long was met 1 short in de hoge bits en 1 short in de lage. (ben te lui om het op te zoeken, doe binnenkort wel).

Toen op het x86 platform de architectuur naar 32 bits ging en daarmee ook de compiler, maakte MS nogal redelijk grootte veranderingen in de win16 api om er win32 van te maken (win16 was toch niet zo *enorm* in gebruik). Met win32 is dat veel moeilijker, en win64 is dan maar een relatief kleine aanpassing hierop. (de echte nieuwe API is .NET).

  • .oisyn
  • Registratie: September 2000
  • Nu online

.oisyn

Moderator Devschuur®

Demotivational Speaker

misschien compatibility met oude code?
Waar mischien wel wat problemen te verwachten waren en mischien dat dat de reden van MS is om nu aan de grootes vast te houden, is dat in win32 veel van de types WPARAM en LPARAM gebruikt wordt. Ik meen me herinneren dat LPARAM voor sommige functies 2 kleineren types bevatte, dat het een long was met 1 short in de hoge bits en 1 short in de lage. (ben te lui om het op te zoeken, doe binnenkort wel).
ja maar die zijn natuurlijk ook makkelijk opnieuw te definieren. Het zijn sowieso custom types, dus een #ifdef _WIN64 eromheen en je kunt ze mappen naar types die wel goed blijven

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1 2 Laatste