Tekrar merhaba sevgili yazılımperver dostlarım. Bugün sizlerle birlikte, belki de birçoğunuzun hiç farkında olmadığı ya da kullanırken pek farketmediği bir konuya değineceğim, “Endianness”.
Eğer masaüstü ya da web ya da mobil bir yazılım geliştiricisiyseniz, bu kavramla hiç tanışmamış olabilirsiniz. Hatta, gömülü yazılım geliştiricisi iseniz ve eğer tek bir işlemci ailesi ile çalışma fırstı yakalamış çok şanslı bir azınlık içerisindeyseniz, bu kavramı yine duymamış olabilirsiniz. Fakat, farklı işlemci mimarileri ve haberleşme protokolleri ile çalışmışsanız, bu yazım ilginizi çekebilir.
Bu yazımda, bu kavramı masaya yatırıp, ne anlama geldiğine, bize yansımalarının ne olduğuna ve bunu nasıl kotaracağımıza bakıyor olacağız.
Öncelikle bunun ne olduğuna bir bakalım. Bunun için de birinci kaynağımız elbette wikipedia. Türkçe kısmı çok detaylı değil ve yanlış yönlendirebilir ama ingilizce kısmı oldukça detaylı, okumanız iyi olur, elbette ben burada önemli noktaları sizler ile paylaşacağım. Öncelikle tanımına bir bakalım. Elbette, hepsinin üzerine C++ da serpiştireceğiz 😉
“Endianness” aslında değerlerin/sayısal verilerin, byte olarak bellekte tutulma sırasına verilen isim (benzer şekilde haberleşme kanalları üzerinden gönderilme sırası). Günümüz dünyasında kullandığımız bilgisayar mimarileri, yazma ve okuma işlemleri için kullandıkları en küçük birim byte’dır. Bir çok işlemci, bellekteki sayısal verilere byte olarak erişmeye imkan verirler, fakat birden fazla byte söz konusu olduğunda, ilgili yaklaşım önem arz eder. İki dominant “endianness” yaklaşımı bulunmaktadır. Bunlar “big-endian (BE)” ya da “little-endian (LE)” ‘dır.
“Big endian” sistemlerde, ilgili dijital verinin en yüksek byte’ı en küçük adreste, en düşük byte’ı ise en büyük adreste tutulur. “Little endian” sistemlerde ise bunun tam tersi geçerlidir. Örneğin, elimizde 16 bit’lik bir short değişkeni olduğunu düşünelim. Bu değişken temelde iki byte’dan oluşmakta. 0-7 bitleri düşük byte’ı, 8-15 bitleri ise büyük byte değerini tutar. Buraya kadar aslında “endianness” ın bir etkisi yok. Ne zaman ki, bu byte’lar bellekte nasıl tutulur diye sorduğumuzda ya da örneğin seri kanal üzerinde ilk hangi byte gönderilir dediğimizde, “endianness” önem kazanır.
Kullanıdığımız İntel ve AMD işlemciler, “little-endian” yaklaşımını kullanmaktadır. Bazı Power PC, IBM z mimarileri ise “big-endian” yaklaşımını kullanmaktadır. Ayrıca, ağ protokollerinde de “big-endian” yaklaşımı dominanttır.
Şimdi elimizde 43981 sayısı olduğunu düşünelim. Bu sayının byte dizilimi 16’lık düzende şu şekildedir : 0xABCD. Bu sayının, MSB’si (“most significant byte”) AB ve rakamı da (16 lık düzende), A’dır. Bu sayının, iki yaklaşıma göre nasıl ifade edildiğine hemen bir figür ile bakalım:
Sanırım, bu figür, sizler için bu iki yaklaşımı özetlemek adına faydalı olmuştur.
Şimdi, azıcık kodlama yapalım. Örneğin, elimizdeki bir sayının bellekteki byte dizilimini görebilir miyiz? Elbette, hemen bakalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <stdio.h> // Bellekten verilen adresten (kucukten-buyuge) baslayarak, ilgili sayiyi 16 duzende gosteren fonksiyon void ShowMemRep(char *start, int n) { for (int i = 0; i < n; i++) printf(" %.2X", static_cast<unsigned char>(start[i])); printf("\n"); } int main() { unsigned short i = 0xABCD; ShowMemRep(reinterpret_cast<char *>(&i), sizeof(i)); return 0; } |
Bu kodu, “little endian” bir makinede çalıştırırsanız ” CD AB” görürsünüz. Tam tersi, “big endian” bir makinede çalıştırırsanız ” AB CD” görürsünüz.
Peki yazılımsal olarak, şu an kodumuzun çalıştığı makinenin “endianness” yaklaşımını öğrenebilir miyiz? Evet, onu da öğrenebiliriz. Nasıl mı, hemen bakalım.
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> using namespace std; int main() { unsigned int i = 1; char *c = reinterpret_cast<char *>(&i); if (*c) std::cout<<"Little endian"; else std::cout<<"Big endian"; return 0; } |
Evet, gelelim yazımızın 10 puanlık sorusuna. “Endiannes” olayı benim yoluma ne zaman çıkar?
- Farklı işlemci mimarileri ile haberleşen bir yazılımınız var ise,
- Ya da farklı işlemci mimarileri tarafından, bellek verilerinin üretildiği dosyalar ile uğraşıyorsanız.
Elbette, bu tarz haberleşen sistemlere ilişkin bir protokol tasarlıyorsanız, kaynak sistemin “endianness”‘ına ilişkin bir bitlik bilgiyi mesajlarınız içine gömebilirsiniz ve kim neye sahip duyurabilirsiniz 😉
Velev ki, baktınız elinizdeki veri, sizin makinenizin “endiannes” ı ile uyumlu değil ne yapacaksınız. Eğer QT kullanıyorsanız, işiniz kolay. Verileri dönüştürmek için QDataStream tek kaynak olarak kullanabileceğiniz, donanımlı ve oldukça yakışıklı bir sınıf. Bu sınıf saysesinde, farklı boyutlarda verileri belirleyeceğiniz “endianness” yaklaşımına göre, byte dizisine çevirebilir ya da byte dizisi olarak aldığınız verilerden ilgili sayıları üretebilirsiniz. Ayrıca sayıları dönüştürmek için <QtEndian> başlık dosyası içerisinde de bir takım yardımcı fonksiyonlar mevcut. Bu sınıflardan esinlenerek kendi sınıflarınızı da yazabilirsiniz, misal ben zamanında bir aviyonik projesi için DataStream sınıfı yazdıydım.
Yazımı bitirmeden, kod bohçamın içerisinde, teee 2017’de, yazdığım bir sınıfı da sizler ile paylaşıyorum, tepe tepe kullanabilirsiniz. O zamanlar yazılan bu sınıf, bu satıra kadar yazdığım bir çok noktayı içerisinde barındırıyor. Özellikle GetSystemByteOrder() tek başına kullanılabilir, ayrıca “variadic template” ‘ların kullanımına da güzel bir örnek oldu ne dersiniz 😉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
#include <iostream> #include <cstdint> #include <string.h> using namespace std; enum class uByteOrder : uint32_t { eBYTE_ORDER_BIG_ENDIAN, eBYTE_ORDER_LITTLE_ENDIAN, }; class uEndianness { public: uEndianness() = delete; //! Calisma zamaninda endianness'a bakalim static uByteOrder GetSystemByteOrder() { int16_t word = 0x0001; int8_t* byteToCheck = (int8_t*)&word; return (byteToCheck[0] ? uByteOrder::eBYTE_ORDER_LITTLE_ENDIAN : uByteOrder::eBYTE_ORDER_BIG_ENDIAN); } //! Makrolar ile kod icerisinde belirtmek istersek constexpr static bool IsBigEndian() { #ifdef UENGINE_BIG_ENDIAN return true; #else return false; #endif } //! Verilen sayiyi big endian'a cevirir ve doner. Zaten big endian olan sistemlerde bir donusum yapma //! Calisma zamaninda kontrol de eklenebilir template<class T> static T BigEndian(T value) { #ifdef UENGINE_BIG_ENDIAN return number; #else return Swap(value); #endif } //! Verilen sayiyi tipine gore gerekli degisim fonksiyonuna paslar template<class T> static T Swap(T value) { return Swap<sizeof(T)>(value); } //! Verilen sayiyinin kendisini big endian'a cevirir, zaten big endian olan sistemlerde bir donusum yapmaz //! Calisma zamaninda kontrol de eklenebilir #if !defined(UENGINE_BIG_ENDIAN) template<class ...T> static void BigEndianInPlace(T&... values) { BigEndianInPlaceInternal(values...); } #else template<class ...T> static void BigEndianInPlace(T&...) {} #endif //! Verilen sayiyi little endian'a cevirir ve doner. Zaten little endian olan sistemlerde bir donusum yapma //! Calisma zamaninda kontrol de eklenebilir template<class T> static T LittleEndian(T number) { #ifdef UENGINE_BIG_ENDIAN return Swap(number); #else return number; #endif } //! Verilen sayiyinin kendisini little endian'a cevirir, zaten little endian olan sistemlerde bir donusum yapmaz //! Calisma zamaninda kontrol de eklenebilir #if defined(UENGINE_BIG_ENDIAN) template<class ...T> static void LittleEndianInPlace(T&... values) { LittleEndianInPlaceInternal(values...); } #else template<class ...T> static void LittleEndianInPlace(T&...) {} #endif private: template<std::size_t size> struct TypeFor {}; template<std::size_t size> static typename TypeFor<size>::Type Swap(typename TypeFor<size>::Type value); #ifndef UENGINE_BIG_ENDIAN template<class T, class ...U> static void BigEndianInPlaceInternal(T& first, U&... next) { first = BigEndian(first); BigEndianInPlaceInternal(next...); } static void BigEndianInPlaceInternal() {} #else template<class T, class ...U> static void LittleEndianInPlaceInternal(T& first, U&... next) { first = LittleEndian(first); LittleEndianInPlaceInternal(next...); } static void LittleEndianInPlaceInternal() {} #endif }; //! 1, 2, 4, ve 8 byte'lik sayilar icin kullanilacak donusum fonksiyonlari template<> struct uEndianness::TypeFor<1> { typedef std::uint8_t Type; }; template<> struct uEndianness::TypeFor<2> { typedef std::uint16_t Type; }; template<> struct uEndianness::TypeFor<4> { typedef std::uint32_t Type; }; template<> struct uEndianness::TypeFor<8> { typedef std::uint64_t Type; }; template<> inline std::uint8_t uEndianness::Swap<1>(std::uint8_t value){ return value; } template<> inline std::uint16_t uEndianness::Swap<2>(std::uint16_t value) { return (value >> 8) | (value << 8); } template<> inline std::uint32_t uEndianness::Swap<4>(std::uint32_t value) { return (value >> 24) | ((value << 8) & 0x00ff0000u) | ((value >> 8) & 0x0000ff00u) | (value << 24); } template<> inline std::uint64_t uEndianness::Swap<8>(std::uint64_t value) { return (value >> 56) | ((value << 40) & 0x00ff000000000000ull) | ((value << 24) & 0x0000ff0000000000ull) | ((value << 8) & 0x000000ff00000000ull) | ((value >> 8) & 0x00000000ff000000ull) | ((value >> 24) & 0x0000000000ff0000ull) | ((value >> 40) & 0x000000000000ff00ull) | (value << 56); } void ShowMemRep(char *start, int n) { for (int i = 0; i < n; i++) printf(" %.2X", static_cast<unsigned char>(start[i])); printf("\n"); } int main() { // Sistemimiz nedir? if (uByteOrder::eBYTE_ORDER_LITTLE_ENDIAN == uEndianness::GetSystemByteOrder()) std::cout<<"Little endian\n"; else std::cout<<"Big endian\n"; // API'ler ile biraz oynasalim std::cout<<"Tekli dönüsümler\n"; uint16_t value = 0xABCD; ShowMemRep(reinterpret_cast<char *>(&value), sizeof(value)); value = uEndianness::Swap(value); ShowMemRep(reinterpret_cast<char *>(&value), sizeof(value)); uint64_t bigValue = 0x1234567890ABCDEF; ShowMemRep(reinterpret_cast<char *>(&bigValue), sizeof(bigValue)); bigValue = uEndianness::Swap(bigValue); ShowMemRep(reinterpret_cast<char *>(&bigValue), sizeof(bigValue)); std::cout<<"Toplu dönüsümler\n"; // Sistemimiz little-endian oldugu icin, variadic template'lar ile biraz raks edelim :) // Tabi ki BigEndianInPlace i kullanarak uint64_t bigValue1 = 0x1234567890ABCDEF; uint64_t bigValue2 = 0x34567890ABCDEF12; uint64_t bigValue3 = 0x567890ABCDEF1234; std::cout<<"Little endian gosterimler\n"; ShowMemRep(reinterpret_cast<char *>(&bigValue1), sizeof(bigValue1)); ShowMemRep(reinterpret_cast<char *>(&bigValue2), sizeof(bigValue2)); ShowMemRep(reinterpret_cast<char *>(&bigValue3), sizeof(bigValue3)); uEndianness::BigEndianInPlace(bigValue1, bigValue2, bigValue3); std::cout<<"Big endian gosterimler\n"; ShowMemRep(reinterpret_cast<char *>(&bigValue1), sizeof(bigValue1)); ShowMemRep(reinterpret_cast<char *>(&bigValue2), sizeof(bigValue2)); ShowMemRep(reinterpret_cast<char *>(&bigValue3), sizeof(bigValue3)); return 0; } |
UENGINE kullanımı dikkatinizi çekmiştir, nereden geliyor diye merak ediyorsanız, zamanıdna giriştiğim, üç dört iterasyon oyun motoru çalışmalarından geliyor. U’nun nereden geldiği ise şimdilik ben de kalsın 😛
Bu arada, bu ve benzeri kod parçalarını da https://github.com/yazilimperver altına bir proje açıp toplamayı planlıyorum, ne dersiniz? Görüşlerinizi bekliyorum.
Bir sonraki yazıma kadar kendinize çok iyi bakın sevgili yazılımperver dostlarım.
Kaynaklar:
- https://www.wikiwand.com/en/Endianness
- https://www.youtube.com/watch?v=NcaiHcBvDR4
- https://www.techtarget.com/searchnetworking/definition/big-endian-and-little-endian
- https://www.bgasecurity.com/2015/04/exploit-gelistiriciler-icin/
- https://doc.qt.io/qt-5/qdatastream.html
- https://doc.qt.io/qt-5/qtendian.html