Me kõik teame, et CPU ja mälu on programmi kaks kõige olulisemat mõõdikut, nii et kui paljud inimesed on tõesti mõelnud küsimusele: Kui palju baite võtab ühe tüübi (väärtustüüp või viitetüüp) eksemplar mälus? Paljud meist ei oska vastata. C# pakub mõningaid operaatoriid ja API-sid suuruste arvutamiseks, kuid ükski neist ei lahenda täielikult seda probleemi, mida ma just küsisin. See artikkel annab meetodi mälubaitide arvu arvutamiseks, mida hõivavad väärtustüüpide ja viitetüüpide eksemplarid. Lähtekood on allalaaditud siit.
1. operaatori suurus 2. Marssal.Meetodi suurus 3. Ebaturvaline. Meetodi suurus > 4. Kas seda saab arvutada väljakuliikme tüübi põhjal? 5. Väärtustüüpide ja rakendustüüpide paigutus 6. LDFLDA direktiiv 7. Arvuta väärtustüübi baitide arv 8. Loe viitetüübi baitide arv 9. Täielik arvutus
1. operaatori suurus
Operatsiooni suurust kasutatakse selleks, et määrata baitide arv, mida tüübi eksemplar hõivab, kuid seda saab rakendada ainult haldamata tüüpidele. Niinimetatud haldamata tüüp on piiratud:
Primitiivtüübid: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double ja Single) Kümnendkomaa tüüp Loendamistüüp Osuti tüüp Struktuurid, mis sisaldavad ainult andmeliikmeid tüübiga Unmanaged Nagu nimigi ütleb, on haldamata tüüp väärtustüüp ning vastav eksemplar ei tohi sisaldada viiteid hallatavale objektile. Kui defineerime sellise üldise meetodi, mis kutsub suuruseoperaatoriks, peab üldine parameeter T lisama meetodile haldamata piirangu ja ebaturvalise märgise.
Ainult natiivsed ja enum-tüübid saavad kasutada sizeof-operaatorit otse, mida tuleb lisada, kui rakendada ka teistele tüüpidele (viideid ja kohandatud struktuure)./unsafekompileerimissildid ja need tuleb samuti paigutadaOhtlikkontekstis.
Kuna järgmine struktuur Foobar ei ole haldamata tüüp, tekib programmis kompileerimisviga.
2. Marssal.Meetodi suurus
Staatilised tüübid Marshal määratleb rea API-sid, mis aitavad meil jaotada ja kopeerida haldamata mälu, konverteerida hallatamata ja haldamata tüüpide vahel ning teha mitmeid muid toiminguid haldamata mälus (arvutusteaduses viitab Marshal mäluobjektide teisendamisele vastavasse vormingusse andmete salvestamiseks või edastamiseks). Staatiline, mis sisaldab järgmisi 4 SizeOf meetodi ülekoormust, et määrata antud tüübi või objekti baitide arv.
Marshal.SizeOf meetodil puudub piirang määratud tüübile Unmanaged tüübi puhul, kuid see nõuab siiski ühe määramistVäärtuse tüüp。 Kui sissetulev objekt on objekt, peab see olema ka väärtustüübi kast.
Kuna järgmine Foobar on defineeritud järgmiselt:lahke, seega mõlema SizeOf meetodi kutsed annavad ArgumentException erandi ja vihje: Tüüpi 'Foobar' ei saa korraldada haldamata struktuurina; tähenduslikku suurust ega nihet ei saa arvutada.
Marssal.Meetodi suurusGenerikumid ei ole toetatud, kuid neil on ka nõuded struktuuri paigutuse kohta, mis toetab tugeJärjestikunejaSelgesõnalinePaigutusrežiim. Kuna allpool näidatud Foobari struktuur võtab kasutusele automaatse paigutuse režiimi (Auto, mis ei toeta mälupaigutuse "dünaamilist planeerimist" välja liikmete põhjal, kuna mälupaigutuse nõuded on rangemad haldamata keskkondades), kasutavad SizeOf meetodi kutsed ikkagi sama ArgumentException erandi nagu eespool.
3. Ebaturvaline.Suuruse meetod
Static Unsafe pakub rohkem madala taseme operatsioone haldamata mälule ning sarnased SizeIOf meetodid on samuti selles tüübis defineeritud. Meetodil pole määratud tüübile mingeid piiranguid, kuid kui määrad viitetüübi, tagastatakseViidebaitide arv"(IntPtr.Suurus)。
4. Kas seda saab arvutada väljakuliikme tüübi põhjal?
Me teame, et nii väärtus- kui ka viitetüübid on kaardistatud pideva fragmendiks (või salvestatakse otse registrisse). Tüübi eesmärk on määrata objekti mälupaigutus ning sama tüüpi eksemplaridel on sama paigutus ja baitide arv on loomulikult sama (viitetüüpi väljade puhul salvestab see ainult viidatud aadressi selles baidijadas). Kuna baitide pikkus määratakse tüübi järgi, siis kui suudame määrata iga välja liikme tüübi, kas me ei saaks arvutada selle tüübi vastavate baitide arvu? Tegelikult pole see võimalik.
Näiteks teame, et baitide, lühikese, int ja pika baitid on 1, 2, 4 ja 8, seega on baitide binaari baitide arv 2, kuid tüübikombinatsiooni korral bait + lühike, bait + int ja bait + pikk ei ole vastavad baitid 3, 5 ja 9, vaid 3, 8 ja 16. Sest see puudutab mälu joondamise küsimust.
5. Väärtustüüpide ja viitetüüpide paigutus
Viitetüübi ja alamtüübi eksemplaride poolt hõivatud baitide arv erineb samuti täpselt sama andmeliikme puhul. Nagu järgmisel pildil näidatud, väärtustüübi eksemplari baitjadaKõik on välitöötajad, keda kasutatakse selle hoiustamiseks。 Viitetüüpide puhul salvestatakse vastava meetoditabeli aadress ka välja baitide jada ette. Meetoditabel annab peaaegu kogu tüübi kirjeldava metainfo ning me kasutame seda viidet, et määrata, millisesse tüüpi eksemplar kuulub. Kõige ees on ka lisabaitid, mida nimetameObjekti päisSeda kasutatakse mitte ainult objekti lukustatud oleku salvestamiseks, vaid ka räsi väärtust saab siin vahemällu salvestada. Kui loome viitetüübi muutuja, siis see muutujaSee ei näita esimest mälubaiti, mida instants hõivab, vaid kohta, kus metooditabeli aadress on salvestatud。
6. LDFLDA direktiiv
Nagu eespool tutvustasime, ei suuda staatilise tüübi Marshal/Unsafe poolt pakutav operaatori suuruse ja SizeOf meetod tegelikult lahendada baitide pikkuse arvutust, mida instantsid hõivavad. Minu teada ei saa seda probleemi lahendada ainult C# väljaga, kuid see on IL tasemelLdfldaJuhised aitavad meil seda probleemi lahendada. Nagu nimigi ütleb, tähendab Ldflda Load Field Address'i, mis aitab meil saada välja aadressi instantsis. Kuna sellel IL käsul puudub C# jaoks vastav API, saame seda kasutada ainult järgmises vormis IL Emiti abil.
Nagu ülaltoodud koodilõigus näidatud, on meil SizeCalculatori tüübis GenerateFieldAddressAccessor meetod, mis genereerib delegaadi tüübiga Func<object?, long[]> vastavalt määratud tüüpi väljade nimekirjale, mis aitab meil tagastada määratud objekti mäluaadressi ja kõigi selle väljade. Objekti enda aadressi ja iga välja aadressi abil saame loomulikult iga välja nihke ning seejärel hõlpsasti arvutada, kui palju baite mälu kogu eksemplars hõivab.
7. Arvuta väärtustüübi baitide arv
Kuna väärtustüüpidel ja viitetüüpidel on mälus erinevad paigutused, peame kasutama ka erinevaid arvutusi. Kuna struktuuri bait on kõigi mäluväljade sisu, kasutame nutikat meetodit selle arvutamiseks. Oletame, et peame arvutama tüübi T struktuuri baitide arvu, siis loome ValueTuple<T,T> tupli ja selle teise välja Item2 nihe on struktuuri T baitide arv. Spetsiifiline arvutusmeetod kajastub järgmises CalculateValueTypeInstance meetodis.
Nagu ülaltoodud koodilõigus näidatud, eeldades, et arvutamiseks vajalik struktuuritüüp on T, kutsume meetodi GetDefaultAsObject meetodiks, et saada vaikimisi objekt peegelduse kujul ja seejärel loome ValueTuple<T,T>tuple. Pärast GenerateFieldAddressAccessor meetodi kutsumist, et saada Func<object?, long[]> delegaat instantsi ja selle väljaaadresside arvutamiseks, kutsume seda delegaati argumendina. Kolme mäluaadressi puhul on koodituple ja väljade 1 ning 2 aadressid samad, kasutame kolmandat aadressi, mis esindab Item2 miinus esimene aadress, ja saame soovitud tulemuse.
8. Loe viitetüübi baitide arv
Baitide arvutamine viitetüüpide jaoks on keerulisem, kasutades seda ideed: pärast seda, kui saame eksemplari aadressi ja iga välja aadressi, sorteerime aadressid, et saada viimase välja nihe. Lisame selle nihke viimase välja baitide arvule ja seejärel lisame soovitud tulemusele vajaliku "esimese ja viimase baidi", mis kajastub järgmises CalculateReferneceTypeInstance meetodis.
Nagu ülaltoodud koodilõigus näidatud, kui määratud tüübil pole ühtegi välja määratletud, tagastab CalculateReferneceTypeInstance viitetüübi instantsi minimaalse baitide arvu: 3 korda aadressiosuti baitide arv. x86 arhitektuuride puhul võtab rakenduse tüüpi objekt vähemalt 12 baiti, sealhulgas ObjectHeader (4 baiti), meetodite tabeli viiteid (baite) ja vähemalt 4 baiti väljasisu (see 4 baiti on vajalik isegi siis, kui ühtegi tüüpi pole määratletud ilma väljadeta). x64 arhitektuuri puhul on see minimaalne baitide arv 24, sest meetodi tabeli osuti ja minimaalne väljasisu muutuvad 8 baidiks, kuigi ObjectHeaderi kehtiv sisu hõivab vaid 4 baiti, kuid ette lisatakse 4 baiti täitematerjali.
Viimase välja poolt hõivatud baitide arvestus on samuti väga lihtne: kui tüüp on väärtustüüp, siis kutsutakse arvutamiseks välja varem defineeritud CalculateValueTypeInstance meetod, kui see on viitetüüp, siis väljal salvestatud sisu on ainult sihtobjekti mäluaadress, seega pikkus on IntPtr.Size. Kuna viitetüübi instantsid on vaikimisi kooskõlastatud mälus IntPtr.Size'iga, tehakse seda ka siin. Lõpuks ära unusta, et viitetüübi instantsi viide ei viita mälu esimesele baidile, vaid baidile, mis hoiab metooditabeli osutit, seega tuleb lisada ObjecthHeaderi baitide arv (IntPtr.Size).
9. Täielik arvutus
Kaks meetodit, mida kasutatakse väärtustüübi ja viitetüübi baitide arvu arvutamiseks, kasutatakse järgmises SizeOf meetodis. Kuna Ldflda käsu kutse peab pakkuma vastavat eksemplari, annab see meetod delegaadi vastava eksemplari hankimiseks lisaks sihttüübile. Selle delegaadi parameetrid saab vaikimisi määrata ning kasutame vaikimisi väärtust selle väärtuse tüübi jaoks. Viitetüüpide puhul proovime luua sihtobjekti vaikimisi konstruktoriga. Kui seda delegeeritud objekti ei pakuta ja sihteksemplari ei saa luua, viskab SizeOf meetod erandi. Kuigi peame esitama sihteksemplari, on arvutatud tulemus seotud ainult tüübiga, seega salvestame arvutatud tulemuse vahemällu. Kutsumise lihtsuse huvides pakume ka teist üldist <T>SizeOf meetodit.
Allolevas koodilõigus kasutame seda kahe sama välja definitsiooniga struktuuri ja tüübi baitide arvu väljastamiseks. Järgmises artiklis saame täpsemalt teada instantsi täieliku binaarsisu mälus arvutatud baitide arvu põhjal, seega jääge lainel.
Originaallink:Hüperlingi sisselogimine on nähtav. |