Thread overview
Big-endian nedir, ne işe yarar? (endianness kavramı)
Mar 13, 2022
Salih Dincer
Mar 13, 2022
Salih Dincer
Mar 13, 2022
Ali Çehreli
Mar 13, 2022
Salih Dincer
Mar 13, 2022
Ali Çehreli
March 13, 2022

Merhaba,

Şimdi hızlı bir turla ikilik sistemden girip UTF'den çıkacağız; koltuklarınıza yaslanabilirsiniz 😀

İkilik tabandaki bir sayının en sağdaki biti en az anlamlı bit (LSB, çoğulsa LSBs) olarak adlandırılırken, en solundaki biti en anlamlı bit (MSB, çoğulsa MSBs) olarak bilinir. İngilizce karşılıkları ise hemen altta:

>

The right most bit of a binary number is
called least-significant bit (LSB) while
the left most bit of a binary number is
known as the most-significant bit (MSB).

    enum : char { // 8'er bit veri
      D = 0x44,
      _ = 0x20,
      L = 0x4c,
      A = 0x41,
      N = 0x4e,
      G = 0x47
    }
    import std.stdio;

    void main()
    {
       wchar[] dataW = [ D, _, L, A, N, G ];
       dataW.writefln!"%(%08X\n%)";
    } /* ÇIKTISI:
    00000044
    00000020
    0000004C
    00000041
    0000004E
    00000047
    */

Yukardaki gibi her elemanın/karakterin 4 bayt yer kapladığı (dataW dizisi, yani UTF32 türünde) bir verimiz olsun. Her ne kadar wchar 32 bit olsa da, dizi kurulurken aktarılan bilgi char olduğundan en sağdaki bitler (LSBs) içinde temsil edilir. Bunu zaten anlamlarıyla ilişkili çıktıdaki düz sıralamada (Big-endianness) görmekteyiz.

Aşağıdaki alıntıda belirtildiği gibi, ağ iletişimlerinde baskın olan bu dizilimken, işlemci ve bellek mimarilerinde ters sıralama (Little-endianness) baskındır. İş dosyalara gelince her ikisi de karışık olarak kullanılır.

>

Big-endianness is the dominant ordering in networking protocols, such as in the internet protocol suite, where it is referred to as network order, transmitting the most significant byte first. Conversely, little-endianness is the dominant ordering for processor architectures (x86, most ARM implementations, base RISC-V implementations) and their associated memory. File formats can use either ordering; some formats use a mixture of both or contain an indicator of which ordering is used throughout the file.

Hatta ağ iletişiminden sorumlu bazı donanımlar, bu anlattıklarımızdan birini rasgele seçip kullandığı karışık mode da varmış ama konumuz dışındadır.

Bu kısa girişten sonra (bu sadece özetti, daha çok bilgi internette mevcut!), anlattıklarımızı güzel bir örnekle şerbetleyelim:

import std.stdio, std.conv;
import std.algorithm, std.range;

  union uintToBytes {
    uint data;  // 4 bytes
    byte[4] s; // segments
  }

  auto merge(R)(R range) {
    uintToBytes convert;
    scope(exit) convert.data.writefln!"%032b";

    // if range is string, dchar[]
    // if range is File, ubyte[]

    foreach(i, uint c; range.array) {/*
    int i = -1;
    foreach_reverse(uint c; range.array) {
    i++;//*/
      convert.data |= c << (i * 8);
    }
    return convert.s;
  }

void main()
{
  enum text ="D LANG99";

  auto f = File("text.dat", "w+");
  text.chunks(4)
      .each!(bytes =>
        f.rawWrite(bytes.merge)/*
        f.rawWrite(bytes.to!string)//*/
      );


  writeln("The head is rewind...");
  f.rewind;


  auto data = f.rawRead(new byte[](text.length));
  data.chunks(4)
      .each!(bytes =>
        bytes.merge.writefln!"%(0x%02x %)"/*
        bytes.writefln!"%(0x%02x\n%)"//*/
      );

  string test;
  foreach(char c; data) {
    test ~= c;
    c.writef!"%s";
  }
  assert(test == text);
  "\n".writeln("\u2713 TEST OK!");
} /* ÇIKTISI:
  01000001010011000010000001000100
  00111001001110010100011101001110
  The head is rewind...
  01000001010011000010000001000100
  0x44 0x20 0x4c 0x41
  00111001001110010100011101001110
  0x4e 0x47 0x39 0x39
  D LANG99
  ✓ TEST OK!
  */

