2013-02-21 12 views
5

Pytanie specyficzne dla C++. Czytałem więc pytanie o to, co sprawia, że ​​program 32-bitowy/64-bitowy, i anwser, który dostał, był czymś podobnym (przepraszam, że nie mogę znaleźć pytania, byłem kiedyś temu spojrzałem na niego i nie mogę go znaleźć ponownie :(): Tak długo jak nie robisz żadnych "założeń wskaźnika", musisz tylko rekompilować go.To moje pytanie brzmi, jakie są założenia wskaźnika? Do mojego zrozumienia jest wskaźnik 32-bitowy i wskaźniki 64-bitowe, więc sądzę, że jest to coś wspólnego z Wszelkie inne dobre nawyki, o których należy pamiętać podczas pisania kodu, ułatwiające konwersję między nimi są również mile widziane :) prosimy o udostępnienie przykładów z nimiJak napisać kod zamienny, 32-bitowy/64-bitowy?

Ps . Wiem, że jest ten post: How do you write code that is both 32 bit and 64 bit compatible? , ale z trudem generalizowałem go bez dobrych przykładów, dla nowych programistów takich jak ja. Podobnie jak w przypadku jednostki pamięci 32-bitowej. Kinda przeskakuje, by rozbić go jeszcze bardziej (nie gra słów ^^) ds.

Odpowiedz

3

Dla dobrze uformowanego programu (to znaczy programu napisanego zgodnie ze składnią i regułami semantycznymi C++ bez niezdefiniowanego zachowania), standard C++ gwarantuje, że twój program będzie miał jeden z zestawu obserwowalnych zachowań. Obserwowalne zachowania różnią się ze względu na nieokreślone zachowanie (w tym zachowanie zdefiniowane przez implementację) w ramach programu. Jeśli unikniesz nieokreślonego zachowania lub go rozwiążesz, twój program będzie miał pewność, że ma określone i określone dane wyjściowe. Jeśli napiszesz swój program w ten sposób, nie zobaczysz żadnych różnic między twoim programem na maszynie 32-bitowej lub 64-bitowej.

prosty (wymuszona) Przykład programu, które mają różne możliwe wyniki są następujące:

int main() 
{ 
    std::cout << sizeof(void*) << std::endl; 
    return 0; 
} 

ten program będzie może mają różne wyjściowe na maszynach 32- i 64-bitowe (nie koniecznie). Wynik sizeof(void*) jest zdefiniowany przez implementację. Jednakże, jest to z pewnością można mieć program, który zawiera zachowanie implementacji zdefiniowane, ale jest zdecydowany być dobrze zdefiniowane:

int main() 
{ 
    int size = sizeof(void*); 
    if (size != 4) { 
    size = 4; 
    } 
    std::cout << size << std::endl; 
    return 0; 
} 

Program ten będzie zawsze wydrukować 4, pomimo faktu, że używa zachowanie implementacji zdefiniowane. To jest głupi przykład, ponieważ mogliśmy właśnie zrobić int size = 4;, ale zdarzają się przypadki, gdy pojawia się to w formie pisemnej kodu niezależnego od platformy.

Zatem zasadą pisania przenośnego kodu jest: celem uniknięcia lub rozwiązania nieokreślonego zachowania.

Oto kilka wskazówek dla uniknięcia nieokreśloną zachowanie:

  1. nie wiem nic o wielkości podstawowych typów poza to, co C++ standard określa przypuszczać. Oznacza to, że char ma co najmniej 8 bitów, zarówno short i int są co najmniej 16 bitów, i tak dalej.

  2. Nie próbuj robić magii wskaźnika (rzutowanie między typami wskaźników lub przechowywanie wskaźników w typach całkowych).

  3. Nie należy używać unsigned char* do odczytu reprezentacji wartości obiektu innego niż char (w przypadku serializacji lub powiązanych zadań).

  4. Należy unikać reinterpret_cast.

  5. Należy zachować ostrożność podczas wykonywania operacji, które mogą być zawyżone lub niedostateczne. Zastanów się dobrze, wykonując operacje bit-shift.

  6. Zachowaj ostrożność podczas wykonywania obliczeń arytmetycznych na wskaźnikach.

  7. Nie używaj void*.

