19 Ağustos 2015 Çarşamba

malloc calloc ve new

Giriş
 IPC (Inter Process Communication) ve Segmentation Fault başlıklı yazılara göz atabilirsiniz.
malloc ve türevleri yazısına bakabilirsiniz
- Linux ve Overcommit yazısına bakabilirsiniz

Heap ve Threadler
Heap, uygulama içindeki tüm threadler tarafından paylaşılır. Heap parçalandıkça (fragmentation), allocator talep edilen alanı bulmak için daha fazla işlemci harcamak zorunda kalabilir. Bu yüzden emniyet kritik (safety critical) uygulamalarda dinamik bellek kullanımı yasaklanmıştır. Daha fazla bilgi aşağıdaki için DO-178B başlıklı bölüme bakabilirsiniz.

Pointer Arithmetic

Pointer arithmetic C,C++'ta çokça kullanılır. Malloc ile ayrılmış iki pointer'ın karşılaştırılmasına yarar Pointer arithmetic kullanırken dikkat edilmesi gereken bazı kurallar var.

ptrdiff_t
İki pointer'ın farkını hesaplayınca ptrdiff_t tipinden bir sonuç alırız. Bu tip signed bir tiptir.

When two pointers to elements of the same array object are subtracted, the result is the difference of the subscripts of the two array elements. The type of the result is an implementation-defined signed integral type; this type shall be the same type that is defined as std::ptrdiff_t in the <cstddef> header
array ile kullanımı
pointer arithmetic'te pointer ve array gibi kullanım bazen karıştırılıyor.
char message[]="hello everyone";
char *pm =message;
pm [0] yapmak *(pm + 0) yapmak ile aynı şeydir.
&pm[0] yapmak ise pm ile aynı şeydir.

array ile negatif indeks
Pointer arithmetic dizilerde çok kullanılır. Dizinin dışına taşmadığımız müddetçe eksi indek kullanılabilir. Aşağıdaki kod doğru çalışır.
int main()
{
    int *p = new int[3];
    int *q = &p[2];
    q[-1] = 41;
    std::cout << p[1];
    delete[] p;
}
Member Field'lar
Pointer arithmetic member field'lar için de kullanılabilir.
struct A {
    int x, y;
}

A a;
int *px = &a.x, *py = &a.y
px ve py şöyle karşılaştırılabilir.
px > py

Diğer notlar

1. C memory modeline göre yaratılmış bir alanda p her zaman p+1'den küçüktür.
Is it possible, for a pointer variable p, that p<(p+1) is false?

Bu sorunun cevabı bir tek aşağıdaki kod için belirsiz olabilir.
p = reinterpret_cast<char*>(numeric_limits<uintptr_t>::max);
2. C memory modeline göre yaratılmamış iki pointer karşılaştırılamaz!

You can't legally compare arbitrary pointers in C/C++. The result of such comparison is not defined.

3. malloc ile ayrılan alandan farklı bir adrese gitmek undefined davranıştır. Örnek:
float a = malloc(size);
a--;
Pointer derefence edilmese bile davranış undefined'dır. Örnekte begin göstergeci dereference edilmiyor ancak sonuş yine de tanımsızdır.
const char* str = "abcdef";
const char* begin = str;
if (begin - 1 < str) { /* ... do something ... */ }

4. Ayrılan alanın bir eleman boyu kadar geçmek ise sorun teşkil etmez.

Unless both the pointer operand and the result point to a member of the same array object, or one past the last member of the array object, the behavior is undefined


kalloc()
Konuyu malloc ve türevleri yazısına taşıdım.

alloca
Konuyu malloc ve türevleri yazısına taşıdım.

malloc()
Konuyu malloc ve türevleri yazısına taşıdım.

calloc()
Konuyu malloc ve türevleri yazısına taşıdım.

realloc()
Konuyu malloc ve türevleri yazısına taşıdım.

new()

global operator new metodun 3 tane overload edilmiş hali var.

throwing (1)
void* operator new (std::size_t size) throw (std::bad_alloc);
nothrow (2)
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
placement (3)
void* operator new (std::size_t size, void* ptr) throw();

global operator new metodu

global operator new aşağıdaki gibi kullanılabilir.
MyClass *ptr = (MyClass*)::operator new (sizeof(MyClass));
Bir başka örnek
::operator new (10);
Bu durumda malloc gibi bellek ayırmaya yarar.

