Konuyu Oyla:
  • Derecelendirme: 4.5/5 - 2 oy
  • 1
  • 2
  • 3
  • 4
  • 5
Offset Kavramı ve Dinamik Dizi Yapımı
#1
Birçok programlama dilinde dinamik dizi kullanabiliyoruz.Dizilerde [] indexer operator yardımıyla dizi elemanlarına erişebiliyoruz.

C++
int* myArray = new int[2];
myArray[0] = 10;
delete[] myArray;
C#
int[] myArray = new int[2];
myArray[0] = 10;
Java
int[] myArray = new int[2];
myArray[0] = 10;
Delphi
var
 MyArray: array of Integer;
begin
 SetLength(MyArray, 2);
 MyArray[0] := 10;
end.

Yukarıdaki örnekleri incelediğimizde C++, C#, Java gibi dillerde new operatörü, delphi için ise SetLength fonksiyonu yardımıyla dinamik dizileri kullanabiliyoruz. 

C dilinde ise; C++, C#, java da bulunan new operatoru ya da delphi'deki gibi bir SetLength fonksiyonu bulunmuyor.Dolayısı ile dinamik dizimizi kendimiz gerçekleştirmemiz gerekiyor.
Aşağıdaki örnekte; 8 byte'lık bir hafıza bloğu tahsis edilip, myArray işaretçisinin [] indexer operatörü yardımıyla değerlere erişiyoruz.
C
int* myArray = malloc(sizeof(int) * 2);
myArray[0] = 10;
myArray[1] = 20;
free(myArray);
Sanırım nihayet konuya giriş yapabilmek için gerekli alt yapıyı sağlamış oldum Smile

Peki dil C, C++, C#, java ya da delphi ya da bir başka dil farketmeksizin myArray[index] operatorü ile dizi elemanlarına erişmek istediğimde, hafıza bloğunda ilgili index'deki veriye nasıl eriştiğini biliyor muyum ? Bilmiyorum.

Bu yazıda dil seviyesinde index mantığını nasıl gerçekleştirebiliriz bunu anlamaya ve öğrenmeye çalışacağız. Bilindiği üzere C, C++, C#, Delphi gibi dillerde, işaretçileri(pointer), işaretçi aritmetiğini(pointer math) kullanabiliyoruz.Java'da ise yine bildiğim kadarıyla şuan için işaretçi kullanımına dil seviyesinde izin verilmiyor.

İşaretçi(pointer) kullanımına izin veren dillerde index'leme mantığını işaretçiler üzerinden verinin adresini hesaplayarak gerçekleştireceğiz. Dinamik bellek yöneten fonksiyonlar veriyi hafızada tahsis ettikten sonra geriye, veri bloğunun başlangıç adresini untyped pointer(void* veya PVOID veya LPVOID veya Pointer) tipinde döndürmektedir.

C
void *        _RTLENTRY _EXPFUNC malloc(_SIZE_T __size);
Windows API
WINBASEAPI
_Ret_maybenull_
_Post_writable_byte_size_(dwBytes)
DECLSPEC_ALLOCATOR
LPVOID
WINAPI
HeapAlloc(
   _In_ HANDLE hHeap,
   _In_ DWORD dwFlags,
   _In_ SIZE_T dwBytes
   );

C#
IntPtr p = Marshal.AllocHGlobal(size);

Delphi
procedure GetMem(var P: Pointer; Size: Integer);
function HeapAlloc(hHeap: THandle; dwFlags, dwBytes: DWORD): Pointer; stdcall;

UnTyped Pointer'larda aritmetik işlemlere izin verilmediği için, fonksiyonlardan dönen veri tipini typed pointer'lara çevireceğiz.
Örneğin C dilinde aşağıdaki gibi untyped pointer tipinde ki p değişkeni için p += 4; işlemine izin verilmeyecektir.

void* p = malloc(sizeof(int) * 2);
p += 4;

[bcc32 Error] main.c(17): E2453 Size of the type 'void' is unknown or zero
Error C2036 'void *': unknown size

Örneğin Delphi dili için aşağıdaki gibi untyped pointer tipinde ki P için P := P + 4; ya da Inc(P,4); işlemine izin verilmeyecektir.
var
 P: Pointer;
begin
 GetMem(P, SizeOf(Integer) * 2);
 P := P + 4;
end.
[dcc32 Error] Project2.dpr(60): E2015 Operator not applicable to this operand type
ya da
var
 P: Pointer;
begin
 GetMem(P, SizeOf(Integer) * 2);
 Inc(P, 4);
end.
[dcc32 Error] Project2.dpr(70): E2001 Ordinal type required 

Dolayısı ile hafıza adreslerini index yoluyla hesaplayabilmemiz için typed pointerları kullanmamız gerekiyor.
C
int* p = malloc(sizeof(int) * 2);
*p = 10;
 p += 4;
*p = 20;
free(p);
Delphi
var
 P: ^Integer; // ya da PInteger