Istnieje o wiele więcej wystąpień o nieokreślonym lub niezdefiniowanym zachowaniu w standardzie. Warto je sprawdzić. Dostępne są online some great articles, które obejmują niektóre bardziej typowe różnice między platformami 32- i 64-bitowymi.

+0

awsome anwser :) właśnie tego szukałem :) –

+0

@FredrikBostonWestman to powinieneś to zaakceptować! – gsamaras

3

"Założenia wskaźnika" dotyczy pisania kodu opartego na wskaźnikach pasujących do innych typów danych, np. int copy_of_pointer = ptr; - jeśli int jest typem 32-bitowym, kod ten zostanie złamany na maszynach 64-bitowych, ponieważ tylko część wskaźnika zostanie zapisana.

Dopóki wskaźniki są przechowywane tylko w typach wskaźników, nie powinny stanowić problemu.

Zazwyczaj wskaźniki są wielkością "słowa maszynowego", więc w architekturze 32-bitowej, 32-bitowej i architekturze 64-bitowej wszystkie wskaźniki są 64-bitowe. Istnieją jednak NIEKTÓRE architektury, w których nie jest to prawdą. Nigdy nie pracowałem na takich komputerach [poza x86 z jego wskaźnikami "daleko" i "blisko" - ale teraz zignorujmy to].

Większość kompilatorów powie ci, kiedy konwertujesz wskaźniki na liczby całkowite, do których nie pasuje wskaźnik, więc jeśli włączysz ostrzeżenia, NAJBARDZIEJ z problemów stanie się oczywistych - napraw ostrzeżenia, a szanse są całkiem przyzwoite, że twój kod będzie działać od razu.

1

Nie będzie różnicy między kodem 32-bitowym a 64-bitowym, celem C/C++ i innych języków programowania jest ich przenośność zamiast języka asemblera.

Jedyną różnicą będzie dystrybucja, w której skompilujesz swój kod, cała praca jest wykonywana automatycznie przez twój kompilator/linker, więc nie myśl o tym.

Ale: jeśli programujesz w dystrybucji 64-bitowej i musisz użyć biblioteki zewnętrznej, na przykład SDL, biblioteka zewnętrzna będzie musiała być również skompilowana w wersji 64-bitowej, jeśli chcesz skompilować swój kod.

Jedno jest pewne, że plik ELF będzie większy na 64-bitowej dystrybucji niż na 32-bitowej, to tylko logika.

Jaki jest sens wskaźnika? podczas zwiększania/zmieniania wskaźnika kompilator zwiększa wskaźnik od rozmiaru typu wskazującego.

Zawarty typ rozmiaru jest określany przez rozmiar rejestru procesora/dystrybucję pracy.

Ale nie musisz się tym przejmować, kompilacja zrobi wszystko za Ciebie.

Suma: Z tego powodu nie można wykonać 64-bitowego pliku ELF w dystrybucji 32-bitowej.

+0

kiedy mówisz disrib you meen? –

+1

@FredrikBostonWestman: ta odpowiedź zakłada nowoczesny system operacyjny oparty na systemie Linux/BSD. "Wdrożenie" takiego systemu operacyjnego nazywa się dystrybucją; są zaprojektowane jako stabilny kompletny pakiet, w którym wszystko jest specjalnie skompilowane dla architektury, na której będzie działać. Trudno uzyskać takie korzyści dzięki technologii z zamkniętym źródłem, oczywiście ... – leftaroundabout

6