Biz Neden global ::operator new () şeklinde kodlamıyoruz?

Derleyici
new A();
şeklinde bir kod yazılınca, bizim arka planda kod üretiyor. Üretilen kod şu şekilde çalıışyor. Önce
::operator new (sizeof(A));
ile aynı malloc'taki gibi bellek alanı ayıran kod parçası ayrılır. Daha sonra da A nesnesinin constructor metodunu çağıran kod parçasını üretiyor.


Global operator new ile derleyicinin ürettiği kodun farkını gösteren örnek aşağıda.
// Allocates space for a T, and calls T's constructor,
// or calls a user-defined overload of new.
//
T* v = new T;

// Allocates space for N instances of T, and calls T's 
// constructor on each, or calls a user-defined overload
// of new[]
//
T* v = new T[N];

// Simply returns a raw byte array of `sizeof(T)` bytes.
// No constructor is invoked.
//
void* v = ::operator new(sizeof(T));

Üretilen kod parçası virtual memory biterse std::bad_alloc exception'ı atar. Genelde de aşağıdaki mesaj ile uygulama sonlanır.

terminate called after throwing an instance of std::bad_alloc
what(): std::bad_alloc
Aborted(core dumped)

exception atmayan new
new operator kullanımı yazısına taşıdım.

placement new
new operator kullanımı yazısına taşıdım.

placement delete
Böyle bir operator yok!. Dolayısıyla placement new ile yaratılan nesnenin destructor'ını elle çağırmak gerekir. delete veya delete[] kullanılmamalı!

kendi new operatörümüz
operator new()'i override etmek için tek yapmamız gereken buradaki metod imzalarından farklı metodlar yaratmak.

Buradaki soruda benzer bir açıklama var.
There is really no such thing as “overriding” global operator new. You can overload it by supplying a function which takes additional arguments beyond the required size_t first argument, and you can provide a class with its own operator new. 
Aşağıda macro kullanarak kendi new operatörümüzü çağırma örneği var.
#define MY_NEW (CONSTRUCTOR) (new (__FILE__,__LINE__) CONSTRUCTOR)

void* operator new (size_t sizeInBytes,const char* file,int line){
  void* buffer = //get from pool
  return buffer;
}

delete
delete veya delete[] eğer silinen şey bir nesne ise yani POD (C struct'ı gibi düşünülebilir) değilse, nesnenin destructor'ının çağırılmasını sağlar.

free
free() veya delete() çağrıları, uygulamanın kullandığı resident bellek alanını hemen düşürmez. Bu konu kafa karıştırıyor, insanlarda free() veya delete() çağrısından hemen sonra resident bellek alanının düşmesi beklentisi var.


DO1-78B ve Dinamik Bellek Kullanımı

Buradaki yazıda dinamik bellek kullanımını kontrol etmek için bellek ayıran  metodların override edildiği anlatılmış.
Developers can head off risks by taking away responsibility for memory management in safety-critical tasks from malloc and free and assigning it to the application. The developer replaces the standard allocator with a custom allocator that sets aside a buffer for the exclusive use of that task, and satisfies all memory allocation requests out of that buffer. If the memory reserved for this buffer is exhausted, the application is informed, and can then free up memory within the buffer or find more memory to devote to the task. Exhausting the memory in this dedicated pool has no impact on other parts of the system.
Memory Alignment
Bazen bellek adresinin belli bir sayının katı olması istenir. Bu durumda posix_memalign() kullanılabilir. Örneğin posix_memalign(&p, 32, 128) 128 byte büyüklüğünde ve başlangıç adresi 32'nin katı olan bir bellek alanı döner. Linux'ta memalign posix_memalign ile aynı işi yapar.

Bu metodun Windows'taki karşılığı _aligned_malloc() metodudur.
Aşağıdaki metodu her ikisinde de çalışır.
#if defined(_WIN32)
  return _aligned_malloc(m_Size, m_Alignment);
#else
  void* mem;
  int rs = posix_memalign(&mem, m_Alignment, m_Size);
  return rs == EOK ? mem : NULL;
#endif


Bazen de bellek alanının belli bir sayının katı olması istenir. Bu durumla ilgili olarak Data Alignment başlıklı yazıya bakabilirsiniz.


Hiç yorum yok:

Yorum Gönder