Deviously Playing with Memory

When a buffer is void, its contents are treated as raw memory, not assigned to any specific data type. This ambiguity means your code can cast the memory’s data type and do interesting things with it.

To get data into a void buffer, use the memcpy() function, which was introduced in last week’s Lesson.

For example, say buffer is a void pointer: void *buffer. The malloc() function allocates storage as void by default, so you can easily set aside a memory chunk and assign it to buffer easy-peasy:

buffer = malloc( sizeof(struct person) );

To access the contents of void buffer, however, it’s best to use a specific data type. For example, create an unsigned char pointer bufchar. Assign its address to the void buffer pointer:

bufchar = buffer;

At this point, you can use the bufchar pointer to peer into memory, viewing the byte values directly, which is what I’ve done in the following code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    struct person {
        int age;
        char name[12];
    } man = { 34, "George" };
    void *buffer;
    unsigned char *bufchar;
    int x;

    /* output the structure */
    printf("%s is %d years old\n",man.name,man.age);

    /* allocate the void buffer */
    buffer = malloc( sizeof(struct person) );
    if( buffer==NULL )
    {
        fprintf(stderr,"Unable to allocate memory\n");
        exit(1);
    }

    /* copy memory */
    memcpy(buffer,&man,sizeof(struct person));
    
    /* dump the buffer */
    puts("Buffer dump:");
        /* reference (void)buffer as (char)bufchar */
    bufchar = buffer;
    for( x=0; x<(int)sizeof(struct person); x++ )
    {
        printf(" %02X",*(bufchar+x));
    }
    putchar('\n');

    return(0);
}

The person structure man is copied into the void buffer at Line 27. At Line 32, unsigned char pointer bufchar is assigned the same address as buffer. The contents of buffer are then dumped as unsigned char hexadecimal digits, effectively peering into buffer at a raw-byte level. Here’s the output:

George is 34 years old
Buffer dump:
 22 00 00 00 47 65 6F 72 67 65 00 00 00 00 00 00

The buffer consists of two members: The first four bytes are the integer member age, the next 12 bytes store the name string, “George,” which occupies only the first seven bytes of this storage. This allocation is illustrated in Figure 1.

Figure 1. Memory allocation for the person structure variable man, shown as unsigned char hex values.

Because the data types and their offsets within the void buffer are known, you can access them directly. To access the integer portion, an int pointer bufint can be used. This pointer would be initialized to the start of buffer, which is where the integer value dwells:

bufint = (int)buffer;

You can re-use the bufchar pointer to access the string. Its location is at offset 4 (the fifth byte) in buffer:

bufchar = (char *)buffer+4;

The final part of this code update shows the modifications:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    struct person {
        int age;
        char name[12];
    } man = { 34, "George" };
    void *buffer;
    int *bufint;
    char *bufchar;
    int x;

    /* output the structure */
    printf("%s is %d years old\n",man.name,man.age);

    /* allocate the void buffer */
    buffer = malloc( sizeof(struct person) );
    if( buffer==NULL )
    {
        fprintf(stderr,"Unable to allocate memory\n");
        exit(1);
    }

    /* copy memory */
    memcpy(buffer,&man,sizeof(struct person));
    
    /* dump the buffer */
    puts("Buffer dump:");
        /* reference (void)buffer as (char)bufchar */
    bufchar = buffer;
    for( x=0; x<(int)sizeof(struct person); x++ )
    {
        printf(" %02X",*(bufchar+x));
    }
    putchar('\n');

    /* sneak into the buffer */
        /* reference (void)buffer as (int)bufint */
    bufint = (int *)buffer;
        /* reference (void)buffer (offset 4) as (char)bufchar */
    bufchar = (char *)buffer+4;
        /* data can now be accessed directly */
    printf("Int portion: %d\n",*(bufint));
    printf("String portion: %s\n",bufchar);

    return(0);
}

And here’s the output:

George is 34 years old
Buffer dump:
 22 00 00 00 47 65 6F 72 67 65 00 00 00 00 00 00
Int portion: 34
String portion: George

Because the memcpy() function duplicates the structure into a void buffer, other pointers of specific data types can access the void data, interpreting it accordingly.

This type of memory manipulation isn’t without risk: Accessing unknown (void) buffers with any old data type can lead to unpredictable results. Ensure that you know the data you’re accessing and, should you want to manipulate it, that your actions result in safe and secure coding practices.

2 thoughts on “Deviously Playing with Memory

  1. In this line of code

    bufchar = (char *)buffer+4;

    can you use ++ instead of +4 to automatically calculate the correct number of bytes to add, like you can with an array?

  2. Yes, but you lose the base address of buffer. Further, because buffer is a void, I believe it would be incremented only one byte. Now if you did (int *)buffer++;, it might increment four bytes (the size of an int), but I’d have to run a test to see if that’s the case. If so, it would be truly interesting.

Leave a Reply