operator new etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
operator new etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

5 Kasım 2020 Perşembe

operator new - Sınıf İçin (Raw Bellek Alanı)

Giriş
Sınıf için operator new() metodunu override etmek mümkün. Böylece sınıf için custom allocator yazılabilir. Bir kere bile bu işi yapmam gerekmedi. 

Örnek
Sınıf operator  new istenirse şöyle yaparız.
class Foo
{
public:
  void* operator new( size_t );
}
Peki Bu Bize Neden Lazım
Genellikle sınıf için ayrılan bellek alanını takip etmek istersek kullanırız. Elimizde şöyle bir kod olsun
//The `Traceable` struct is used as a base class
//for any object which should be managed by GC.
struct Traceable {
  ObjectHeader *getHeader() { return traceInfo.at(this); }
 
  static void *operator new(size_t size) {
    // Allocate a block:
    void *object = ::operator new(size);
 
    // Create an object header for it:
    auto header = new ObjectHeader{.marked = false, .size = size};
    traceInfo.insert(std::make_pair((Traceable *)object, header));
 
    // Result is the actual block:
    return object;
  }
};
Bu kodda sınıfa bellek alanı ayrılır ve bu bellek alanı bir başka veriyapısı (bu örnekte bir std::map) içinde takip edilir.

Diğer Örnekler
Örnek - Ata Sınıf
Eğer ata sınıf operator new metodunu sunuyorsa kalıtımda da bu operator devreye girer. Örnekte Derived sınıfı Base sınıfın operator new metodunun farklı bir size_t ile çağrılmasına sebep olur.
struct Base
{
  void* operator new (size_t s) { ... }
  int a;
};

struct Derived : public Base
{
  int b;
};

Derived* d = new Derived; 
Örnek
Elimizde şöyle bir kod olsun.
class Test
{
public:
  virtual int testCall() { return 0; };

  static void* operator new(std::size_t);
};

class TestImpl : public Test
{
  virtual int testCall() override
  {
    return i;
  }

  int i = 15;
};

void* Test::operator new(size_t sz)
{
  return ::new TestImpl();
}

void main()
{
  Test * t = new Test(); // Call the new operator, correctly
  int i = test->testCall(); // i == 0 and not 15
}
Açıklaması şöyle.
Note that for every new expression, the two following things will be performed:
1. allocate memory via appropriate operator new.
2. construct the object on the memory allocated by step#1.

So operator new only allocates memory, doesn't construct the object. That means, for Test * t = new Test();, still a Test will be constructed, on the memory allocated by the overloaded operator new; even you constructed a TestImpl inside the operator new, but it'll be overwritten on the same memory soon, after the operator new finished.


30 Mayıs 2019 Perşembe

operator new - Raw Bellek Alanı

Giriş
operator new global ve sınıf için olmak üzere ikiye ayrılır. Global operator new metodunu çağırmak için şöyle yaparız
::new T();
Sınıf için olanı (eğer varsa) çağırmak için şöyle yaparız
new T();
Global Operator New Nasıl Çalışır
Effective C++ 55'e göre operator new şöyle çalışır.
When operator new is unable to fulfill a memory request, it calls the new-handler function repeatedly until it can find enough memory.

A well-designed newhandler function must do one of the following:
  • Make more memory available.
  • Install a different new-handler.
  • Deinstall the new-handler
  • Throw an exception
  • Not return
pseudo kod olarak metod şuna benzer.
while (true) {
  //attempt to allocate size bytes
  if (the allocation was successful) 
     return (a pointer to the memory);
  // allocation was unsuccessful; find out what the
  // current new-handling function is 
  new_handler globalHandler = set_new_handler(0);
  set_new_handler(globalHandler);
  if (globalHandler) (*globalHandler)();
  else throw std::bad_alloc();
}
new_handler bir function pointer olarak tanımlıdır. Gerçek kod ise  şöyle. Tüm algoritmanın nasıl çalıştığı şöyle anlatılıyor. Açıklaması şöyle
In case of failure, the standard library implementation calls the function pointer returned by std::get_new_handler and repeats allocation attempts until new handler does not return or becomes a null pointer, at which time it throws std::bad_alloc.
Thread Safe
Global operator new thread safe. Açıklaması şöyle.
For purposes of determining the existence of data races, the library versions of operator new, user replacement versions of global operator new, the C standard library functions aligned_­alloc, calloc, and malloc, the library versions of operator delete, user replacement versions of operator delete, the C standard library function free, and the C standard library function realloc shall not introduce a data race ([res.on.data.races]). Calls to these functions that allocate or deallocate a particular unit of storage shall occur in a single total order, and each such deallocation call shall happen before the next allocation (if any) in this order.
Operator New ve void *
void * C++'ta pek kullanılmaz. operator new bu kullanım yerlerinden bir tanesi.

