-2

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.

  • 1
    Side note: [Do I cast the result of malloc?](https://stackoverflow.com/q/605845/12149471) – Andreas Wenzel Jan 06 '23 at 18:48
  • 2
    @pixiethepixel The variable ptr is declared as having the type char char ptr;. So this assignment ptr=(char *)realloc(name,sizeof(char)); does not make sense. – Vlad from Moscow Jan 06 '23 at 18:48
  • A single byte C string is a nearly useless C string. Tip: Don't use `realloc()` aggressively. Try and minimize the number of calls made. – tadman Jan 06 '23 at 18:50
  • Tip: Don't allocate one byte at a time. Read in the whole string into some sufficiently large buffer, figure out how much data is in the buffer, and then copy out of there into a correctly sized allocation. Use `strdup()` if you have it. – tadman Jan 06 '23 at 18:51
  • @pixiethepixel After you have updated your program the identifier ptr is undeclared. – Vlad from Moscow Jan 06 '23 at 18:51
  • 1
    You need `name = ptr;` after the reallocation. – Barmar Jan 06 '23 at 18:52
  • Tip: Never forget to NUL terminate your strings! – tadman Jan 06 '23 at 18:52
  • 1
    @tadman I think he's trying to implement `getline()`, so he doesn't have to declare a "sufficiently large buffer". – Barmar Jan 06 '23 at 18:52
  • 2
    @pixiethepixel And you are always allocating one character. – Vlad from Moscow Jan 06 '23 at 18:53
  • If `realloc` fails, you print an error message, but you continue program execution as if it succeeded. I suggest that you add `exit( EXIT_FAILURE );` underneath the line `printf("realloc failed");`. – Andreas Wenzel Jan 06 '23 at 18:53
  • @Barmar That's certainly an interpretation, but this implementation leaves a lot to be desired. A fixed-sized buffer is probably a better place to start. Dynamic allocation and resizing requires a lot more finesse. – tadman Jan 06 '23 at 18:53
  • 1
    @tadman True, but I think he's trying to learn how to handle input without arbitrary limits. It's a reasonable exercise. – Barmar Jan 06 '23 at 18:54
  • Thanks for the help. @tadman the problem is that I can't store it in a large buffer, the data is needed to be handled with only dynamic arrays. – pixiethepixel Jan 06 '23 at 19:09
  • The return type of getchar is int for a reason. You need to test it for EOF. – stark Jan 06 '23 at 19:10
  • 1
    `char c;` then `while((c=getchar())!=' '){...}` has multiple issues. First, `getchar()` returns `int`, not `char`, because `EOF` is an integer value that can not be represented as a `char`. So your code can't reliably detect `EOF` even if it checked for `EOF` - which it doesn't. And that's the second problem - if your code reaches `EOF` before a `' '` character, it falls into an infinite loop. – Andrew Henle Jan 06 '23 at 19:24

2 Answers2

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

Please note that this is allocating a single byte. You have done this several times. A C string is an array of characters with a null terminator ('\0') byte at the end. An array that can only hold one byte can only ever hold an empty C string, or lack that null terminator, resulting in undefined behavior.

You likely want to start by allocating a more useful amount of space, and then grow exponentially as your input grows to avoid excessive calls to realloc.

E.g.

size_t allocated_bytes = 64;
char *name = malloc(allocated_bytes);
size_t read_bytes = 0;

char ch;

while ((ch = getchar()) != ' ') {
    if (read_bytes >= allocated_bytes - 1) {
        allocated_bytes *= 2;
        name = realloc(name, allocated_bytes);
    }

    name[read_bytes++] = ch;
}

name[read_bytes] = '\0';

Checking the return of realloc and for EOF I leave as an exercise for the OP.

Chris
  • 26,361
  • 5
  • 21
  • 42
  • You should error check the memory allocation calls — and `oldptr = realloc(oldptr, newsize);` is an anti-pattern. You should use: `void *newptr = realloc(oldptr, newsize); if (newptr == NULL) { …handle memory allocation failure… } oldptr = newptr;` so that you still have a pointer to the old memory if allocation fails (so you can free it, for example). I usually use `size_t newsize = oldsize * 2;` and only assign `newsize` to `oldsize` when the allocation worked. If `oldsize` can start at `0` (rather than `64`), I use `size_t newsize = (oldsize + 2) * 2;` to get the sequence 4, 12, 28, etc. – Jonathan Leffler Jan 06 '23 at 19:26
1

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
John Bode
  • 119,563
  • 19
  • 122
  • 198