10 Temmuz 2018 Salı

Endian Agnostic

Giriş
Portable kod yazmak için Endian Agnostic yöntemleri kullanmak gerekir. Agnostic denmesinin sebebi kendi işlemcimin mimarisi ile ilgilenmemem. Sadece veriyi big endian mı yoksa little endian mı göndermek istememle ilgileniyorum.

Bu yöntemi kullanmazsak
1. Kodun içine #if BIG_ENDIAN ya da #if LITTLE_ENDIAN gibi macrolar koymak gerekir.
2. Daha sonra little endian ise htons(), htonl() çağrıları  yapmak gerekir.
Float tipler için bu çağrılar yoksa şöyle yaparız.
float f = 1.2f;
auto it = reinterpret_cast<uint8_t*>(&f);
std::reverse(it, it + sizeof(f)); //f is now in the reversed endianness

Big Endian Gönder ve Al
Socket kullanımından dolayı Big Endian okuma yazma işlemleri için hazır gelen htons, ntohs veya htonl, ntohl gibi metodlar kullanılır.

Eğer bu tür metodlar sistemimizde yoksa kendimiz de kolayca yazabiliriz. Altta yatan mantık çok basit.

Yazma
Şöyle yaparız. Buffer açısından bakarsak her zaman yazmaya LSB byte'tan başlarız. Yani Buffer'ın ilk alanından yazmaya başlarız. Bellegi soldan sağa okuduğumuzu düşünürsek bu aslında byte'ların ters çevrilmesi gibidir.
uint32_t i = ...;
uint8_t buf[4];
buf[0] = (i&0xff000000) >> 24;
buf[1] = (i&0x00ff0000) >> 16;
buf[2] = (i&0x0000ff00) >> 8;
buf[3] = (i&0x000000ff);
write(fd, buf, sizeof buf);
Okuma
Şöyle yaparız.
uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[0] << 24;
i |= (uint32_t)buf[1] << 16;
i |= (uint32_t)buf[2] << 8;
i |= (uint32_t)buf[3];
Bu kod aslında tek satır olarak şöyle de yazılabilir.
i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);
Örnek
Şöyle yaparız.
uint8_t* buf = any address;

uint32_t val = 0;
uint32_t  b0 = buf[0];
uint32_t  b1 = buf[1];
uint32_t  b2 = buf[2];
uint32_t  b3 = buf[3];

val = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
Little Endian Gönder ve Al
Little Endian yazma ve okuma metodları gelmiyor. Mecburen kendimiz yazmalıyız.

Yazma
Şöyle yaparız. Buffer açısından bakarsak her zaman yazmaya MSB byte'tan başlarız. Yani Buffer'ın son alanından yazmaya başlarız. Belleği soldan sağa okuduğumuzu düşünürsek bu aslında byte'ların olduğu sırada gönderilmesi gibidir.
uint32_t i = ...;
uint8_t buf[4];
buf[3] = (i&0xff000000) >> 24;
buf[2] = (i&0x00ff0000) >> 16;
buf[1] = (i&0x0000ff00) >> 8;
buf[0] = (i&0x000000ff);
write(fd, buf, sizeof buf);

Okuma
Şöyle yaparız.
uint32_t i ;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[3] << 24;
i |= (uint32_t)buf[2] << 16;
i |= (uint32_t)buf[1] << 8;
i |= (uint32_t)buf[0];
Bu kod aslında tek satır olarak şöyle de yazılabilir.
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);





Hiç yorum yok:

Yorum Gönder