Чтение строк с неопределенной длиной в C

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

Я не так хорош в программировании на C, и меня попросили прочитать "строковый" ввод с неопределенной длиной.

Это мое решение

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

char *newChar();
char *addChar(char *, char);
char *readLine(void);

int main() {
  char *palabra;
  palabra = newChar();

  palabra = readLine();
  printf("palabra=%sn", palabra);

  return 0;
}

char *newChar() {
  char *list = (char *) malloc(0 * sizeof (char));
  *list = '';
  return list;
}

char *addChar(char *lst, char num) {
  int largo = strlen(lst) + 1;
  realloc(&lst, largo * sizeof (char));
  *(lst + (largo - 1)) = num;
  *(lst + largo) = '';
  return lst;
}

char *readLine() {
  char c;
  char *palabra = newChar();

  c = getchar();
  while (c != 'n') {
    if (c != 'n') {
      palabra = addChar(palabra, c);
    }
    c = getchar();
  }
  return palabra;
}

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

спасибо заранее


EDIT: что ж, спасибо за ответы,они были очень полезны. Теперь я публикую отредактированный(и я надеюсь, что лучше) код, возможно, может быть полезным для кого-то нового для C (например, меня) и снова быть feedbacked.

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


void reChar(char **, int *);
void readLine(char **, int *);

int main() {
    char *palabra = NULL;
    int largo = 0;

    reChar(&palabra, &largo);
    readLine(&palabra, &largo);
    printf("palabra=%sn", palabra, largo);

    system("pause");
    return 0;
}

void reChar(char **lst, int *largo) {
    (*largo) += 4;
    char *temp = (char*) realloc(*lst, (*largo) * sizeof (char));

    if (temp != NULL) {
        *lst = temp;
    } else {
        free(*lst);
        puts("error (re)allocating memory");
        exit(1);
    }
}

void readLine(char **lst, int *largo) {
    int c;
    int pos = 0;

    c = getchar();
    while (c != 'n' && c != EOF) {
        if ((pos + 1) % 4 == 0) {
            reChar(lst, largo);
        }
        (*lst)[pos] =(char) c;
        pos++;
        c = getchar();
    }
    (*lst)[pos] = '';
}

PS:

  • Это, кажется, достаточно, чтобы увеличить размер "палабра".

  • Я не уверен, что захват getchar() на int и затем бросить его в char - это правильный способ hadle EOF pitfall

5 ответов


  1. посмотрите определение POSIX getline().

  2. помните, что вам нужно захватить возвращаемое значение из realloc(); не гарантируется, что новый блок памяти запускается в том же положении, что и старый.

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

  4. нельзя писать'*list = ''; когда список указывает на нулевой байт выделенной памяти; у вас нет разрешения на запись. Если вы получите NULL обратно, вы, вероятно, получите дамп ядра. В любом случае, вы вызываете неопределенное поведение, которое является 'Плохая Идея™'. (спасибо)

  5. на palabra = newChar(); на main() утечки памяти - предполагая, что вы уже исправить другие проблемы обсуждаемый.

  6. код readLine() не рассматривает возможность получения EOF перед получением новой строки; это плохо и приведет к дампу ядра, когда выделение памяти (наконец) завершится неудачей.

  7. ваш код будет демонстрировать низкую производительность, потому что он выделяет по одному символу за раз. Как правило, вы должны выделять значительно больше одного дополнительного символа за раз; начиная с начального выделения, возможно, 4 байта и удвоение распределения каждый раз, когда вам нужно больше места, может быть лучше. Держите начальное распределение небольшим, чтобы правильно протестировать код перераспределения.

  8. возвращаемое значение getchar() это int, а не char. На большинстве машин он может возвращать 256 различных положительных значений символов (даже если char является подписанным типом) и отдельным значением, EOF, которое отличается от всех char значения. (Стандарт позволяет ему возвращать более 256 различных символы, если на компьютере байты больше 8 бит каждый.) (спасибо) стандарт C99 §7.19.7.1 говорит о fgetc():

    если индикатор конца файла для входного потока, на который указывает поток, не установлен и следующий символ присутствует, функция fgetc получает, что персонаж как unsigned char преобразуется в int и выдвигает связанный индикатор положения файла для поток (если определенный.)

    (Курсив мой.) Он определяет getchar() С точки зрения getc(), и определяет getc() С точки зрения fgetc().

  9. (заимствовано: спасибо). Первый аргумент realloc() - указатель на начало выделенной памяти, а не указатель на начало выделенной памяти. Если вы не получили предупреждение о компиляции из него, вы не компилируете с достаточным количеством предупреждений ваш компилятор. Вы должны включить предупреждения по максимуму. Вы должны прислушаться к предупреждениям компилятора - они обычно указывают на ошибки в вашем коде, особенно когда вы все еще изучаете язык.

  10. часто легче сохранить строку без нулевого Терминатора, пока вы не знаете, что достигли конца строки (или конца ввода). Когда больше нет символов для чтения (на данный момент), добавьте значение null, чтобы строка правильно завершается до его возвращения. Эти функции не нуждаются в правильном завершении строки во время чтения, если вы отслеживаете, где вы находитесь в строке. Убедитесь, что у вас есть достаточно места в любое время, чтобы добавить нул '' до конца строки, хотя.

См. Kernighan & Pike 'практика программирования' для многих соответствующих обсуждениях. Я также думаю, Магуайр 'Управления' есть соответствующий совет предложить, для всех он несколько устарел. Тем не менее, вы должны знать, что есть те, кто ругает книгу. Следовательно, я рекомендую TPOP через WSC (но Amazon имеет WSC, доступный от $0.01 + p&p, тогда как TPOP начинается с $20.00 + p&p-это может быть рынок).


TPOP ранее был на http://plan9.bell-labs.com/cm/cs/tpop и http://cm.bell-labs.com/cm/cs/tpop но оба сейчас (2015-08-10) сломан. Смотрите также Википедию на обработка поставки третьему лицу.


  • вы всегда выделяете на один байт меньше, чем используете. Например, в начале вы выделяете пространство для нулевых символов, а затем пытаетесь установить (несуществующий) первый символ в ''.

  • realloc не принимает указатель на указатель в качестве первого параметра. Он должен использоваться следующим образом:

    lst = realloc(lst, largo * sizeof (char));
    
  • если вы хотите обрабатывать условия нехватки памяти, вам придется проверить, если malloc() или realloc() возвращать null.

  • было бы более эффективно выделять больший буфер в начале и увеличивать его большими шагами вместо перераспределения каждого добавленного символа отдельно.


первый аргумент для вызова realloc на

realloc(&lst, largo * sizeof (char));

должно быть lst, а не &lst

также указатель, возвращенный realloc не всегда нужно быть таким же, как его первый аргумент. Если свободная память рядом с существующей памятью не найдена, выделяется совершенно другой фрагмент и возвращается его адрес.

char *new_lst = realloc(lst, largo * sizeof (char));
if(new_lst != NULL) {
  lst = new_lst;
}

помимо ошибок в вашем коде, я думаю, что лучше создать строку переменной длины в C. Как только у вас это будет, вы можете написать функцию getLine (). Эта строка переменной длины включает в себя понятие емкости, поэтому ее размер увеличивается в блоках степеней 2, а не один за другим.

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

typedef struct _mystring {
    char * native;
    size_t size;
    size_t capacity;
} String;

size_t String__len(String this)
{
    return this.size;
}

String String__create(char native[], size_t capacity) {
  String this;

  this.size = strlen( native );
  if ( capacity < ( this.size + 1 ) )
        this.capacity = this.size + 1;
  else  this.capacity = capacity;

  this.native = (char *) malloc( capacity * sizeof( char ) );
  strcpy( this.native, native );

  return this;
}

String * String__set(String *this, char native[]) {
    this->size = strlen( native );

    if ( this->size >= this->capacity ) {
        do {
            this->capacity <<= 1;
        } while( this->size > this->capacity );

        this->native = realloc( this->native, this->capacity );
    }

    strcpy( this->native, native );

    return this;
}

String * String__add(String *this, char ch) {
    ++( this->size );

    if ( this->size >= this->capacity ) {
        do {
            this->capacity <<= 1;
        } while( this->size > this->capacity );

        this->native = realloc( this->native, this->capacity );
    }

    char * zeroPos = this->native + ( this->size -1 );
    *( zeroPos++ ) = ch;
    *zeroPos = 0;

    return this;
}

void String__delete(String *this)
{
    free( this->native );
}

после того, как вы сделали эту реализацию, которая полезна для этой проблемы и многих других, вы можете создать функцию getLine:

String String__getLine()
{
    int ch;
    String this = String__create( "", 16 );

    do {
        ch = fgetc( stdin );
        String__add( &this, ch );
    } while( ch != EOF
          && ch != '\n' );

    size_t len = String__len( this );
    this.size = len -1;
    *( this.native + this.size ) = 0;

    return this;
}

Теперь вы можете просто использовать это:

int main()
{
    printf( "Enter string: " );
    String str = String__getLine();
    printf( "You entered: '%s'\n", str.native );
    String__delete( &str );

    return EXIT_SUCCESS;
}

вот рабочий пример для realloc и fgets. Его C89, не требуется POSIX. Параметр можно задать с помощью собственной предварительно выделенной памяти или NULL. Окончательная "свобода" всегда необходима.

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

char *getstringStdin(char *s)
{
  char buffer[9];
  s=realloc(s,1);
  *s=0;
  while( fgets(buffer,9,stdin) )
  {
    s=realloc(s,strlen(s)+1+strlen(buffer));
    strcat(s,buffer);
    if( strchr(s,'\n') )
    {
      *strchr(s,'\n')=0;
      break;
    }
  }
  return s;
}

main()
{
  char *s;
  while( *(s=getstringStdin(0)) ) /* a single Enter breaks */
  {
    puts(s);
    free(s);
  }
  free(s);
  puts("end");
  return 0;
}