Scarh
Scarh

Reputation: 29

dynamic memory allocation with structs

it seems like i did not really understand how the memory allocation with pointers works.

Quick example:

I got a structure,

    struct Friends{
    char *firstname; 
    char *lastname;
    };

if I do allocate memory now, it will get me

    2x sizeof(char) = 2x 1Byte

but, doesnt the free memory I need depend on how many characters I fill it with?

example:

    char array[10] needs 10x sizeof(char), 1byte for each character?

Everywhere I look they allocate the memory, before they know how much they will fill the structure with.

Upvotes: 2

Views: 5256

Answers (5)

John Bode
John Bode

Reputation: 123468

So with types like yours above, you have basically a two-step allocation process:

  1. First, you allocate an object of type struct Friends, which contains space for two pointers;

  2. Second, you allocate memory for the objects that each member of struct Friends will point to.

Quick and dirty example:

struct Friends *addFriend( const char *firstName, const char *lastName )
{
  /**
   * First, allocate an instance of `struct Friends`, which will contain
   * enough space to store two pointers to `char`
   */
  struct Friends *f = malloc( sizeof *f ); // sizeof *f == sizeof (struct Friends)

  if ( f )                                 
  {
    /**
     * Allocate space to store a *copy* of the contents of the firstName
     * parameter, assign the resulting pointer to the firstname member of
     * the struct instance.
     */
    f->firstname = malloc( strlen( firstName ) + 1 ); 
    if ( f->firstname )
      strcpy( f->firstname, firstName );

    /**
     * Do the same for lastName
     */
    f->lastName = malloc( strlen( lastName ) + 1 );
    if ( f->lastname )
      strcpy( f->lastname, lastName );
  }
  return f;
}

If we call this function as

struct Friends newFriend = addFriend( "John", "Bode" );

we get something like the following in memory:

           +---+                           +---+      +---+---+---+---+---+
 newFriend:|   | --> newFriend->firstname: |   | ---->|'J'|'o'|'h'|'n'| 0 |
           +---+                           +---+      +---+---+---+---+---+
                     newFriend->lastname:  |   | -+
                                           +---+  |   +---+---+---+---+---+
                                                  +-->|'B'|'o'|'d'|'e'| 0 |
                                                      +---+---+---+---+---+

Here's how it plays out on my system:

                Item        Address   00   01   02   03
                ----        -------   --   --   --   --
           newFriend 0x7fffe6910368   10   20   50   00    ..P.
                     0x7fffe691036c   00   00   00   00    ....

          *newFriend       0x502010   30   20   50   00    0.P.
                           0x502014   00   00   00   00    ....
                           0x502018   50   20   50   00    P.P.
                           0x50201c   00   00   00   00    ....

newFriend->firstname       0x502030   4a   6f   68   6e    John

 newFriend->lastname       0x502050   42   6f   64   65    Bode

The newFriend pointer variable lives at address 0x007fffe6910368. It points to an object of type struct Friends which lives at address 0x502010. This object is large enough to store two pointer values; newFriend->firstname lives at address 0x5020101 and points to the string "John", which lives at address 0x502030. newFriend->lastname lives at address 0x502018 and points to the string "Bode", which lives at address 0x502050.

To deallocate, you free the members before freeing the struct object:

void deleteFriend( struct Friends *f )
{
  free( f->firstname );
  free( f->lastname );
  free( f );
}

It doesn't matter what order you free f->firstname and f->lastname with respect to each other; what matters is that you have to delete both of them before you can delete f. Freeing f won't free what f->firstname and f->lastname point to2.

Note that this all assumes I'm being given data with a known size (the firstName and lastName parameters in addFriend); I use the lengths of those input strings to determine how much space I need to allocate.

Sometimes you do not know ahead of time how much space you need to set aside. The usual approach is to allocate some initial amount of storage and to extend it as necessary using the realloc function. For example, suppose we have code to read a single line of text terminated by a newline character from an input stream. We want to be able to handle lines of any length, so we start out by allocating enough space to handle most cases, and if we need more, we double the size of the buffer as necessary:

size_t lineSize = 80;  // enough for most cases
char *line = calloc( lineSize, sizeof *line );
char buffer[20]; // input buffer for reading from stream

/** 
 * Keep reading until end of file or error.
 */
while ( fgets( buffer, sizeof buffer, stream ) != NULL )
{
  if ( strlen( line ) + strlen( buffer ) >= lineSize )
  {
    /**
     * There isn't enough room to store the new string in the output line,
     * so we double the output line's size.
     */
    char *tmp = realloc( line, 2 * lineSize );
    if ( tmp )
    {
      line = tmp;
      lineSize *= 2;
    }
    else
    {
      /**
       * realloc call failed, handle as appropriate.  For this
       * example, we break out of the loop immediately
       */
      fprintf( stderr, "realloc error, breaking out of loop\n" );
      break;
    }
  }
  strcat( line, buffer );

  /**
   * If we see a newline in the last input operation, break out of the
   * loop.
   */
  if ( strchr( buffer, '\n' ) )
    break;
}


1. The address of the first element of a struct is the same as the address of the whole struct object; C doesn't store any kind of metadata before the first struct member.

2. Note that you can only call free on an object that was allocated with malloc, calloc, or realloc; you would not call free on a pointer that points to a string literal or another array.

Upvotes: 1

David C. Rankin
David C. Rankin

Reputation: 84561

Everywhere I look they allocate the memory, before they know how much they will fill the structure with.

Yes and no.

struct Friends {
    char *firstname; 
    char *lastname;
};

