Sunday, September 4, 2022

implement sizeof, malloc,memcpy, memmove, memset,

size of implementation

#include<stdio.h>
#define my_sizeof(type) (char *)(&type+1)-(char*)(&type)
int main()
{
	double x;
	printf("%ld", my_sizeof(x));
	getchar();
	return 0;
}
malloc implementation
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>


void *malloc(size_t size) {
  void *p = sbrk(0);
  void *request = sbrk(size);
  if (request == (void*) -1) {
    return NULL; // sbrk failed.
  } else {
    assert(p == request); // Not thread safe.
    return p;
  }
}
Reference: https://moss.cs.iit.edu/cs351/slides/slides-malloc.pdf

malloc(0) :

alloc(0) could return NULL or a valid pointer that may not be dereferenced. In either case, it's perfectly valid to call free() on it.
memcpy implementation
// Copies "numBytes" bytes from address "from" to address "to"
void * memcpy(void *to, const void *from, size_t numBytes);
// A C implementation of memcpy()
#include<stdio.h>
#include<string.h>

void myMemCpy(void *dest, void *src, size_t n)
{
// Typecast src and dest addresses to (char *)
char *csrc = (char *)src;
char *cdest = (char *)dest;

// Copy contents of src[] to dest[]
for (int i=0; i<n; i++)
	cdest[i] = csrc[i];
}

// Driver program
int main()
{
char csrc[] = "GeeksforGeeks";
char cdest[100];
myMemCpy(cdest, csrc, strlen(csrc)+1);
printf("Copied string is %s", cdest);

int isrc[] = {10, 20, 30, 40, 50};
int n = sizeof(isrc)/sizeof(isrc[0]);
int idest[n], i;
myMemCpy(idest, isrc, sizeof(isrc));
printf("\nCopied array is ");
for (i=0; i<n; i++)
	printf("%d ", idest[i]);
return 0;
}

memmove implementation
// Copies "numBytes" bytes from address "from" to address "to"
void * memmove(void *to, const void *from, size_t numBytes);
//C++ program to demonstrate implementation of memmove()
#include<stdio.h>
#include<string.h>

// A function to copy block of 'n' bytes from source
// address 'src' to destination address 'dest'.
void myMemMove(void *dest, void *src, size_t n)
{
// Typecast src and dest addresses to (char *)
char *csrc = (char *)src;
char *cdest = (char *)dest;

// Create a temporary array to hold data of src
char *temp = new char[n];

// Copy data from csrc[] to temp[]
for (int i=0; i<n; i++)
	temp[i] = csrc[i];

// Copy data from temp[] to cdest[]
for (int i=0; i<n; i++)
	cdest[i] = temp[i];

delete [] temp;
}

// Driver program
int main()
{
char csrc[100] = "Geeksfor";
myMemMove(csrc+5, csrc, strlen(csrc)+1);
printf("%s", csrc);
return 0;
}


