2013-04-15 23 views
7

TłoJak piszesz (przenośnie) odwrotną kolejność bajtów sieciowych?

Przy projektowaniu binarnych formatów plików, to zazwyczaj zaleca się pisać liczby całkowite w sieciowej kolejności bajtów. W tym celu dostępne są makra, takie jak htonhl(). Ale w przypadku formatu takiego jak WAV używany jest mały format endianów.

Pytanie

Jak przenośnie napisać małe wartości endian, niezależnie od tego, czy procesor pracuje na Twój kod jest duży endian lub little endian architekturę? (Pomysły: Można standardowe makra ntohl() i htonl() być używane „w drugą stronę” w jakiś sposób powinno kod tylko Runtime testu, jeśli jest uruchomiony na małym lub dużym endian CPU i wybrać odpowiednią ścieżkę kodu?)

Tak pytanie nie dotyczy formatów plików, format plików był tylko przykładem. Może to być wszelkiego rodzaju serializacja, w której wymagany jest mały endian "na drucie", taki jak protokół sieciowy (heretycki).

+0

Masz trzy pytania tam, ale tylko dwa wyróżniać. –

+0

@ user2096041, przeprojektowany, czy jest jaśniejszy? –

+0

Aby znaleźć endianness systemu hosta, można kliknąć ten link: http://stackoverflow.com/questions/1001307/detecting-endianness-programmatically-in-ac-programu –

Odpowiedz

16

C zapewnia już abstrakcją bajt gospodarza u: number i nóż; lub int & sztylet ;.

wytwarza kopii w danym bajt można zrobić przenośnie, nie stara się być mądry: po prostu interpretować numery jak numery i użycie nieco przesuwa wyodrębnić każdy bajt:

uint32_t value; 
uint8_t lolo = (value >> 0) & 0xFF; 
uint8_t lohi = (value >> 8) & 0xFF; 
uint8_t hilo = (value >> 16) & 0xFF; 
uint8_t hihi = (value >> 24) & 0xFF; 

Następnie wystarczy napisać bajtów w dowolnej kolejności.

Podczas robienia sekwencji bajtów z pewnym endianness jako wejście, można odtworzyć je w endianness gospodarza ponownie konstruowania liczb z operacji bitowych:

uint32_t value = (hihi << 24) 
       | (hilo << 16) 
       | (lohi << 8) 
       | (lolo << 0); 

& sztylet; Tylko reprezentacje liczb jako sekwencji bajtów mają endianię; liczby (tj. ilości) nie.

+2

'hihi',' lolo', te to najlepsze imiona, jakie widziałem. Co masz na myśli, mówiąc o ilościach? –

+0

@AmigableClarkKant abstrakcyjny pomysł liczby, w przeciwieństwie do jej reprezentacji. –

+0

OK, może powinieneś link do jakiegoś rodzaju C++ zamiast numeru. W każdym razie, jak to pomaga z serializacją i endianness? Rozumiem (i zatwierdzam resztę odpowiedzi). –

2

W rzeczywistości, funkcje MSDN ntohl() i htonl() odwrotność siebie:

Funkcja htonl przekształca u_long od gospodarza sieciowych/IP bajt celu TCP (co jest big-endian).

Funkcja ntohl zamienia u_long z kolejki sieci TCP/IP na hosta kolejność bajtów (która jest mało-endianowa na procesorach Intela).

Tak, runtime detecting endianness jest bardzo rozsądny rzeczą do zrobienia, a przede wszystkim to, co każdy gotowy do użycia makro/funkcja będzie zrobić w pewnym momencie tak.

Jeśli chcesz zrobić małe, duże konwersje endianinowe, zobacz odpowiedź @ R-Martinho-Fernandes.

+2

Nie zgadzam się z tym, że wykrycie endianizmu jest bardzo rozsądną rzeczą. Zwykle nie powinieneś o tym wiedzieć. O ile nie piszesz kodu krytycznego pod względem wydajności, pisanie kodu niezależnego od endianów jest lepszym pomysłem. – jamesdlin

+1

@jamesdlin podczas pisania procesu wieloplatformowego, ten sam kod może działać na sprzęcie Little-Endian lub Big-Endian. Gdy proces komunikuje się za pośrednictwem sieci lub plików, chcesz, aby rozmawiali w tym samym języku. Kod może sprawdzić endianię w czasie wykonywania i odpowiednio dostosować proces wyjściowy/wejściowy. –

+0

To nie ma sensu. Jeśli piszesz odpowiedni kod agnostyczny dla endianów, twój kod nadal jest wieloplatformowy i nie musi wykonywać żadnych kontroli środowiska wykonawczego (lub kompilacji). Na przykład, jeśli robisz: 'fputc ((x >> 24) & 0xFF, fp)' gdzie 'x' jest 32-bitową liczbą całkowitą, zawsze będziesz zapisywać najbardziej znaczący bajt niezależnie od platformy. Zobacz odpowiedź R. Martinho Fernandesa. – jamesdlin

4

Oto szablon wersja oparta:

#include <iostream> 
#include <iomanip> 

enum endianness_t { 
    BIG,   // 0x44332211 => 0x44 0x33 0x22 0x11 
    LITTLE,  // 0x44332211 => 0x11 0x22 0x33 0x44 
    UNKNOWN 
}; 

const uint32_t test_value = 0x44332211; 
const bool is_little_endian = (((char *)&test_value)[0] == 0x11) && (((char *)&test_value)[1] == 0x22); 
const bool is_big_endian  = (((char *)&test_value)[0] == 0x44) && (((char *)&test_value)[1] == 0x33); 

const endianness_t endianness = 
    is_big_endian ? BIG: 
    (is_little_endian ? LITTLE : UNKNOWN); 


template <typename T> 
T identity(T v){ 
    return v; 
} 

// 16 bits values ------ 

uint16_t swap_(uint16_t v){ 
    return ((v & 0xFF) << 8) | ((v & 0xFF00) >> 8); 
} 

// 32 bits values ------ 

uint32_t swap_(uint32_t v){ 
    return ((v & 0xFF) << 24) | ((v & 0xFF00) << 8) | ((v & 0xFF0000) >> 8) | ((v & 0xFF000000) >> 24); 
} 

template <typename T, endianness_t HOST, endianness_t REMOTE> 
struct en_swap{ 
    static T conv(T v){ 
    return swap_(v); 
    } 
}; 

template <typename T> 
struct en_swap<T, BIG, BIG>{ 
    static T conv(T v){ 
    return v; 
    } 
}; 

template <typename T> 
struct en_swap<T, LITTLE, LITTLE> { 
    static T conv(T v){ 
    return v; 
    } 
}; 

template <typename T> 
T to_big(T v) { 

    switch (endianness){ 
    case LITTLE : 
    return en_swap<T,LITTLE,BIG>::conv(v); 
    case BIG : 
    return en_swap<T,BIG,BIG>::conv(v); 
    } 
} 

template <typename T> 
T to_little(T v) { 
    switch (endianness){ 
    case LITTLE : 
    return en_swap<T,LITTLE,LITTLE>::conv(v); 
    case BIG : 
    return en_swap<T,BIG,LITTLE>::conv(v); 
    } 
} 


int main(){ 

    using namespace std; 

    uint32_t x = 0x0ABCDEF0; 
    uint32_t y = to_big(x); 
    uint32_t z = to_little(x); 

    cout << hex << setw(8) << setfill('0') << x << " " << y << " " << setw(8) << setfill('0') << z << endl; 

}