2016-09-05 15 views
5

Jakiś czas temu stworzyłem prosty komputer symulowany. Miał peryferia, bufor ekranu, który mógł być renderowany do tekstury OpenGL, oraz kilka innych fajnych funkcji. Działa, działa dobrze i ogólnie jestem z tego całkiem zadowolony.Bezpieczny, skuteczny typ danych podstawowych dla prostej maszyny wirtualnej

Z wyjątkiem, oszukałem.

Podstawowy typ danych to połączenie liczby całkowitej, zmiennoprzecinkowej i typu instrukcji (podzielone na pola bitowe).

Dla każdego poprawnego (symulowanego) programu, związek zawsze jest używany bezpiecznie, tylko czytając od ostatniego członka związku zapisanego w. Jednak potencjał, że źle uformowane programu (np ładowane z symulowanym twardym dysku) może uzyskać dostęp do członków out of order może narazić mnie na zwykłych problemów związanych z nadużywaniem UNION:

  • możliwość, że zapis może być zoptymalizowane w czasie kompilacji - kompilator nie może mieć wystarczającej ilości informacji, aby próbować tej optymalizacji.
  • Wartość odczytana z unii może być śmieciem - jest to całkowicie dopuszczalne zachowanie dla mnie.
  • Odczytany w ten sposób wskaźnik może być wartością sygnalizującą-NaN/pułapkę - jest to poważny problem - awarie symulowanego komputera są w porządku, ale awarie programu są katastrofalne.
  • To jest technicznie niezdefiniowane zachowanie, więc chociaż prawdopodobnie nie będzie, może ustawić komputer w ogniu, usunąć dysk twardy lub przywołać Cthulhu.

Solutions rozważyć:

  • Kłucie z unii - Może to wystarczająco dobrze zdefiniowane dla wszystkich platform świata rzeczywistego? Być może są sposoby na dezynfekcję sNaNs?
  • Oznaczone połączenie - skutecznie zmniejszy ilość pamięci w połowie
  • Oddzielnie przechowywana tablica efektywnie zapakowanych tagów - trochę skrzypliwie propaguje znacznik, ale poza tym jest nieco żywotna.
  • tablica znaków - wydaje się prosta, ale koszty zrobienia tego bezpiecznie, pozwalając na odczyt z innego rodzaju niż ten, który został napisany, naprawdę sumują się.
  • Typ całkowity - jak wyżej w przypadku danych zmiennoprzecinkowych i instrukcji, z tą różnicą, że liczby całkowite są trywialne.
  • tablica znaków plus oddzielne rejestry liczb całkowitych i zmiennoprzecinkowych - charakterystyczne i pod wieloma względami idealne, ale wymagałoby ode mnie napisania kompilatora, który mógłby z nich efektywnie korzystać.

Wyobrażam sobie, że jest to projekt, który wielu użytkowników SO podjęło lub nie podjęło, dlatego szczególnie mile widziane są specyficzne problemy.

+0

Co rozumiesz przez "podstawowy typ danych"? Podstawowy typ danych rzeczywistej maszyny to 99,9999% czasu uint8_t – James

+0

@James - right, aw moim kompilatorze uint8_t to tylko typedef do unsigned char, który jest jedną z opcji, które rozważam. Ale nawet wtedy większość maszyn wykonuje operacje arytmetyczne na operandach 32- lub 64-bitowych. – DeveloperInDevelopment

+0

Możesz napisać swoją maszynę wirtualną w C. C umożliwia czytanie ze związków w znacznie bardziej zrelaksowany sposób niż w C++. –

Odpowiedz

3

Jeśli twój kompilator obsługuje to, możesz użyć C++ 17 std::variant (na podstawie boost::variant).


Edit: Na co najwyżej o niewielkich gabarytach, opt-in bezpieczeństwa typu, można zrobić coś wzdłuż linii

union Word { int32_t i; float f; Instruction inst; }; 

namespace MemAccess 
{ 
     static std::bitset<MEM_SIZE> int32_whitelist, 
            float_whitelist, 
            inst_whitelist; 
     static std::array<Word, MEM_SIZE> memory; 
     // set or reinterpret as int32 
     int32_t & 
     int32_at(const size_t at) 
     { 
       int32_whitelist[at] = 1; 
       float_whitelist[at] = inst_whitelist[at] = 0; 

       return memory[at].i; 
     } 
     // interpret as int32 only if whitelisted 
     int32_t & 
     int32_checked(const size_t at) 
     { 
       if (int32_whitelist[at]) 
       { 
         return memory[at].i; 
       } 
       else 
       { 
         throw; 
       } 
     } 
     // equivalent functions for floats and instructions 
} 

Edit 2: dotarło do mnie to może być również wykonane z jednego bitset .

static std::array<Word, MEM_SIZE> memory; 
static std::bitset<MEM_SIZE * 2> whitelist; 

float & 
float_at(const size_t at) 
{  // None = 00, Inst = 10, Int32 = 11 
     whitelist[at * 2]  = 0; 
     whitelist[at * 2 + 1] = 1; 

     return memory[at].f; 
} 

float & 
float_checked(const size_t at) 
{ 
     if (!whitelist[at * 2] && whitelist[at * 2 + 1]) 
     { 
       return memory[at].f; 
     } 

     throw; 
} 
+0

To była interesująca lektura, a w bardziej ogólnym kontekście wygląda jak miła alternatywa dla związku i zapobiegnie potencjalnie niebezpiecznemu użyciu. Jednak pod maską (przynajmniej w wersji boost) oprócz członka "storage_" znajduje się element "which_", w którym znajduje się int/float/etc, a więc ma takie same problemy z wydajnością pamięci jak tagged union . Zdecydowanie przydatnym narzędziem, o którym należy pamiętać. – DeveloperInDevelopment

+0

@DeveloperInDevelopment, jeśli oszczędność miejsca jest bardzo ważna, można użyć bitsets jako białych list, tak jak dodałem do mojej odpowiedzi –

+0

Byłem skłonny do czegoś bardzo podobnego do tego i jest to ładne, czyste wdrożenie. Pierwotnie zamierzano spakować 4 x 2-bitowe znaczniki do każdego bajtu tablicy znaków, ale wygląda to znacznie ładniej. Patrząc na to, dwa zestawy bitowe działałyby jeszcze lepiej. Zostawi to trochę czasu, aby sprawdzić, czy są jakieś inne odpowiedzi, ale wygląda to jak rozwiązanie, z którym mogę żyć. – DeveloperInDevelopment

Powiązane problemy