begin
 GetMem(P, SizeOf(Integer) * 2);
 P^ := 10;
 Inc(P, 4);
 P^ := 20;
 FreeMem(P, SizeOf(Integer) * 2);
end;

C#
public static IntPtr Add(IntPtr pointer, int offset);
C#'da ise Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.(Pointer ya da Handle veri tipi için) yapısında bulunan Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız. methodu yardımıyla hesaplama yapabiliyoruz.
static void Main(string[] args)
{
 IntPtr p = Marshal.AllocHGlobal(sizeof(int) * 2);
 Marshal.WriteInt32(p, 10);
 IntPtr newP = IntPtr.Add(p, 4);
 Marshal.WriteInt32(newP, 20);
 Marshal.FreeHGlobal(p);
}

Yukarıdaki gibi hafıza bloğunun başlangıç adresini gösteren bir işaretçi üzerinde "doğrudan" değişiklik yapan p += 4; ya da P := P + 4; gibi aritmetik işlemler yazacağımız index'leme mantığı için çok doğru olmayabilir.O nedenle dinamik bellekte veriye işaret eden işaretçi üzerinde doğrudan bir değişiklik yapmadan yeni adresi hesaplayabiliriz.

Basit bir hesap ile formülümüz aşağıdaki gibi olacaktır. 
Kaç Bayt = index X Veri tipinin kapladığı alan(Byte)
Yeni Adres = Başlangıç adresi + Kaç Byte

Kabaca başlangıç adresine kaç byte ekleyeceğimizi index yoluyla hesaplayarak verinin offset'ini hesaplamış olduk. Formülü kodlayacak olursak;

C
int index = 0;
int* p = malloc(sizeof(int) * 2);
int* newp = p + (index * sizeof(int));
*newp = 10;
free(p);
Delphi
var
 P, NewP: PInteger;
 Index: Integer;
begin
 Index := 0;
 GetMem(P, SizeOf(Integer) * 2);
 NewP := PInteger(PByte(P) + (Index * SizeOf(Integer)));
 NewP^ := 10;
 FreeMem(P, SizeOf(Integer) * 2);
end;

index değişkeninin değerini değiştirerek dizinin elemanlarına erişim sağlayabiliriz. Yazdığımız kodları eleştirecek olursak; Kodumuz çok da yakışıklı olmadı.

Örneğin; index * sizeof(int) yerine bir offset fonksiyonu yazabiliriz. 
int Offset(int index){
 return index * sizeof(int);
}
int* newp = p + Offset(index);

Delphi
function Offset(Index: Integer): Integer;
begin
 Result := Index * SizeOf(Integer);
end;
NewP := PInteger(PByte(P) + Offset(Index));

Dizinin eleman sayısından, hafızada tahsis edilecek veri bloğununun kaplayacağı alanı hesaplayan bir fonksiyon yazılabilir.
C
int* p = malloc(sizeof(int) * 2);
yerine
int MemSize(int count) {
  return sizeof(int) * count;
}


Delphi
GetMem(P, SizeOf(Integer) * 2); 
yerine
function MemSize(Count: Integer): Integer;
begin
 Result := SizeOf(Integer) * Count;
end;
GetMem(P, MemSize(2));

Eleştirmeye devam edecek olursak;
Şuana kadar yazdığımız kodların tamamı int veri tipi üzerine idi. Programlama sadece int veri tipinden ibaret değil ki ? 
Başka veri tipleri için ne yapacağız ? 
Aynı mantığı her veri tipi için tekrar tekrar mı yazacağız ? 

İşte tam bu noktada, algoritmayı veri tipinden soyutlamak ya da bağımsız kılmak için, nesne yönelimli programlamanın sağladığı faydalardan yararlanabiliriz. Bunun için C++'da templates, C# ve Delphi'de generics'ler ile daha esnek kodlamalar yapabiliriz.Generics ve templates ile her ne kadar veri tipini esnek hale getirmiş olsak da, algoritmamız her veri tipi için uygun olmayabilir.Bu nokta da generics constraint'ler ile belli tipler için kurallar getirebiliriz. Tabi ki yine C'de untyped pointerlar ile daha generic fonksiyonlar mümkün. 

Bahsettiğim generic haline Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız. adresinden erişebilir, katkı sağlayabilir (mümkünse), gördüğünüz eksik ya da yanlışlar konusunda bilgilendirirseniz sevinirim. 

Sağlıcakla.
  • Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
  • Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
  • Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
  • Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
  • Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
  • Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
  • Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
  • Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
WWW
Cevapla
#2
Elinize saglik
Cevapla
#3
Verdiğim github linki'ne erişemeyebilirsiniz.Hesabım bloklanmış sanırım.
git.png
WWW
Cevapla
#4
Merhaba,
Değerli paylaşım için teşekkür ederim.
While true do; Hayat döngüsü, kısır değildir! Yapılan bir yanlış, o döngünün dışına çıkmanızı sağlayacaktır.
WWW
Cevapla
#5
Elinize sağlık,

