Udostępnij za pośrednictwem


Typowe problemy przy migracji Visual C++ ARM

Ten sam kod źródłowy Visual C++ może wygenerować różne wyniki w ramach architektury ARM, niż na architekturach x 86 lub x 64.

Źródeł zagadnienia migracji

Wiele problemów, które można napotkać podczas migracji kodu z architekturach x 86 lub x 64 do architektury ARM odnoszą się do konstrukcji kodu źródłowego, które mogą wywołać zachowanie niezdefiniowane, zdefiniowane w implementacji lub nieokreślony.

  • Niezdefiniowany zachowanie
    Zachowanie, które nie definiuje C++ standard i powstał w wyniku operacji, która nie ma żadnego wyniku rozsądny — na przykład Konwertowanie liczb zmiennoprzecinkowych wartości na liczbę całkowitą bez znaku lub Przesunięcie wartość przez liczba pozycji, która jest liczbą ujemną lub przekracza liczbę bitów w jego typ promowane.

  • Zachowanie zdefiniowane w implementacji
    Zachowanie, które C++ standard wymaga dostawcy kompilatora określić i udokumentować.Program może polegać na zdefiniowane w implementacji zachowanie bezpiecznie, mimo że takie postępowanie więc może być przenośne.Przykładami zdefiniowane w implementacji zachowanie rozmiarów wbudowane typy danych i ich wymagania co do wyrównania.Przykładem operacji, która może mieć wpływ zdefiniowane w implementacji zachowanie uzyskuje dostęp do listy zmiennymi argumentami.

  • Zachowanie nieokreślony
    Zachowanie, które C++ standard pozostawia celowo deterministyczny.Chociaż zachowanie jest uważana za deterministyczny, określonego wywołania zachowań nieokreślony zależą od implementacji przez kompilator.Jednak nie jest wymagany dla dostawcy kompilator wcześniej określić wynik lub gwarancji spójne zachowanie pomiędzy wywołaniami porównywalne i nie jest wymagane dla dokumentacji.Przykładem nieokreślonego zachowanie jest porządek w sub wyrażenia, które — które zawierają argumentów do wywołania funkcji — są oceniane.

Inne zagadnienia migracji można przypisać różnice sprzętowe między ARM i architektur x 86 lub x 64, które współdziałają z C++ standard inaczej.Na przykład modelu pamięci silne architektury x 86 i x 64 daje volatile-kwalifikacje zmienne niektóre dodatkowe właściwości, które zostały wykorzystane w celu ułatwienia niektóre rodzaje komunikacji inter-thread w przeszłości.Ale model pamięci słabe architektura ARM nie obsługuje tego zastosowania ani C++ standard wymaga go.

Ważna uwagaWażne

Chociaż volatile zyski niektóre właściwości, które służą do zaimplementowania ograniczone form inter-thread komunikacji w x 86 i x 64, te dodatkowe właściwości nie są wystarczające do implementowania inter-thread komunikacji w ogóle.C++ standard zaleca się, że takiego zawiadomienia można implementować przy użyciu odpowiednich synchronizacyjne zamiast.

Ponieważ różnych platform może wyrazić tego rodzaju zachowanie inaczej, przenoszenie oprogramowania między platformami może być trudne i podatne błąd na Jeśli zależy od zachowania określonej platformy.Chociaż wiele z tych rodzajów zachowanie można zaobserwować i może pojawić się stabilna, opierając się na nich jest co najmniej nie są przenośne, a w przypadku zachowanie niezdefiniowane lub nieokreślona, jest również błąd.Nawet zachowanie jest cytowany w tym dokumencie nie powinno się powoływać na i może zostać zmienione w przyszłości kompilatory lub implementacjach Procesora.

Przykład zagadnienia migracji

Reszta niniejszego dokumentu opisuje, jak różne zachowanie tych elementów języka C++ może wygenerować różne wyniki na różnych platformach.

Konwersja zmiennoprzecinkowych na liczba całkowita bez znaku

W architekturze ARM konwersja wartość zmiennoprzecinkowa na 32-bitowa liczba całkowita nasyconych kwasów tłuszczowych do najbliższej wartości całkowitej mogącą reprezentować, jeśli wartość zmiennoprzecinkowa wykracza poza zakres, który może reprezentować wartości całkowitej.Na architekturach x 86 i x 64 konwersja otaczany Jeśli całkowitej jest podpisany lub jest ustawiona na-2147483648, jeśli liczba całkowita jest podpisany.Żaden z tych architektur obsługuje bezpośrednio konwertowania wartości zmiennoprzecinkowych do mniejszych typu integer; Zamiast tego zestawami na 32 bity, a wyniki są zaokrąglane do mniejszego rozmiaru.

Dla architektury ARM połączenie nasycenie i obcinania oznacza, że konwersja do typów niepodpisane poprawnie nasyconych mniejszych niepodpisane typów, gdy nasyconych kwasów tłuszczowych 32-bitowa liczba całkowita, ale produkuje obcięty wynik dla wartości, które są większe, niż może reprezentować typ mniejsze, ale za mały do nasycenia Pełna liczba całkowita 32-bitowych.Konwersja również nasyconych poprawnie dla 32-bitowe liczby całkowite z podpisanych, ale obcinania nasyconych, podpisane liczb całkowitych powoduje -1 dla wartości pozytywnie nasycone i 0 dla wartości negatywnie nasycone.Konwersja mniejszych całkowita produkuje obcięty wynik, który jest nieprzewidywalna.

Dla architektur x 86 i x 64 połączenie otoczone zachowanie podczas konwersji liczba całkowita bez znaku i jawne wyceny dla konwersji całkowita przy przepełnieniu, łącznie z obcinania, zrobić wyniki dla większości zmian roboczych nieprzewidywalne, jeśli są zbyt duże.

Tych platform różnią się także w jaki sposób obsługi konwersji NaN (nie nieliczbowych) do typu integer.Na ARM NaN konwertuje 0x00000000; x 86 i x 64 konwertuje 0x80000000.

Zmiennoprzecinkowe konwersji tylko można powoływać się, gdy wiadomo, że wartość jest w zasięgu typu Liczba całkowita, która jest konwertowana do.

Przesunięcie operatora (<< >>) zachowanie

W architekturze ARM wartość może być przesunięty left lub right do 255 bitów przed deseń zacznie być powtarzany.Na architekturach x 86 i x 64 wzorzec jest powtarzany przy każda wielokrotność 32 chyba, że źródło wzorka jest zmienną 64-bitowe; w takim przypadku wzór powtarza się w każda wielokrotność 64 na x 64 i każda wielokrotność 256 w architekturze x 86, w którym jest zatrudniony implementacji oprogramowania.Na przykład dla zmiennej 32-bitowe, który ma wartość 1, przesunięte w lewo na określonych stanowiskach 32, na ARM wynik jest równy 0, w architekturze x 86 wynikiem będzie liczba 1 i x 64 wynik jest również 1.Jednak jeśli źródłem wartości jest zmienna 64-bitowych, wynik na wszystkich platformach trzech jest 4294967296, a wartość nie "zawinięcia" do przesunęła pozycje 64 na x 64 lub 256 stanowiska w sprawie ARM i x 86.

Ponieważ wynik operacji przesunięcia, która przekracza liczbę bitów w typ źródła jest niezdefiniowana, kompilator nie musi mieć spójne zachowanie we wszystkich sytuacjach.Na przykład jeśli oba operandy zmiany są znane w czasie kompilacji, kompilator może zoptymalizować program za pomocą wewnętrznych rutynowych wcześniej obliczyć wynik zmiany i następnie zastępując wynik zamiast operacji przesunięcia.Jeżeli kwota shift jest za duży lub ujemne, wynik wewnętrzne procedury, może być inna niż wynik tego samego wyrażenia shift wykonywane przez Procesor.

Zmiennymi argumentami (varargs) zachowanie

W architekturze ARM parametrów z listy zmiennych argumentów, które są przekazywane na stosie podlegają wyrównanie.Na przykład 64-bitowy parametr jest wyrównany na granicy 64-bitowych.X 86 i x 64 argumenty, które są przekazywane na stosie nie podlegają wyrównanie i dodatkiem Service pack szczelnie.Różnica ta może spowodować, że funkcja zmiennych, takich jak printf odczytać adresów pamięci, które były przeznaczone jako wykładzina na ARM Jeśli oczekiwane układ listy zmiennymi argumentami nie pasuje dokładnie, chociaż może działać dla podzbioru niektórych wartości na architekturach x 86 lub x 64.Spójrzmy na następujący przykład:

// notice that a 64-bit integer is passed to the function, but '%d' is used to read it.
// on x86 and x64 this may work for small values because %d will “parse” the low-32 bits of the argument.
// on ARM the calling convention will align the 64-bit value and the code will print a random value
printf("%d\n", 1LL);   

W takim przypadku można ustalić błąd upewniając się, że specyfikacja poprawny format jest używany tak, aby uważane za wyrównanie argumentu.Ten kod jest poprawny:

// CORRECT: use %I64d for 64-bit integers
printf("%I64d\n", 1LL);

Kolejność uwzględniania argument

Ponieważ ARM, x 86 i x 64 procesory są tak różne, prezentują się różne wymagania implementacjami kompilatorów, a także różnych możliwości optymalizacji.W związku z tym wraz z innych czynników, takich jak ustawienia połączeń Konwencji i optymalizacji kompilator może ocenić argumenty funkcji w innej kolejności na różnych architektur lub zmianie innych czynników.Może to spowodować zachowanie aplikacji, które opiera się na kolejności szczególnej oceny nieoczekiwane zmienianie.

Tego rodzaju błąd może wystąpić, gdy argumenty do funkcji mieć żadnych efektów ubocznych, które mają wpływ inne argumenty funkcji do wywołania tej samej.Zazwyczaj ten rodzaj zależności jest łatwe do uniknięcia, ale może być zasłonięte czasami, zależności, które trudno jest rozpoznać lub przeciążanie operatora.Spójrzmy na następujący przykład kodu:

  handle memory_handle;

  memory_handle->acquire(*p);

Ten opis pojawia się wyraźnie określone, ale jeśli -> i * są przeciążonych operatorów, a następnie ten kod jest tłumaczony na coś, co przypomina to:

  Handle::acquire(operator->(memory_handle), operator*(p));

I jeśli istnieje zależność między operator->(memory_handle) i operator*(p), kod może być uzależnione od zamówienia szczególnej oceny, nawet jeśli oryginalny kod wygląda jak nie ma żadnych zależności możliwe.

lotne słowo kluczowe domyślne zachowanie

Kompilator Microsoft C++ obsługuje dwie różne interpretacje kwalifikator pamięci nietrwałej, przez użytkownika za pomocą przełączników kompilatora./volatile:ms Przełącznik / wybiera Microsoft extended semantykę lotnych, który gwarantuje silne zamawiania, jak miało to miejsce tradycyjnego dla x 86 i x 64 na kompilator Microsoft ze względu na model pamięci silne na tych architektur./volatile:iso Przełącznik / wybiera ścisłe C++ standardowych lotnych semantyka który nie daje gwarancji, silne zamawiania.

W architekturze ARM, wartością domyślną jest /volatile:iso ponieważ procesory ARM mają słabo zamówione modelu pamięci, a oprogramowanie ARM nie ma dziedzictwo opierając się na rozszerzonych semantykę /volatile:ms i zwykle nie ma łączące się z oprogramowania, które wykonuje.Jednak jest nadal czasami Wyobraźmy sobie nawet wymagane program ARM, aby używać rozszerzonego semantyka.Na przykład mogą być zbyt kosztowne, do portu program do obsługi semantyki ISO C++ lub oprogramowanie sterownika może okazać się stosować się do tradycyjnych semantykę do poprawnego działania.W takich przypadkach można użyć /volatile:ms przełączyć; Jednakże, aby odtworzyć tradycyjnych semantykę lotnych na cele ARM, kompilator należy wstawić bariery pamięci wokół każdego odczytu lub zapisu volatile do wymuszania silne zamawiania, zmienną, która może mieć negatywny wpływ na wydajność.

Na architekturach x 86 i x 64, wartością domyślną jest /volatile:ms ponieważ większość oprogramowania, które już istnieje dla tych architektur przy użyciu kompilatora Microsoft C++ opiera się na nich.Podczas kompilowania programów x 86 i x 64, można określić /volatile:iso switch w celu uniknięcia niepotrzebnego uzależnienia od tradycyjnych semantykę lotnych i wspieranie możliwości przenoszenia.

Zobacz też

Inne zasoby

Konfigurowanie programów pod kątem procesorów ARM (Visual C++)