Как мне взять строку переменного размера в качестве входных данных, используя только getchar, не спрашивая раньше у пользователя размер строки? - PullRequest
1 голос
/ 09 мая 2020

Мне нужно собрать пользовательский ввод только с getchar() и malloc(), чтобы сохранить его в строке (размер которой неизвестен). Я делал это раньше, но забыл, как я понял, и теперь у меня проблема со строкой, печатаю только первую букву, что означает, что моя функция get_string не собирает все символы из stdin ИЛИ указатель не указывает к нему ИЛИ он просто не печатается правильно с помощью printf.

char *get_string(void);

int main(void)
{
    printf("Input string: ");
    char *p = get_string();
    printf("Output string: %s\n", p);
}

char *get_string(void)
{
    int c = 0;
    char *str = NULL;
    char *buff = NULL;

    for(int i = 0; c != '\n'; i++)
    {
        if(i > 0) // skips on first iteration (no char collected yet)
        {
            if(i > 1) // skips on second iteration (1st char collected)
            {
                buff = malloc(i + 1);
                for(int j = 0; j < i - 1; j++)
                    buff[j] = str[j];
                free(str);
            }
            str = malloc(i + 1); // allocate space for string
            if(i > 1) // no need to copy string from buffer
            {
                for(int j = 0; j < i - 1; j++)
                    str[j] = buff[j];
                free(buff);
            }
            str[i - 1] = c; // place char into string
            str[i] = '\0'; // terminate string with '\0'
            printf("%s\n", str); // print contents on each iteration
        }
        c = getchar();
    }
    return (str);
}

Если я запускаю printf в основном с возвращенной строкой, ничего не печатается. Если я запускаю printf внутри l oop, он печатает только на первой итерации (первая буква).

Что я получаю:

$ > gcc get_string.c -o get_string
$ > ./get_string
Input string: Hello World!
H
Output string:

Что я ожидаю:

$ > gcc get_string.c -o get_string
$ > ./get_string
Input string: Hello World!
H
He
Hel
Hell
Hello
...
Output string: Hello World!

Кроме того, если вы знаете лучший (и более короткий) способ решения этой проблемы, поделитесь, пожалуйста.

Ответы [ 2 ]

3 голосов
/ 09 мая 2020

Вы захотите использовать realloc для расширения входного буфера, хотя вы не захотите делать это для каждого отдельного символа (это относительно дорогая операция и может привести к перемещению строки в памяти) . Распространенный трюк состоит в том, чтобы удвоить размер буфера по мере того, как вы достигнете его конца, чтобы при чтении символов размер буфера увеличивался с 16 до 32 до 64, et c., Минимизируя количество вызовов realloc. Компромисс - небольшая внутренняя фрагментация - вы можете сохранить 65 символов в 128-символьном буфере. Но в среднем это не должно быть большой проблемой. Вот пример:

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

#define START_SIZE 16 // some size that should handle most cases

/**
 * Get the next line from the specified input stream.  Return characters up to
 * (but not including) the next newline character or EOF.  Return the size of the
 * allocated buffer as well.  
 */
char *getline( FILE *stream, size_t *size )
{
  *size = START_SIZE;
  size_t i = 0;

  /**
   * Initial allocation, buf can store a string up to START_SIZE - 1 characters.
   * If initial allocation fails, return NULL.
   */
  char *buf = malloc( sizeof *buf * *size );
  if ( !buf )
  {
    fprintf( stderr, "Failure to allocate initial buffer\n" );
    return NULL;
  }

  /**
   * Read from the input stream until we see a newline or EOF.  Newline will
   * *not* be stored in the returned string.
   */
  for ( int c = fgetc( stream ); c != '\n' && c != EOF; c = fgetc( stream ))
  {
    /**
     * Have we hit the end of the input buffer yet (allowing for the terminator)?
     */
    if ( i + 1 == *size )
    {
      /**
       * Yes.  Double the size of the buffer using realloc.  
       * If realloc cannot satisfy the request, it will return 
       * NULL and leave the contents of buf unchanged.  Therefore,
       * we want to make sure we assign the result to
       * a temporary variable and check it, otherwise we
       * could potentially lose our reference to the
       * previously allocated memory, leading to a memory leak.
       */
      char *tmp = realloc( buf, sizeof *buf * (*size * 2));
      if ( tmp )
      {
        buf = tmp;
        *size *= 2;
      }
      else
      {
        fprintf( stderr, "Unable to extend buf, returning what we have so far\n");
        return buf;
      }
    }
    buf[i++] = c;
    buf[i] = 0; // zero terminate the string as we go
  }
  return buf;
}

int main( void )
{
  size_t bufsize;
  printf( "Gimme a string: ");
  char *str = getline( stdin, &bufsize );
  printf( "You entered: \"%s\"\n", str );
  printf( "length = %zu, buffer size = %zu\n", strlen( str ), bufsize);
  free( str );
  return 0;
}

И несколько примеров запускаются:

john@marvin:~/Development/getline$ gcc -o getline -std=c11 -pedantic -Wall -Werror getline.c

john@marvin:~/Development/getline$ ./getline
Gimme a string: this
You entered: "this"
length = 4, buffer size = 16

john@marvin:~/Development/getline$ ./getline
Gimme a string: this is a test
You entered: "this is a test"
length = 14, buffer size = 16

john@marvin:~/Development/getline$ ./getline
Gimme a string: this is a test of
You entered: "this is a test of"
length = 17, buffer size = 32

john@marvin:~/Development/getline$ ./getline
Gimme a string: this is a test of the emergency broadcast system.
You entered: "this is a test of the emergency broadcast system."
length = 49, buffer size = 64

john@marvin:~/Development/getline$ ./getline
Gimme a string: this is a test of the emergency broadcast system.  in the event of an actual emergency, you would be dead by now.  
You entered: "this is a test of the emergency broadcast system.  in the event of an actual emergency, you would be dead by now.  "
length = 115, buffer size = 128
1 голос
/ 09 мая 2020

Думаю, это то, что вам нужно сделать:

char *get_string( )
{
    char* buffer = (char*)malloc(sizeof(char));
    char c;
    int size = 0;

    c = getc(stdin);
    buffer[size++] = c;

    while( c != '\n')
    {
        c = getc(stdin);
        buffer = (char*)realloc(buffer, (size+1)*sizeof(char));

        if(buffer != NULL) // Check if space was re allocated
            buffer[size++] = c;
        else // If re allocation failed
            return NULL;
    }
    return buffer;
}

Сначала вы создаете буфер размером 1 и читаете первый символ из stdin. Тогда белым цветом следующий символ не будет \n:

  1. Прочитать следующий символ.

  2. Перераспределить пространство для следующего символа (примечание что realloc может вернуть NULL в случае сбоя перераспределения, вы должны это проверить).

  3. Добавить текущий символ в буфер.

...