-1-es címről akar olvasni a Visual C++ 64 bites módban

-1-es címről akar olvasni a Visual C++ 64 bites módban
2020-12-31T14:48:00+01:00
2021-06-23T15:33:25+02:00
2022-12-06T20:25:38+01:00
x00
Tisztelt tagság!

Visual C++ 2019 Community 16.8.3
64 bites Windows 10 Home 2004 teljesen befrissítve

Van egy sokat tudó AVL-fákat használó programom, mely 32 biten mindenhogy megy. 64 biten debug módban megy, release módban a -1-es címről (0xFFFF...) akar olvasni az AVL-fa rutin, 0xCCCC... címeket is látok ami definiálatlant jelent. Ha * helyett * __ptr32 van, akkor 32 bitesek a címek, ekkor debug módban se megy, még hamarabb jelentkezik a hiba, ekkor is -1-es címről akar olvasni. A fordítóprogramban lenne a hiba? Találtam már egyébként pár hibát a fordítóprogramban. Mivel hosszú, még nem készítettem el a legrövidebb, legegyszerűbb változatot amiben jelentkezik a hiba, pár 10 000 soros a forráskód. Egyébként natív unmanaged parancssoros program, a projekt fájl közel 20 éves, többször átkonvertálta, így nincs benne még MASM se, új projekt fájlt kellett létrehoznom hogy be tudjam állítani hogy a routine.asm MASM-mal fordul, nem marad ki. Azokat az AVL-rutinok nem használják.