memset implementation
void *memset(void *ptr, int x, size_t n);
void
*my_memset(void *s, int c, unsigned int len)
{
unsigned char* p=s;
while(len--)
{
*p++ = (unsigned char)c;
}
return s;

why we need mem move?

memmove() is similar to memcpy() as it also copies data from a source to destination. memcpy() leads to problems when source and destination addresses overlap as memcpy() simply copies data one by one from one location to another. For example consider below program. 

// Sample program to show that memcpy() can lose data.
#include <stdio.h>
#include <string.h>
int main()
{
char csrc[100] = "Geeksfor";
memcpy(csrc+5, csrc, strlen(csrc)+1);
printf("%s", csrc);
return 0;
}


The Difference

The difference between memcpy and memmove is simple: when the source and destination blocks of memory overlap (for example, if they are the same), memmove works, but memcpy‘s behavior is undefined. It might work. It might crash. It might corrupt data. It might behave differently during debugging.

memmove is safer.

memcpy can be faster, and usually is. There are less restrictions on it’s implementation, so more can be done to optimize it. But not necessarily a lot more — in fact, it could even be slower then memmove, and sometimes this is the case. On some systems, memcpy may just be memmove.

Faster

So how much faster can memcpy be then memmove? They both take O(N) time to copy N bytes of data. So in some computer-science circles, memcpy wouldn’t be considered faster then memmove.


Ref: https://vgable.com/blog/2008/05/24/memcopy-memmove-and-speed-over-safety/


Example#

A wide variety of standard library functions have among their effects copying byte sequences from one memory region to another. Most of these functions have undefined behavior when the source and destination regions overlap.

For example, this ...

#include <string.h> /* for memcpy() */

char str[19] = "This is an example";
memcpy(str + 7, str, 10);

... attempts to copy 10 bytes where the source and destination memory areas overlap by three bytes. To visualize:

               overlapping area
               |
               _ _
              |   |
              v   v
T h i s   i s   a n   e x a m p l e \0
^             ^
|             |
|             destination
|
source

Because of the overlap, the resulting behavior is undefined.

Among the standard library functions with a limitation of this kind are memcpy()strcpy()strcat()sprintf(), and sscanf(). The standard says of these and several other functions:

If copying takes place between objects that overlap, the behavior is undefined.

The memmove() function is the principal exception to this rule. Its definition specifies that the function behaves as if the source data were first copied into a temporary buffer and then written to the destination address. There is no exception for overlapping source and destination regions, nor any need for one, so memmove() has well-defined behavior in such cases.


Ref: https://riptutorial.com/c/example/2150/copying-overlapping-memory


Our OWN malloc:

copied from here:
https://danluu.com/malloc-tutorial/

Let’s write a malloc and see how it works with existing programs!
This tutorial is going to assume that you know what pointers are, and that you know enough C to know that *ptr dereferences a pointer, ptr->foo means (*ptr).foo, that malloc is used to dynamically allocate space, and that you’re familiar with the concept of a linked list. For a basic intro to C, Pointers on C is one of my favorite books. If you want to look at all of this code at once, it’s available here.
Preliminaries aside, malloc’s function signature is
void *malloc(size_t size);
It takes as input a number of bytes and returns a pointer to a block of memory of that size.
There are a number of ways we can implement this. We’re going to arbitrarily choose to use the sbrk syscall. The OS reserves stack and heap space for processes and sbrk lets us manipulate the heap. sbrk(0) returns a pointer to the current top of the heap. sbrk(foo) increments the heap size by foo and returns a pointer to the previous top of the heap.

Diagram of linux memory layout, courtesy of Gustavo Duarte.
If we want to implement a really simple malloc, we can do something like
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>


void *malloc(size_t size) {
  void *p = sbrk(0);
  void *request = sbrk(size);
  if (request == (void*) -1) {
    return NULL; // sbrk failed.
  } else {
    assert(p == request); // Not thread safe.
    return p;
  }
}
When a program asks malloc for space, malloc asks sbrk to increment the heap size and returns a pointer to the start of the new region on the heap. This is missing a technicality, that malloc(0) should either return NULL or another pointer that can be passed to free without causing havoc, but it basically works.
But speaking of free, how does free work? Free’s prototype is
void free(void *ptr);
When free is passed a pointer that was previously returned from malloc, it’s supposed to free the space. But given a pointer to something allocated by our malloc, we have no idea what size block is associated with it. Where do we store that? If we had a working malloc, we could malloc some space and store it there, but we’re going to run into trouble if we need to call malloc to reserve space each time we call malloc to reserve space.
A common trick to work around this is to store meta-information about a memory region in some space that we squirrel away just below the pointer that we return. Say the top of the heap is currently at 0x1000 and we ask for 0x400 bytes. Our current malloc will request 0x400 bytes from sbrk and return a pointer to 0x1000. If we instead save, say, 0x10 bytes to store information about the block, our malloc would request 0x410 bytes from sbrk and return a pointer to 0x1010, hiding our 0x10 byte block of meta-information from the code that’s calling malloc.

our own sizeof:
#define my_sizeof(type) (char *)(&type+1)-(char*)(&type)
int main()
{
    double x;
    printf("%d", my_sizeof(x));
    getchar();
    return 0;
}

No comments:

Post a Comment