Başarılar...

March 13, 2022

İddia ediyorum yukarda yazdıklarımdan hiçbir şey anlamadınız çünkü konu öyle hızlıca geçiştirilecek kadar kısa değil. Buna rağmen örneğimiz, o kadar da kafa karıştırıcı değil 😀 Eğer test bölümünü saymazsak, 2 bölümden oluşuyor:

Önce f.rawWrite() ile bir dosyaya D LANG99 yazıyor ve bunu diğer bölümde f.rawRead() ile (tabi okuma kafasını başa sardıktan sonra) okuyor...

Hepsi bu ama en önemlisi, veri döngünü içinde daha büyük bir veri içine yerleştirilirken (convert.data) yapılan kaydırma işlemi. Burada kullanılan index (i: elamanın sırası), yapılan işlem ile uyumlu olmasını atın cebinize. Cepte dursun birazdan tekrar değineceğiz!

Teknik olarak endianness kavramını belirgin olarak göstermediğim için şu örnekle devam edelim lütfen:

Cepte duruyor değil mi 😀

import std.stdio, std.conv;
import std.algorithm, std.range;

  enum : char {
    D = 0x44,
    _ = 0x20,
    L = 0x4c,
    A = 0x41,
    N = 0x4e,
    G = 0x47
  }

  union uintToBytes {
    uint data;  // 4 bytes
    byte[4] s; // segments
  }

void main()
{
   wchar[] dataW = [ D, _, L, A, N, G ];

   ubyte[] data;
   foreach(w; dataW)
   {
     const convert = uintToBytes(w);
     //      v--- because LSBs are put first in union
     foreach_reverse(c; convert.s)
     {
       data ~= c;
     }
     assert(w == convert.s[0]);

     auto chr = cast(char)convert.s[0];
     chr.writef!"%s"; // D LANG
   }
   writeln;
   data.chunks(4)
       .each!(bytes =>
           bytes.writefln!"0x%(%02X%)"
       );
} ÇIKTISI:
D LANG
0x00000044
0x00000020
0x0000004C
0x00000041
0x0000004E
0x00000047
*/

Bu örnekte reverse ile karşılaştık, işte buraya da dikkat. Ama önce Wikipedia'daki verinin bellekteki yerleşimini temsili gösteren 2 resme bakın: https://en.m.wikipedia.org/wiki/Endianness

Şimdi bir soru: niye reverse yapmış olabiliriz? Cevaplamak için cebe attığınız sola kaydır ve birleştir işlemini çıkaralım...

foreach(i, uint c; range.array)
{
  convert.data |= c << (i * 8);
}

Burda olan şu: Union (birlik) içindeki 4 baytlık uint ile byte[4] birbirleriyle ilişkilendirilirken LittleEndian düzenine göre aynı bellek bölgelerini gösteriyor. Böylece yukarıda döngü ile her gelen veri (uint türünde c) sırasına göre (i) ve içerdiği anlamlı bilgi (8 bit) kadar sola kaydırılıyor. Bu da birlikle uyumlu en akla yakın yol. Dilerseniz burada da reverse kullanın size_t i değeri de değişeceğinden hiçbir veri uyumsuzluğu veya terslenmesi gerçekleşmeyecektir.

Bilgisayar dünyasında hangi endianness avantajlıdır tartışıla dursun aradaki farkın bilincinde olduğunuz sürece karışıklık çıkmayacaktır. Tıpkı 2. örneğimizde foreach_reverse ile veriyi sıraladığımız gibi. Burada hiçbir ekstra birşeye ihtiyaç duymadan Big -> Little Endian arasında geçiş yapmanız mümkün.