Na úgy néz ki nemsokára lesz egy rövid program amiben jelentkezik a hiba. int-eket tartalmazó AVL-fába 0-t betettem, utána a -1-et már nem tudta betenni, 0xFFF....FFF3-as címről akar olvasni. Én továbbra is a fordítóprogramra gyanakszok. Ennél 32 bites címeket használok 64 bites programnál. Nyilván a linkelésnél beállítottam hogy csak az alsó 2 GB-ot használja. Bonyolultabbnál 64 bites címeknél is jelentkezik a hiba, de csak release módban. Ez a fura, hogy 32 bites programnál mindig megy, ezért gyanakszok fordítóprogram-hibára.
Mutasd a teljes hozzászólást!
Az lehet egyébként, hogy a __ptr32 és az & (address of) operátor nem úgy működnek együtt, mint ahogyan azt gondolnád: https://developercommunity.visualstudio.com/content/problem/620885/-..
Mutasd a teljes hozzászólást!

  • de az 'i' változó az ugye 'p' akart lenni?

    Nem, az a beszúrandó fapont, p meg lemegy a gyökértől a levélig a keresőúton a bináris keresőfában, hogy megkeresse a beszúrandó elem helyét.

    Meg van oldva egyébként, ahogy írtam. Alapértelmezetten biztos előjel-kiterjesztetten megy, de szabályozható: __ptr32 __sptr, __ptr32 __uptr. Amúgy meg az alsó 2 GB-on tök mindegy. A hiba az volt, hogy ha p egy 32 bites mutató: * __ptr32 típusú, akkor referencia kezdőértéke ha *p: például int &r = *p, beleértve rutinnak paraméterátadást, akkor 8 byte-ot olvas ki a memóriából, így a mögötte levő, másik változóban levő 4 byte lesz a 64 bites cím felső fele. A debugger se tudja a 32 bites címeket 64 biten.

    Nincs szándékosan rossz program, a linkernek megadtam hogy minden az alsó 2 GB-ban legyen.

    A másik hiba az operator <-ben a rendezési kulcs hasonlításában volt, biztos címtől függ hogy előjön-e, a saját assembly rutinomban kellett lennie, nem kerestem meg, C++ kódot írtam helyette.

    A változó azért nem volt inicializálva, mert nem volt rá szükség! Minél egyszerűbb kódot próbáltam csinálni, így nem inicializáltam, mert nem volt rá szükség: tök mindegy, hogy a < mit ad rá. Abban a pár soros példakódban, nem a programomban amit fejlesztek!
    Mutasd a teljes hozzászólást!
  • Szivesen!

    A hiba, amiről itt szó van, nem teljesen ugyanaz, mint, amit linkeltem. Azt csak azért küldtem, hogy mutassam, van más baj is a __ptr32-vel egyébként. Ott csak az addressof operátorral volt probléna, de itt a referenciák is rosszul "működnek". Akár be is jelentheted a hibát, ha gondolod.
    Mutasd a teljes hozzászólást!
  • Kíváncsi vagyok, mi lesz belőle.
    Mutasd a teljes hozzászólást!
  • Szerintem kicsit félreértetted. Szerintem itt nincs szó sem az 'operator&' bug-járól, sem semmilyen más fordító bug-ról.

    Van egy függvényed, amit referencia szerint kapja a paramétert (ennek nincs köze az 'operator&'-hoz.) 64-bites környezetben ez a függvényed 64bit-es címet fog várni.
    Amikor te ezt a függvényt meghívod egy 32-bites pointerrel, akkor valószínűleg (compiler warning-ok mellett) implicit 64-bites címmé alakítja neked. A konverzió sign-extension-el történik. Ez azt jelenti, hogy ha az MSB bit '1', akkor csupa 1-essel egészíti ki a felső 32 bitet és nagyon rossz helyen próbál olvasni a programod.. Ha az MSB bit '0', akkor szerencséd van és működni fog.

    Nem teszteltem a kódod, de nekem nagyon erős a gyanúm, hogy ennyi a problémád.
    Mutasd a teljes hozzászólást!
  • The program reads 64 bit address instead of 32 bit address, so the upper 32 bit

    Csináltál disassembly-t, hogy mivé fordul a programod?
    Szerintem signed-extension instruction-al fogsz találkozni és nem adott címről történő 64bit olvasással..
    Mutasd a teljes hozzászólást!
  • Nézd meg jobban, mi történik a mellékelt kódban, a pointer melletti változó értéke bekerül a címbe, ahonnan olvasni próbál...
    Mutasd a teljes hozzászólást!
  • Mondjuk Assembly source-t mindenképpen kellene nézni, mert anélkül vakrepülés az egész. (Ha linux lenne, akkor objdump -dS valami.o lenne a módja, Windows-ban nem tudom.)
    Mutasd a teljes hozzászólást!
  • A címek 1-32 MB közöttiek. A Linkernek megadtam hogy az alsó 2 GB-ot használja. Ekkor a felső bit mennyi? Véletlenül se fixen 0? És annak előjel-kiterjesztése mennyi?

    Egyébként ehhez disassembly felesleges: elég lenne f-ben kiíratni a paraméter címét a képernyőre: &z mondjuk, meg előtte p[0], p[1]-et. Illetve ha f z-t nem írja ki, csak a címét, akkor ki se akad, így utána is ki lehet íratni p[0], p[1]-et.
    Mutasd a teljes hozzászólást!
  • A forráskód alapján ezt nem tudom megmondani, tesztelni nem teszteltem, de ha lesz időm, akkor ránézek, hogy mire fordul (igaz nincs visual c++-om..)
    Mutasd a teljes hozzászólást!
  • A Linkernek megadtam hogy az alsó 2 GB-ot használja

    Akkor valószínűleg valóban 64bites címet olvas fel. Nézd meg, hogy mivé fordul a kód..

    Egyébként ehhez disassembly felesleges

    Elég egyszerű és gyors módja annak ellenőrzésére, hogy mi is történik. Gondolom visual studio is tud olyan kimenetet generálni, ahol konkrétan adott sorokhoz rendeli a generált asm kódot..
    Mutasd a teljes hozzászólást!
  • 1: include <iostream>
         2: 
         3: using namespace std;
         4: 
         5: int x = 0, y = 1;
         6: int * __ptr32 (a[2]) = {&x, &y};
         7: void f(int const &z) { wcout << z; }
         8: 
         9: int wmain() {
    0000000000C01000  sub         rsp,28h  
        10: f(*(a[0]));
    0000000000C01004  mov         rax,qword ptr [a (0C20210h)]  
    0000000000C0100B  mov         rcx,qword ptr [__imp_std::wcout (0C20088h)]  
    0000000000C01012  mov         edx,dword ptr [rax]  
    0000000000C01014  call        qword ptr [__imp_std::basic_ostream<wchar_t,std::char_traits<wchar_t> >::operator<< (0C20080h)]  
        11: return 0;
    0000000000C0101A  xor         eax,eax  
        12: }
    0000000000C0101C  add         rsp,28h  
    0000000000C01020  ret

    Írtam ugye hogy csak release módban jön elő. Inline-olja f-et. A 10. sorban valóban 8 byte-ot tölt be rax-ba, ez a hiba. Utána a wcout-ot tölti be, mint az operator << egyik paramétere, majd a kiírandó adatot, ami tényleg [rax], és valóban 4 byte-os, de előtte rax-ba 8 byte-os címet tett. Utána meghívja operator <<-t, majd return 0. Az lenne az igazi, ha fordítható .asm fájlt tudna csinálni a C++ fordító. Elképzelhető hogy tudja. Disassemblyben csak az elakadástól mutatta, vissza menni nem lehetett, vagyis csak az edx = * (int *) rax sortól. Ezért leállítottam a debuggolást, és 1 step into után rögtön disassembly.

    A hiba után megnéztem a regisztereket is: tényleg 2 cím van benne: a felső 32 bites felében is van egy cím.
    Mutasd a teljes hozzászólást!
  • Na az előjelesek kedvéért:

    A mutató hosszbeállítása mezőeltolásra nem alkalmazható, vagyis aminél &osztály::típus a típus, &osztály::mezőnév a konstans. .* illetve ->* a használata. A wcout rá mindig 1-et ír ki, így int-re kell konvertálni hogy értelmes legyen, illetve 64 bites módban lehet sizeof_t is, bár a fordító nem hiszem hogy megengedne 2 GB-ot elérő osztályt.

    A mutatók mérete mindig annyi amennyit megadtunk: lehet 32 bites módban is 64 bites mutatót használni. Alapértelmezetten előjelesen terjeszti ki, de ez __sptr, __uptr-rel beállítható:
    void * __ptr32 __uptr p = (void *) -1;
    esetén például nem előjel-kiterjesztett lesz. Ha __ptrszám nélkül van az __sptr, __uptr, akkor a mutató mérete marad, csak a kiterjesztési módot állítja be: 64 bites mutatóknál nyilván ez felesleges. A wcout sose veszi figyelembe hogy milyen mutató, hanem 32 bites módban 8 jegyű, 64 bites módban 16 jegyű számot ír ki. Ez amiatt lehet, hogy ugyanannak a típusnak tekinti: nem lehet például void f(int *); void f(int * __ptr32), mert ezt ekkor már 1 típusnak tekinti! Gondolom 64 biten valójában 64 bitet kap, * __ptr32 típusú mutatónál is. Ha például sablonparaméter, akkor persze az osztály ilyen típusú mezője tényleg 4 byte-os lesz, nem 8. Az alapértelmezett kiterjesztésbe az is beletartozik, ha 32 bites módban használunk __ptr64-et: ekkor csak az alsó 32 bitjét fogja címzéshez használni, de 64 biten tárolja. Függvényparaméternél nem próbáltam.
    Mutasd a teljes hozzászólást!
  • Majd ha elkészültél egy működő implementációval feltölthetnél egy benchmark-ot a 64 és 32 bites verzió összehasonlításáról.
    Mutasd a teljes hozzászólást!
  • 16.10.2. Hát nem kapkodták el, de végre valahára megcsinálták! Mostmár tud 32 bites mutatót kezelni 64 bites módban.
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd