Search code examples
cdynamic-arraysrealloc

How can realloc be used effectively in loops?


I'm new to c so sorry if the question might seem basic. I'm trying to read a string with unknown size from the user and store it in a dynamic array. This program is going to make accounts for users but I can't successfully scan the username or the password. Please note that no arrays with fixed size can be used. Any solutions using files can be used and also be really helpful.

void main(){
    user *dummyUser = (user *) malloc(sizeof(user));
    dummyUser->next=NULL; 
    char* password=(char* )malloc(sizeof(char));
    char* name=(char* )malloc(sizeof(char));
    int i=0;
    char c;
    char ptr;
    while((c=getchar())!=' '){
        name[i]=c;
        ptr=(char *)realloc(name,sizeof(char));
        if(ptr==NULL){
            printf("realloc failed");
            exit( EXIT_FAILURE );
        }
        i++;
    }
    i=0;
    while((c=getchar())!='\n'){
        password[i]=c;
        password=(char *)realloc(password,sizeof(char));
        i++;
    }
}

realloc seem to work for 1 time, but the next times it returns a null pointer and makes the program crash. note: I know I have to check if realloc returns a null pointer, I checked it in my watchlist so I didn't write the related code.Thanks for helping.


Solution

  • This isn't doing what you want:

    ptr=(char *)realloc(name,sizeof(char));
    

    The second argument of realloc specifies the total number of bytes you want allocated, not just how many more bytes to allocate. You're not extending the array by 1 each time, you're just reallocating the same 1-byte space (sizeof (char) is 1 by definition).

    Remember that to store an N-character string, you need to allocate at least N+1 bytes to account for the string terminator.

    The typical way to do this is to double the size of the buffer each time - you'll need to keep track of the total buffer size as well as the number of bytes used, like so:

    #include <ctype.h> // for the isspace call below:
    ...
    
    size_t size = 2; // 2 bytes can store a 1-character string
    char *name = malloc( size ); // no need for a cast
    size_t i = 0;
    int c;
    ...
    
    /**
     * You'll probably want to break on EOF and *any* whitespace
     * character
     */
    while( (c = getchar()) != EOF && !isspace( c ) )
    {
      if ( i == size )
      {
        /**
         * Double the size of the buffer; again, no need for a cast
         *
         * Since realloc will return NULL if it can't extend the
         * buffer, we want to assign the result to a temporary;
         * otherwise we risk overwriting the pointer value in name
         * with a NULL pointer and losing our access to the memory
         * that's been previously allocated.
         */
        char *tmp = realloc( name, size * 2 ); 
        if ( tmp )
        {
          name = tmp;
          size *= 2;
        }
        else
        {
          /**
           * We were unable to extend the buffer; you can treat this
           * as a fatal error and exit immediately, or you can
           * try to proceed with the data you have.  The previous
           * buffer is still allocated at this point.  
           *
           * For this particular program, you'd probably just want
           * to exit with an error message at this point, like so:
           */
          fputs( "Failed to extend name buffer, exiting...", stderr );
          free( name ); // clean up what we've already allocated
          exit( 0 );
        } 
      }
      name[i++] = c;
    }
    name[i] = 0; // terminate the string
    

    For convenience's sake, you'll want to move the buffer extension into its own function:

    char *extend( char **buf, size_t *size )
    {
      char *tmp = realloc( *buf, *size * 2 );
      if ( tmp )
      {
        *buf = tmp;
        *size *= 2;
      }
      return tmp;
    }
    

    and you'd call it as

    while( (c = getchar()) != EOF && !isspace( c ) )
    {
      if ( i == size )
      {
        if ( !extend( &name, &size ) )
        {
          // handle realloc failure
        }
      }
      name[i++] = c;
    }
    name[i] = 0; // terminate the string