Operator New'e Geçilen size_t Büyüklüğü
Kurallar şöyle.

1. Derleyici sınıf için sizeof(T) geçer. Bellek ayıran kod parçası yani operator new belleği yönetebilmek için talep edilenden biraz daha büyük bir alan ayırır.

2. Derleyici sınıf dizisi için N * sizeof(T)'den biraz daha büyük bir alan geçer. Buradaki amaç dizi içinde kaç tane nesne olduğunu takip etmek ve dolayısıyla kaç defa destructor çağrılacağını bulmaktır. Bellek ayıran kod parçası yani operator new belleği yönetebilmek için yine talep edilenden biraz daha büyük bir alan ayırır.
When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array.
Elimizde şöyle bir sınıf olsun ve array için operator new metodunu override etmiş olalım. Sınıfın destructor metodu olduğuna dikkat!
struct foo {
  static void* operator new[](std::size_t sz) {
    return malloc(sz); //0x...16FO döndürür
  }
  static void operator delete[](void* ptr) {
    free(ptr);
  }
  virtual ~foo() {}
};
Foo sınıfını şöyle çağıralım.
new foo[10]; //0x...16F8 alırım
Bu çağrı sonucunda sz parametresinin 10 tanelik bir dizi için gereken 80 byte'tan daha büyük bir değer ile çağrıldığını görebiliriz. Yani derleyici kendisi için kullanmak üzere 80 byte'tan daha büyük bir alan talep etmiştir.

Sınıf için Operator New
operator new - Sınıf İçin (Raw Bellek Alanı) yazısına taşıdım.

Sınıf İçin Operator Delete
Şöyle yaparız.
class A
{
public:
  void* operator new(std::size_t sz);
  void operator delete(void* ptr);
};
Bir sınıf için operator delete metodunun boş olması bu sınıfın belleğinin halen kullanılabileceği anlamına gelmez. Elimizde şöyle bir sınıf olsun
struct Foo {
  int a;
  static void operator delete(void* ptr) {}
  Foo(): a(5) {}
  ~Foo() { std::cout << "Destructor called\n"; }
  void doSomething() { std::cout << __PRETTY_FUNCTION__ << "a = " << a << " called\n"; }
};
Bu sınıfı şöyle kullanalım
Foo* foo = new Foo();
delete foo;
foo->doSomething(); // safe?
Çıktı olarak şunu alırız. Çunkü sınıfın destructor'ı bir kere çağrılırsa tüm alanları için de destructor çağrılır. Bu durumda alanlara erişmek undefined behavior'a (UB) sebep olur.
Destructor called
void Foo::doSomething() a= 566406056 called
Sınıf için Operator New Array
Şöyle yaparız
struct foo {
  void* operator new[](std::size_t sz) {
    ...
  }
  static void operator delete[](void* ptr) {
    ...
  }
};
Global Operator New Overload
Bir kere bile bu işi yapmam gerekmedi. Metod imzaları şöyle. Hem operator new hem de karşılık gelen delete'leri verdim.
void* operator new (std::size_t size) throw (std::bad_alloc);
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
void* operator new (std::size_t size, void* ptr) throw();
void* operator new[] (std::size_t size) throw (std::bad_alloc);
void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) throw();
void* operator new[] (std::size_t size, void* ptr) throw();

void operator delete (void* ptr) throw();
void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) throw();
void operator delete (void* ptr, void* voidptr2) throw();
void operator delete[] (void* ptr) throw();
void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) throw();
void operator delete[] (void* ptr, void* voidptr2) throw();
Global Operator New Overload
global operator new ise şöyle yazılırak override edilir. blah blah yazan yere istenen tip konulur.
void* operator new (std::size_t size, optional blah blah here) blah blah here
Bu işi bir kere yaptım. Kendi yönettiğimiz bellek alanına erişmek için yeni bir operator new yazdık. blah yerine de yine dummy bir sınıf kullandık. 


Array İçin Global Operator New Overload
Bir kere bile bu işi yapmam gerekmedi. Eğer array için new override edilirse, delete de override edilmeli. Metod imzaları şöyle olabilir.
void* operator new[] (std::size_t size) throw (std::bad_alloc);
void* operator new[] (std::size_t size, const std::nothrow_t&nothrow_value)throw()
void* operator new[] (std::size_t size, void* ptr) throw();

void operator delete[] (void* ptr) throw();
void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant)throw();
void operator delete[] (void* ptr, void* voidptr2) throw();
Ya da şöyle olabilir..
void* operator new[](std::size_t size);
void operator delete[](void* ptr) noexcept;
void operator delete[](void* ptr, std::size_t size) noexcept;
Derleyicisine göre denemek gerekiyor.