Bu arada github sitenize erişebiliyorum
P.Safa:Yaşlanarak değil, yaşayarak tecrübe kazanılır.Zaman insanları değil,armutları olgunlaştırır
C.Yücel:Toprak gibi olmalısın! Ezildikçe sertleşmelisin!Seni ezenler sana muhtaç kalmalı! Hayatı sende bulmalı
S.Canan:Bildiğini zannettiğin an hiç bir şey öğrenemezsin
Bilgi uçar
WWW
Cevapla
#6
Başka dil, teknik, yöntem her olursa olursa olsun uygulayabilen var ve paylaşırsa sevinirim.
Umarım faydalı olmuştur...
WWW
Cevapla
#7
Ufak bir soru sorabilir miyim?

Bir kaç kez boyutu değiştirilmiş dinamik bir dizede, dizi elemanları pointer açısından bellekte ardışık mı tutulur? yani burada bir heap'mi yoksa stack mı söz konusudur?
P.Safa:Yaşlanarak değil, yaşayarak tecrübe kazanılır.Zaman insanları değil,armutları olgunlaştırır
C.Yücel:Toprak gibi olmalısın! Ezildikçe sertleşmelisin!Seni ezenler sana muhtaç kalmalı! Hayatı sende bulmalı
S.Canan:Bildiğini zannettiğin an hiç bir şey öğrenemezsin
Bilgi uçar
WWW
Cevapla
#8
Tabi hocam, sorduğunuz sorunun cevabını bilmiyorum.
Delphi'deki SetLength fonksiyonunun ne yaptığını, ya da nasıl çalıştığını bilmemiz gerekiyor.
SetLength için basit bir test kodu yazdım.
procedure L(Msg: String);
begin
 Form2.Memo1.Lines.Add(Msg);
end;

var
 I: Integer;
 Arr: array of Integer;
 PFirst, PLast, PCurrent: PByte;
begin
 SetLength(Arr, 2);
 Arr[0] := 10;
 Arr[1] := 20;

 PFirst := @Arr[Low(Arr)];
 PLast := @Arr[High(Arr)];
 PCurrent := PFirst;
 while PCurrent <= PLast do
 begin
   L(Integer(PCurrent).ToHexString + ' ->' + IntToStr(PCurrent^));
   Inc(PCurrent, SizeOf(Integer));
 end;

 L('Yeniden dizinin eleman sayısını ayarladım.');
 SetLength(Arr, 3);
 Arr[0] := 30;
 Arr[1] := 40;
 Arr[2] := 50;
 PFirst := @Arr[Low(Arr)];
 PLast := @Arr[High(Arr)];
 PCurrent := PFirst;
 while PCurrent <= PLast do
 begin
   L(Integer(PCurrent).ToHexString + ' ->' + IntToStr(PCurrent^));
   Inc(PCurrent, SizeOf(Integer));
 end;
end;

02CC1BE0 ->10
02CC1BE4 ->20
Yeniden dizinin eleman sayısını ayarladım.
02CC1BE0 ->30
02CC1BE4 ->40
02CC1BE8 ->50

Alıntı:Dinamik bellek yöneten fonksiyonlar veriyi hafızada tahsis ettikten sonra geriye, veri bloğunun başlangıç adresini untyped pointer(void* veya PVOID veya LPVOID veya Pointer) tipinde döndürmektedir.
Yukarıda bahsettiğim üzere dinamik bellek fonksiyonlarını (malloc,HeapAlloc,GetMem) kullandığı için Heap diyebiliriz.

Umarım doğru anlamışımdır  Undecided
WWW
Cevapla
#9
Dinamik dizinler için Heap, statik dizinler için Stack diye yazıyor nette.
Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.
WWW
Cevapla
#10
(10-09-2018, Saat: 17:57)uparlayan Adlı Kullanıcıdan Alıntı: Linkleri Görebilmeniz İçin Üye Olmanız Gerekiyor. Üye Olabilmek İçin Lütfen Buraya Tıklayınız.Ufak bir soru sorabilir miyim?

Bir kaç kez boyutu değiştirilmiş dinamik bir dizede, dizi elemanları pointer açısından bellekte ardışık mı tutulur? yani burada bir heap'mi yoksa stack mı söz konusudur?

C ' de dizi elemanlarının bellekte ardışık tutulması garanti altında object pascalın da aynı şekilde olduğuna eminim yoksa ciddi sorunlar çıkar

Boyutunu dinamik olarak artırdığımız bir dizinin elemanlarının bellekte ardışık olması garanti altında fakat dizinin boyutunu artırdığımızda dizinin başlangıç adresi değişebilir bunu her defasında programcı kontrol etmelidir iyi bir c derleyicisi bu durumda undefined behavior şeklinde warning döndürür.

“Do. Or do not. There is no try.”
Cevapla


Konu ile Alakalı Benzer Konular
Konular Yazar Yorumlar Okunma Son Yorum
  Record Üzerindeki Verilere Dinamik Erişim narkotik 0 256 13-04-2019, Saat: 01:41
Son Yorum: narkotik



Konuyu Okuyanlar: 1 Ziyaretçi