devxlogo

Using a Good Parasite Class to Design a Self-Clearing Memory Buffer

Using a Good Parasite Class to Design a Self-Clearing Memory Buffer

any applications use a loop that fills a raw memory buffer with data, processes that data, and then clears that buffer before the next iteration. Standard containers such as vectoraren’t suitable for representing such a memory buffer. This 10-Minute Solution shows a revolutionary technique for implementing a secure, automatic raw memory buffer that clears itself.

The Self-Recycling Buffer Problem

Your mobile carrier’s SMS server, mail client, word processor, and database engine are instances of software applications that use a main loop (known as a message queue). They use these message queues for reading data from an input source into a local memory buffer for further processing. When the processing is done, the applications clear the raw buffer (meaning all bytes are set to binary zeros) before the next loop iteration takes place.

In these types of applications, using a standard container or a smart pointer to implement the raw memory buffer isn’t a good design choice. For example, this loop works but it’s very inefficient:

vector client_sms_buffer(161);for(;;) //main processing loop{  read_data_from_client(client_sms_buffer);  transfer_message(client_sms_buffer);  client_sms_buffer.clear();//actually deallocates memory  client_sms_buffer.resize(161); //allocate a new buffer}

Vector::clear() doesn’t really clear the vector’s buffer; it destroys the buffer. Consequently, after every clear() call you need to allocate a new buffer of the same size using resize(). This is as silly as buying a new car every time the gas tank gets empty!

Instead of deallocating and allocating a new buffer, you can use quick and dirty workarounds like this one for clearing the same buffer:

  process_message(client_sms_buffer);  for(auto it=client_sms_buffer.begin(),      it < client_sms_buffer.end(),       it++)         *it=[0]; //zero every byte in the buffer

However, this is tedious and inefficient because the for-loop clears every byte individually.

As you can see, STL containers are a bad choice for representing a self-recycling memory buffer.


How do I avoid allocating a new chunk of memory on every loop iteration?


Using the "good parasite class" idiom, implement a single raw memory buffer that knows how to clear itself securely.

Author's Note: Click here for answers to frequently asked questions about the good parasite class.

memset() Blues

Without STL containers getting in your way, the main processing loop might look like a good 1970s C textbook:

char sms_buffer[161]={0};for(;;) //main processing loop{  read_data_from_client(sms_buffer);  process_message(sms_buffer);  memset(sms_buffer, 0, 161); //clear all bytes}

Calling memset() directly in C++ code is a faux pas because passing the size of the buffer on every memset()call is a security hazard. Ideally, you want the buffer to clear itself.

Enter the Good Parasite class!

A Good Parasite

A proper solution to the self-clearing buffer requires an interface with the dual attributes of a low-level char array and the qualities of a high-level C++ class. Why use a class instead of a function? Because a class's constructor is invoked automatically, and that constructor is where the buffer clearing will take place.

How will that class keep track of the buffer's size and address? Simple: both the class and the buffer live at the same address! So you monitor the buffer's size by bundling the size into the class's type?that is, you use a template. Bundling the size into the specialization has another advantage: the class will not contain any data members at all. This is crucial for your design.

First, consider how to bundle the buffer's size into the class's type. This Solution uses a template non-type parameter:

#include  //for overriding new and placement new#include  //for memsettemplate  class Zeroer//template arguments are int{private: void * operator new(unsigned int)=delete; Zeroer(const Zeroer&)=delete; Zeroer& operator=(const Zeroer&)=delete; ~Zeroer()=delete;//..};

Because clients are not allowed to create Zeroer objects directly or to copy them, the assignment operator, copy constructor, destructor, and overridden operator new are declared as private deletedfunctions.

You can instantiate a Zeroer object only by using placement new. This will ensure that a Zeroer object is constructed on the buffer's address. Notice that you need to override the global placement new(as shown in the Zeroer definition below).

Finally, look at the constructor. Every time you create a Zeroer object, its constructor automatically clears the buffer. Here's the complete definition of Zeroer:

template  class Zeroer{private: //disabled operations  void * operator new(unsigned int)=delete; Zeroer(const Zeroer&)=delete; Zeroer& operator=(const Zeroer&)=delete; ~Zeroer()=delete;public: Zeroer(){ memset(this, 0, N); } //clear the buffer  void* operator new(unsigned int n, char* p) {return p; }};

Here is Zeroer in action:

int main(){ char buff[512]; for(;;) {  new (buff) Zeroer<512>; //Zeroer ctor clears buff  strcpy(buff,"hello"); //fill the buffer with data  new (buff) Zeroer<512>; //clear the buffer again  strcpy(buff,"world"); //fill the buffer again }}

Zeroer objects are never destroyed because they have nothing to destroy. They don't own the buffer; they merely "iron" it after every use. Every time you want to clear buff, you construct a new instance of Zeroer<512> on top of buff using placement new. In real world code, constants and typedefs will eliminate the use of hard-coded numbers:

const int PAGE_SIZE=512;typedef Zeroer ZP;int main(){ Zeroer<52> z; //error, destructor is inaccessible new Zeroer<1024> ; // error, new is inaccessible   //handle two different buffers at once char buff[PAGE_SIZE];  char sms_buff[161];  //process and recycle new (sms_buff) Zeroer<161>; //clear sms_buffer new (buff) ZP; //clear buff strcpy(buff,"hello");   new (buff) ZP; //clear buff again strcpy(sms_buffer,"call me");   new (sms_buffer) Zeroer<161>; //clear sms_buffer again}

Parasite's Strange but Useful Behavior

A good parasite class like Zeroer almost looks like a bug. Its peculiar design and usage protocol stands out in several aspects:

  • It has no memory footprint at all because it lives at the same address as the buffer (technically, sizeof(Zeroer<512> isn't zero but the buffer and Zeroer overlap).
  • You can instantiate Zeroer only by using placement new.
  • Zeroer objects are never destructed.
  • The information that Zeroer needs is welded into the class itself: its this pointer is equivalent to buffer's address, and the non-type template argument N is the buffer's size.

It may be unusual, but it works. In fact, the good parasite class idiom presented here has plenty of potential applications. Take for example a relational database query that opens a cursor and scrolls down the records that fit the query. You can represent the record key as a template non-type argument. The current record will be represented as a raw memory buffer. The good parasite's constructor will fetch the next record and store it in the buffer when the user hits Next.

Take another example: a media player that displays a variety of fixed-length video clips, where each clip is written to the same recycled buffer before it's displayed. Admittedly, there are other, more traditional ways to accomplish these tasks but the good parasite idiom can be another useful technique you may consider instead of automatically representing everything as vectors.

Author's Note: Click here for answers to frequently asked questions about the good parasite class.
devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist