Динамически выделять введенную пользователем строку

Я пытаюсь написать функцию, которая выполняет следующие действия:

  • Начните входной цикл, напечатав '> ' каждую итерацию.
  • Возьмите то, что вводит пользователь (неизвестная длина), и прочитайте его в массив символов, динамически выделяя размер массива, если это необходимо. Линия, введенная пользователем, закончится символом новой строки.
  • Добавьте нулевой байт, '\0' , в конец массива символов.
  • Петля заканчивается, когда пользователь вводит пустую строку: '\n'

Это то, что я написал в настоящее время:

 void input_loop(){ char *str = NULL; printf("> "); while(printf("> ") && scanf("%a[^\n]%*c",&input) == 1){ /*Add null byte to the end of str*/ /*Do stuff to input, including traversing until the null byte is reached*/ free(str); str = NULL; } free(str); str = NULL; } 

Теперь я не слишком уверен, как перейти к добавлению нулевого байта в конец строки. Я думал примерно так:

 last_index = strlen(str); str[last_index] = '\0'; 

Но я не слишком уверен, что это сработает. Я не могу проверить, будет ли это работать, потому что я сталкиваюсь с этой ошибкой, когда пытаюсь скомпилировать свой код:

 warning: ISO C does not support the 'a' scanf flag [-Wformat=] 

Итак, что я могу сделать, чтобы заставить мой код работать?

EDIT: изменение scanf("%a[^\n]%*c",&input) == 1 to scanf("%as[^\n]%*c",&input) == 1 дает мне ту же ошибку.

Прежде всего, строки формата scanf не используют регулярные выражения, поэтому я не думаю, что что-то близкое к тому, что вы хотите, будет работать. Что касается ошибки, которую вы получаете, согласно моему доверчивому руководству , то флаг конверсии используется для чисел с плавающей запятой, но он работает только на C99 (и ваш компилятор, вероятно, настроен для C90)

Но тогда у вас есть большая проблема. scanf ожидает, что вы передадите ему ранее выделенный пустой буфер, чтобы он заполнил вход чтения. Это не malloc для sctring для вас, поэтому ваши попытки инициализации str на NULL и соответствующие frees не будут работать с scanf.

Самое простое, что вы можете сделать, это отказаться от n строк произвольной длины. Создайте большой буфер и запретите входные данные, которые больше, чем это.

Затем вы можете использовать функцию fgets для заполнения вашего буфера. Чтобы проверить, удалось ли прочитать полную строку, проверьте, заканчивается ли ваша строка «\ n».

 char str[256+1]; while(true){ printf("> "); if(!fgets(str, sizeof str, stdin)){ //error or end of file break; } size_t len = strlen(str); if(len + 1 == sizeof str){ //user typed something too long exit(1); } printf("user typed %s", str); } в char str[256+1]; while(true){ printf("> "); if(!fgets(str, sizeof str, stdin)){ //error or end of file break; } size_t len = strlen(str); if(len + 1 == sizeof str){ //user typed something too long exit(1); } printf("user typed %s", str); } 

Другой вариант – вы можете использовать нестандартную библиотечную функцию. Например, в Linux есть функция getline, которая считывает полную строку ввода, используя malloc за кулисами.

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

 #include  #include  char *readInfiniteString() { int l = 256; char *buf = malloc(l); int p = 0; char ch; ch = getchar(); while(ch != '\n') { buf[p++] = ch; if (p == l) { l += 256; buf = realloc(buf, l); } ch = getchar(); } buf[p] = '\0'; return buf; } int main(int argc, char *argv[]) { printf("> "); char *buf = readInfiniteString(); printf("%s\n", buf); free(buf); } 

Если вы находитесь в системе POSIX, такой как Linux, у вас должен быть доступ к getline . Его можно fgets вести себя как fgets , но если вы начнете с нулевого указателя и нулевой длины, он позаботится о распределении памяти для вас.

Вы можете использовать это в цикле следующим образом:

 #include  #include  #include  // for strcmp int main(void) { char *line = NULL; size_t nline = 0; for (;;) { ptrdiff_t n; printf("> "); // read line, allocating as necessary n = getline(&line, &nline, stdin); if (n < 0) break; // remove trailing newline if (n && line[n - 1] == '\n') line[n - 1] = '\0'; // do stuff printf("'%s'\n", line); if (strcmp("quit", line) == 0) break; } free(line); printf("\nBye\n"); return 0; } 

Прошедший указатель и значение длины должны быть согласованными, так что getline может перераспределять память по мере необходимости. (Это означает, что вы не должны менять line nline или указатель в цикле.) Если строка подходит, в каждом проходе через цикл используется один и тот же буфер, так что вам нужно free строку только один раз, когда вы «Сделано чтение.

Некоторые упомянули, что scanf , вероятно, непригоден для этой цели. Я бы не предложил использовать fgets . Хотя это немного более подходит, есть проблемы, которые, как представляется, трудно избежать, по крайней мере, вначале. Немногим программистам C удается использовать fgets в первый раз, не прочитав руководство fgets в полном объеме. Части, которые большинство людей могут полностью игнорировать:

  • что происходит, когда линия слишком велика, и
  • что происходит, когда встречается EOF или ошибка.

Функция fgets() должна считывать байты из stream в массив, на который указывает s , до тех пор, пока не будут прочитаны n-1 байты, или будет прочитано и передано в s , или будет обнаружено условие конца файла. Затем строка заканчивается нулевым байтом.

После успешного завершения fgets() возвращает s . Если stream находится в конце файла, индикатор конца файла для streamа должен быть установлен, а fgets() должен возвращать нулевой указатель. Если возникает ошибка чтения, индикатор ошибки для streamа должен быть установлен, fgets() должен возвращать нулевой указатель …

Я не чувствую, что мне нужно подчеркнуть важность слишком высокой проверки возвращаемого значения, поэтому я не буду упоминать об этом снова. Достаточно сказать, что если ваша программа не проверяет возвращаемое значение, ваша программа не будет знать, когда возникает EOF или ошибка; ваша программа, вероятно, попадет в бесконечный цикл.

Когда нет '\n' , оставшиеся байты строки еще не были прочитаны. Таким образом, fgets всегда будет анализировать линию хотя бы один раз, внутренне. Когда вы вводите дополнительную логику, чтобы проверить '\n' , вам придется анализировать данные во второй раз.

Это позволяет повторно fgets память и вызвать fgets если вы хотите динамически изменять размер хранилища или отбросить оставшуюся часть строки (предупреждение пользователю об усечении – хорошая идея), возможно, используя что-то вроде fscanf(file, "%*[^\n]"); ,

hugomg упоминается с использованием умножения в динамическом коде изменения размера, чтобы избежать проблем с квадратичным временем выполнения. Вдоль этой линии было бы неплохо избегать синтаксического анализа одних и тех же данных за каждую итерацию (таким образом, возникновение дополнительных квадратичных проблем времени выполнения). Это может быть достигнуто путем хранения количества прочитанных вами байтов (и parsingа). Например:

 char *get_dynamic_line(FILE *f) { size_t bytes_read = 0; char *bytes = NULL, *temp; do { size_t alloc_size = bytes_read * 2 + 1; temp = realloc(bytes, alloc_size); if (temp == NULL) { free(bytes); return NULL; } bytes = temp; temp = fgets(bytes + bytes_read, alloc_size - bytes_read, f); /* Parsing data the first time */ bytes_read += strcspn(bytes + bytes_read, "\n"); /* Parsing data the second time */ } while (temp && bytes[bytes_read] != '\n'); bytes[bytes_read] = '\0'; return bytes; } 

Те, кто действительно умеет читать руководство и придумывают что-то правильное (например, это), могут вскоре осознать, что сложность решения fgets по меньшей мере вдвое хуже, чем одно и то же решение, использующее fgetc . Мы можем избежать анализа данных во второй раз с помощью fgetc , поэтому использование fgetc может показаться наиболее подходящим. Увы, большинству программистов C также удается неправильно использовать fgetc при пренебрежении руководством fgetc .

Самая важная деталь – понять, что fgetc возвращает int , а не char . Он может возвращать обычно одно из 256 различных значений, между 0 и UCHAR_MAX (включительно). В противном случае он может возвращать EOF , то есть обычно 257 различных значений, которые fgetc (или, следовательно, getchar ) может вернуться . Попытка сохранить эти значения в char или unsigned char приводит к потере информации, в частности к режимам ошибок. (Конечно, это типичное значение 257 изменится, если CHAR_BIT больше 8, и, следовательно, UCHAR_MAX больше 255)

 char *get_dynamic_line(FILE *f) { size_t bytes_read = 0; char *bytes = NULL; do { if ((bytes_read & (bytes_read + 1)) == 0) { void *temp = realloc(bytes, bytes_read * 2 + 1); if (temp == NULL) { free(bytes); return NULL; } bytes = temp; } int c = fgetc(f); bytes[bytes_read] = c >= 0 && c != '\n' ? c : '\0'; } while (bytes[bytes_read++]); return bytes; }