Şimdilik bu kadar, soru gelirse yine devam ederiz...

March 13, 2022
On 3/13/22 09:16, Salih Dincer wrote:

> İddia ediyorum yukarda yazdıklarımdan hiçbir şey anlamadınız

:) Daha basit de anlatılabilir. Ben şöyle düşünüyorum: Bu konuyu bilen zaten biliyor, bilmeyen ise enum, union, wchar, UTF-8, vs. içeren çok karmaşık bir konu sanabilir.

(Bu arada, ilk mesajında 4 bayt dediğine bakarak dchar kullanmak istediğini düşünüyorum, wchar değil.)

Bir tamsayının baytlarını art arda göstermek de yeterli olmalı:

import std.stdio;

void baytlarınıGöster(int değer) {
  writefln!"%s değerinin baytları:"(değer);

  auto adres = cast(ubyte*)(&değer);
  foreach (i; 0 .. 4) {
    writefln!"%s: %02x"(i, adres[i]);
  }
}

void main() {
  baytlarınıGöster(0x11_22_33_44);
}

> Bilgisayar dünyasında hangi endianness avantajlıdır tartışıla dursun
> aradaki farkın bilincinde olduğunuz sürece karışıklık çıkmayacaktır.

Bir de, bu konunun yalnızca programın dış dünyayla etkileşimi sırasında ortaya çıkmasıdır. O konu için de bswap() yararlı olabilir:

  https://dlang.org/library/core/bitop.html

Sonculluk (endianness) veri bir kere okunduktan sonra programın işlemlerini etkilemez. Örneğin, bitleri sola kaydırmak anlam olarak hep aynıdır: mikroişlemciden bağımsız olarak değer azalır.

Ali

March 13, 2022

On Sunday, 13 March 2022 at 17:14:44 UTC, Ali Çehreli wrote:

>

(Bu arada, ilk mesajında 4 bayt dediğine bakarak dchar kullanmak istediğini düşünüyorum, wchar değil.)
Evet hocam bu ikisini her zaman karıştırıyorum çünkü dchar dediğimizde aklıma çift yani double geliyor. Öte yandan da wchar pekala sözcüğü yani word'ü çağrıştırıyor. Malum bir kelime için çoğu zaman ikiden fazla harf gerektirir.

Bu arada Big ile Little'ı karıştırmamak için şöyle bir yöntem var:

Soldan sağa, yani büyük değerli bitlere (MSBs) doğru giden düz dizilim Big-endianness, sağdan sola yani düşük değerli bitlere doğru giden ters dizilim Little-endianness.

March 13, 2022
On 3/13/22 1:26 PM, Salih Dincer wrote:

> Bu arada Big ile Little'ı karıştırmamak için şöyle bir yöntem var:
>
> Soldan sağa, yani büyük değerli bitlere (MSBs) doğru giden düz dizilim
> Big-endianness, sağdan sola yani düşük değerli bitlere doğru giden ters
> dizilim Little-endianness.

Kafam daha fazla karıştı. :) "Soldan sağa" derken değerin baytlarından mı bahsediyoruz yoksa bellekteki baytarlardan mı? Tabii "bitlere" dediğine göre değer olmalı. Ama bu sefer de büyük değerli bitler solda değil mi? vs.

Ben de şöyle bir yöntem uyguluyorum:

- Big endian ise, büyük değerli bayttan küçük değerli bayta doğru depolanır. Örneğin, değeri 0x44_33_22_11 olan bir int'in baytları bellekte art arda 0x44, 0x33, 0x22, ve 0x11 olarak bulunur. Başka deyişle, depolama bana mantıklı geliyorsa big endian'dır. :)

- Little endian ise, baytlar onun ters sırasında depolanır: 0x11, 0x22, 0x33, ve 0x44.

Örneğin, eğer aşağıdaki program 0x11 yazıyorsa little endian'dır. (Bende öyle ve zaten Intel işlemcimin little endian olduğunu biliyorum.)

import std.stdio;

void main() {
  int i = 0x44_33_22_11;
  writefln!"%02x"(*cast(ubyte*)(&i));
}

Ali