Ogólnie oznacza, że ​​zachowanie programu nigdy nie powinno zależeć od sizeof() żadnego typu (które nie ma określonego rozmiaru), ani jawnie, ani niejawnie (dotyczy to również możliwych dopasowań strukturalnych).

Wskaźniki to tylko ich podzbiór i prawdopodobnie oznacza to również, że nie należy polegać na możliwości konwersji między niepowiązanymi typami wskaźników i/lub liczbami całkowitymi, chyba że są specjalnie do tego stworzone (np. intptr_t).

W ten sam sposób musisz zadbać o rzeczy zapisane na dysku, gdzie nie powinieneś nigdy polegać na rozmiarze np. typy wbudowane, wszędzie są takie same.

Ilekroć musisz (z powodu np. Zewnętrznych formatów danych) używaj typów o wyraźnie określonych rozmiarach, takich jak uint32_t.

1

Typowe pułapki na 32-bitowy/64-bitowego przenoszenia są:

przyjętym założeniem przez programatora sizeof (void *) == 4 * sizeof (znaki). Jeśli robisz to założenie i np. przydzielaj tablice w ten sposób ("potrzebuję 20 wskaźników, więc przydzielę 80 bajtów"), twój kod zostanie złamany na 64-bitach, ponieważ spowoduje to przepełnienie bufora.

"Kociak-zabójca", int x = (int) i coś; (i odwrotnie, void * ptr = (void *) some_int). Ponownie założenie sizeof (int) == sizeof (void *). Nie powoduje to przepełnienia, ale traci dane - wyższy 32-bitowy wskaźnik, a mianowicie.

Oba te problemy należą do klasy zwanej aliasingiem typu (zakładając tożsamość/wymienność/równoważność na poziomie reprezentacji binarnej między dwoma typami), a takie założenia są wspólne; jak na UN * X, zakładając time_t, size_t, off_t being int, lub na Windows, HANDLE, void * i long being wymienne, itd ...

Założenia dotyczące struktury danych/wykorzystania miejsca stosu (patrz 5. poniżej jako dobrze). W kodzie C/C++ zmienne lokalne są przydzielane na stosie, a używana tam przestrzeń różni się między trybem 32-bitowym a 64-bitowym ze względu na poniższy punkt, a także ze względu na różne reguły przekazywania argumentów (32-bitowy x86 zwykle na stosie, 64-bitowy x86 w części w rejestrach). Kod, który prawie całkowicie ucieka z domyślnym pakietem 32-bitowym, może spowodować awarię przepełnienia stosu na 64-bitach. Jest to stosunkowo łatwe do wykrycia jako przyczyna awarii, ale w zależności od konfiguracji aplikacji trudne do naprawienia.

Różnice w czasie pomiędzy kodem 32-bitowym a 64-bitowym (ze względu na różne rozmiary kodu/ślady posterów w pamięci podręcznej lub różne charakterystyki dostępu do pamięci/różne konwencje wywoływania) mogą przerwać "kalibrację". Powiedz, dla (int i = 0; i < 1000000; ++ i) uśpienia (0); Prawdopodobnie będzie miał różne czasy dla 32bit i 64bit ...