It all depends on how you intend to use your structure. There are multiple ways you can use struct Friends. You can declare a static instance of the struct, and then simply assign the address of existing strings to your firstname and lastname member pointers, e.g.:

int main (void) {

    Struct Friends friend = {{Null}, {NULL]};

    /* simple assignment of pointer address
     * (memory at address must remain valid/unchanged)
     */
    friend.firstname = argc > 1 ? argv[1] : "John";
    friend.lastname = argc > 2 ? argv[2] : "Smith";

    printf ("\n name: %s %s\n\n", friend.firstname, friend.lastname);

However, in most cases, you will want to create a copy of the information and store a copy of the string for the firstname and lastname members. In that case you need to allocate a new block of memory for each pointer and assign the starting address for each new block to each pointer. Here you now know what your strings are, and you simply need to allocate memory for the length of each string (+1 for the nul-terminating character) e.g.:

int main (int argc, char **argv) {

    /* declare static instance of struct */
    struct Friends friend = {NULL, NULL};

    char *first = argc > 1 ? argv[1] : "John";
    char *last = argc > 2 ? argv[2] : "Smith";

    /* determine the length of each string */
    size_t len_first = strlen (first);
    size_t len_last = strlen (last);

    /* allocate memory for each pointer in 'friend' */
    friend.firstname = malloc (len_first * sizeof *friend.firstname + 1);
    friend.lastname  = malloc (len_last * sizeof *friend.lastname + 1);

You then need only copy each string to the address for each member:

    /* copy names to new memory referenced by each pointer */
    strcpy (friend.firstname, first);
    strcpy (friend.lastname, last);

Finally, once you are done using the memory allocated, you must free the memory with free. note: you can only free memory that you have previously allocated with malloc or calloc. Never blindly attempt to free memory that has not been allocated that way. To free the members, all you need is:

    /* free allocated memory */
    free (friend.firstname);
    free (friend.lastname);

A short example putting all the pieces together is:

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

struct Friends {
    char *firstname;
    char *lastname;
};

int main (int argc, char **argv) {

    /* declare static instance of struct */
    struct Friends friend = {NULL, NULL};

    char *first = argc > 1 ? argv[1] : "John";
    char *last = argc > 2 ? argv[2] : "Smith";

    /* determine the length of each string */
    size_t len_first = strlen (first);
    size_t len_last = strlen (last);

    /* allocate memory for each pointer in 'friend' */
    friend.firstname = malloc (len_first * sizeof *friend.firstname + 1);
    friend.lastname  = malloc (len_last * sizeof *friend.lastname + 1);

    /* copy names to new memory referenced by each pointer */
    strcpy (friend.firstname, first);
    strcpy (friend.lastname, last);

    printf ("\n name: %s %s\n\n", friend.firstname, friend.lastname);

    /* free allocated memory */
    free (friend.firstname);
    free (friend.lastname);

    return 0;
}

Always compile with warnings enabled, for example:

gcc -Wall -Wextra -o bin/struct_dyn_alloc struct_dyn_alloc.c

(if not using gcc, then your compiler will have similar options)

Anytime you dynamically allocate memory in your code, run in though a memory error checking program to insure you are not misusing the allocated blocks of memory in some way, and to confirm all memory has been freed. It is simple to do. All OS's have some type of checker. valgrind is the normal choice on Linux. For example:

$  valgrind ./bin/struct_dyn_alloc
==14805== Memcheck, a memory error detector
==14805== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==14805== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==14805== Command: ./bin/struct_dyn_alloc
==14805==

 name: John Smith

==14805==
==14805== HEAP SUMMARY:
==14805==     in use at exit: 0 bytes in 0 blocks
==14805==   total heap usage: 2 allocs, 2 frees, 11 bytes allocated
==14805==
==14805== All heap blocks were freed -- no leaks are possible
==14805==
==14805== For counts of detected and suppressed errors, rerun with: -v
==14805== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Upvotes: 2

rootkea
rootkea

Reputation: 1484

Everywhere I look they allocate the memory, before they know how much they will fill the structure with.

If I understood properly you are asking how we can allocate memory before they know how much space we gonna use.

Consider the following structure:

struct foo
{
    char bar;
    char foobar;
};

struct foo *temp = (struct foo *) malloc(sizeof(struct foo));  

Here 2 * 1 = 2 bytes will be allocated dynamically whether you store something in bar and foobar variables or not.

If you don't store anything then the variable (memory location) contains grabage value.

Upvotes: 0

Danny_ds
Danny_ds

Reputation: 11406

I got a structure,

struct Friends{
char *firstname; 
char *lastname;
};

if I do allocate memory now, it will get me

2x sizeof(char) = 2x 1Byte

Instead of char's, you are allocating pointers here sizeof(char *). Depending on the system, those are 4 bytes (32-bit) or 8 bytes (64-bit) each.

So, if you want data where your pointers point to, you will have to allocate that, with malloc() for example, and free() them later.

Or do something like:

struct Friends{
    char firstname[20]; 
    char lastname[20];
};

But make sure your strings end with a \0 char.

Upvotes: 3

Sourav Ghosh
Sourav Ghosh

Reputation: 134336

If i understood your problem correctly, you want to see the memory allocated for firstname and lastname based on your requirement of the future storage.

I'm afraid, that is not possible.

When you got a variable of type struct Friends, say struct Friends F;, F.firstname and F.lastname will be pointers but they will not be pointing to any valid memory. You need to perform the allocation for F.firstname and F.lastname separately. Something like

F.firstname = malloc(32);
F.lastname = malloc(32);   //perform NULL check too

and then you can actually make use of F.firstname and F.lastname

Upvotes: 0

Related Questions