Wreszcie, ABI (interfejs binarny aplikacji). Zwykle istnieją większe różnice między środowiskami 64-bitowymi a 32-bitowymi niż rozmiar wskaźników ... Obecnie istnieją dwie główne "gałęzie" środowisk 64-bitowych, IL32P64 (co używa Win64 - int i long to int32_t, tylko uintptr_t/void * to uint64_t, rozmawiając w kategoriach liczb całkowitych od) i LP64 (jakiego UN * X używa - int to int32_t, long to int64_t, a uintptr_t/void * to uint64_t), ale są też "podziały" różnych reguł wyrównania - niektóre środowiska zakładają długie, zmienne lub podwójne wyrównanie w odpowiednich rozmiarach, podczas gdy inne przyjmują, że są wyrównane w wielokrotnościach czterech bajtów. W 32-bitowym Linuksie wyrównują wszystko do czterech bajtów, podczas gdy w 64-bitowym Linuksie liczba zmiennoprzecinkowa wynosi cztery, długie i podwójne w ośmio-bajtowych wielokrotnościach. Konsekwencją tych reguł jest to, że w wielu przypadkach rozmiar bitu sizeof (struct {...}) i przesunięcie elementów struktury/klasy są różne w środowiskach 32-bitowych i 64-bitowych, nawet jeśli deklaracja typu danych jest całkowicie identyczna. Oprócz wpływu na alokację macierzy/wektorów, problemy te dotyczą również danych w/danych wyjściowych, np. poprzez pliki - jeśli aplikacja 32-bitowa pisze np. struct {char a; int b; char c, long d; podwójny e} do pliku, który została przekompilowana do 64-bitowej aplikacji, wynik nie będzie tym, na co mamy nadzieję. Podane przykłady dotyczą tylko prymitywów języka (char, int, long itp.), Ale oczywiście wpływają na wszelkiego rodzaju typy danych bibliotek zależnych od platformy/środowiska wykonawczego, czy size_t, off_t, time_t, HANDLE, w zasadzie jakiekolwiek nietrywialne struct/union/class ... - więc miejsce na błędy jest tutaj duże,

A ponadto istnieją różnice niższego poziomu, które wchodzą w grę, np. do montażu zoptymalizowanego ręcznie (SSE/SSE2/...); 32-bitowe i 64-bitowe mają różne (liczby) rejestry, różne reguły przekazywania argumentów; to wszystko mocno wpływa na skuteczność takich optymalizacji i jest bardzo prawdopodobne, że np. Kod SSE2 zapewniający najlepszą wydajność w trybie 32-bitowym będzie musiał zostać przepisany/musi zostać ulepszony, aby zapewnić najlepszą wydajność w trybie 64-bitowym.

Istnieją również ograniczenia projektowania kodu, które są bardzo różne w przypadku wersji 32-bitowych i 64-bitowych, w szczególności w zakresie przydzielania/zarządzania pamięcią; aplikacja, która została starannie zakodowana, aby "zmaksymalizować cholerę z memu, który może uzyskać w 32-bitach", będzie miała złożoną logikę, jak/kiedy przydzielić/zwolnić pamięć, wykorzystanie pamięci mapowane, wewnętrzne buforowanie itp. - z których wiele będzie być szkodliwe na 64-bitach, gdzie można "po prostu" skorzystać z ogromnej dostępnej przestrzeni adresowej. Taka aplikacja mogłaby rekompilować się na 64-bitową wersję, ale działa tam gorzej niż jakaś "prastara, prosta, przestarzała wersja", która nie ma optymalizacji pełno-32-bitowych optymalizacji.

Tak więc, w ostatecznym rozrachunku chodzi również o ulepszenia/zyski, a to oznacza więcej pracy, częściowo w programowaniu, częściowo w projektowaniu/wymaganiach. Nawet jeśli twoja aplikacja czysto rekompiluje się zarówno w środowiskach 32-bitowych, jak i 64-bitowych i jest weryfikowana na obu komputerach. , czy faktycznie korzysta z 64-bitów? Czy są jakieś zmiany, które można/należy wprowadzić w logice kodu, aby sprawić, by działał on szybciej/działał szybciej w wersji 64-bitowej? Czy możesz dokonać tych zmian bez łamania kompatybilności wstecznej 32 bitów? Bez negatywnego wpływu na cel 32-bitowy? Gdzie będą ulepszenia i ile możesz zyskać? W przypadku dużego projektu komercyjnego odpowiedzi na te pytania są często ważnymi markami na mapie drogowej, ponieważ punktem wyjścia jest jakiś istniejący "producent pieniądza" ...

+1

godne obejrzenia pod adresem http://www.viva64.com/en/a/0004/ – Saqlain

Powiązane problemy