2014-09-14 2 views
3

Следующий код устанавливает максимальный размер строки, считанный с stdin. Я бы предпочел не жестко кодировать определенную длину строки и иметь гибкость для обработки любой длины буфера. Каковы хорошие стратегии, позволяющие обрабатывать любые размеры?Чтение из stdin в C без максимальной длины буфера

Если эти стратегии намного сложнее, есть ли способ, по крайней мере, гарантировать, что getline не будет переполняться? Благодарю.

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

#define P 20 

int main() 
{ 
    size_t size = 1920; 
    char *line; 
    // record row; /* structure to store fields */ 
    char tokens[P][41]; 
    int p; 
    char delims[] = ",";  /* ", |" */ 
    char *result = NULL; 

    line = (char *) malloc(size + 1); 

    while(getline(&line, &size, stdin) != -1) 
    { 
     /* chomp */ 
     line[strlen(line)-1] = '\0'; 

     /* load char array */ 
     result = strtok(line , delims); 
     p = 0; 
     while(result != NULL && (p < P)) 
     { 
     strcpy(tokens[p++] , result); 
     result = strtok(NULL, delims); 
     } 

     if (p != P) 
     { 
     fprintf(stderr,"Wrong number of input fields.\nFormat: ID,x1, ... ,x%d\n",P); 
    exit(-1); 
     } 

     /* load record (atol, atof etc... , skipped for brevity) and work with record */ 

     return 0; 
} 
+1

Обычно я решаю его с использованием буферов вместо строк, а затем используя вызовы POSIX, поскольку мне часто требуются неблокирующие вызовы. Если я не знаю размер заранее, я могу вырастить буфер, используя 'realloc()' либо линеаризованно, либо экспоненциально, в зависимости от обстоятельств. Также возможно уменьшить его, когда большой размер больше не нужен, также используя 'realloc()'. –

ответ

7

Вы можете getline выделить память для вас (что весь смысл использования non-standard getline function over the standard fgets function). На странице getline вручную:

Если *lineptr является NULL, то getline() будет выделить буфер для хранения линии, которые должны быть освобождены от пользовательской программы. (Значение в *n игнорируется.)

В качестве альтернативы, перед вызовом getline(), *lineptr может содержать указатель к malloc -allocated буфера *n байт. Если буфер не достаточно большой, чтобы удерживать линию, getline() изменяет размер его с realloc, , обновляя *lineptr и *n при необходимости.

Так что вы можете сделать:

line = NULL; 
while (getline(&line, &size, stdin)) 
{ 
    // ... Do stuff with `line`... 
} 
free(line); 

(Или оставьте свой код, как есть, так как getline будет изменить размер выделенного буфера для вас.)

+0

И если это не 'NULL', он будет' realloc() 'съел его, если понадобится. – 5gon12eder

+1

+1. Однако, пожалуйста, обратите внимание, что 'getline' является расширением GNU (позже становится частью стандарта POSIX), а не частью стандарта C до сих пор. – starrify

+0

+1 Нетрудно реализовать 'getline', если ваша система также не имеет этого, если вы знаете, что делаете. Я уверен, что есть реализация онлайн с BSD-подобной лицензией, если вы не можете использовать какой-либо код GPL или LGPL, и вы не можете или не хотите его кодировать самостоятельно. –

2

Вот код, который я использую - Fgetstr (FILE *, const char *). Он примерно удваивает размер буфера для каждого realloc и не вылетает из-за сбоя malloc/realloc. Вызывается как: char * text = Fgetstr (stdin, "\ n"); или что-то еще.

Функция getdelim() библиотеки аналогична, хотя моя может быть намного старше. В manpage на getline и getdelim не указано, что произойдет, если malloc и realloc выйдут из строя в моей системе, и укажу только на возможную ошибку EINVAL (без ENOMEM). Следовательно, поведение при исчерпании памяти может быть не определено для getline/getdelim для.

Кроме того, в качестве указаний starrify многие системы не имеют имеют getline.

#include <sys/types.h> 
#include <stdio.h> 
#include <string.h> 
#include <malloc.h> 

#ifdef TEST 
#define DEBUG 
#endif 

#ifdef DEBUG 
#undef DEBUG 
#define DEBUG(b) {b} 
#else 
#define DEBUG(b) 
#endif 

#ifdef TEST 
int main (int argc, char **argv) 
{ 
    char *text = (char*)0; 
    char *ends = "\n"; 

    if(argc > 1) ends = argv[1]; 

    while(text = Fgetstr(stdin, ends)) 
    { 
     puts(text); 
     free(text); 
    } 

    return 0; 
} 
#endif 

/* return specifications - 
* 
* terminators include : ends, \0, and EOF 
* 
* root EOF? text? ended? stat returned value 
*   -  -  -  ... 
* 1  -  -  1  return "" 
*   -  1  -  ... 
* 2  -  1  1  return "text" 
* 3  1  -  -  return -null-  EOF-*accepted* 
* 4  1  -  1  return ""   EOF-postponed 
* 5  1  1  -  return "text"  EOF-postponed/fake-end 
* 6  1  1  1  return "text"  EOF-postponed/true-end 
* 
* on ENOMEM, return -null- 
* 
*/ 

static char *Fgetstr_R(FILE *ifp, const char *ends, unsigned int offset) 
{ 
    char *s = (char*)0;      /* the crucial string to return */ 
    unsigned int bufmax = offset;   /* as large as so far */ 
    unsigned int bufidx = 0;    /* index within buffer */ 
    char buffer[bufmax + 1];    /* on-stack allocation required */ 
    int ended = 0;       /* end character seen ? */ 
    int eof = 0;       /* e-o-f seen ? */ 

    DEBUG(fprintf(stderr, "(%d", offset);); 

    while(bufidx <= bufmax)  /* pre-recurse - attempt to fill buffer */ 
    { 
     int c = getc(ifp); 

     if((ended = (!c || (ends && strchr(ends,c)))) || (eof = (EOF==c))) 
      break; 

     buffer[bufidx++] = (char)c; 
    } 

    /* note - the buffer *must* at least have room for the terminal \0 */ 

    if(ended || (eof && offset))     /* root 1,2,4,6 5 */ 
    { 
     unsigned int offset_max = offset + bufidx; 
     DEBUG(fprintf(stderr, " malloc %d", offset_max + 1);); 
     if(s = (char*)malloc((offset_max + 1) * sizeof(char))) 
      s[offset_max] = '\0'; 
     else 
      s = (char*)0, perror("Fgetstr_R - malloc"); 
    } 
    else 
    { 
     if(eof && !offset) /* && !ended */  /* root 3 */ 
      s = (char*)0; 
     else 
      s = Fgetstr_R(ifp, ends, offset + bufidx); /* recurse */ 
    } 

    /* post-recurse */ 

    if(s) 
     strncpy(&s[offset], &buffer[0], bufidx); /* cnv. idx to count */ 

    DEBUG(fprintf(stderr, ")", offset);); 
    return s; 
} 

char *Fgetstr (FILE *ifp, const char *ends) 
{ 
    register char *s = (char*)0; 
    DEBUG(fprintf(stderr, "Fgetstr ");); 
    s = Fgetstr_R(ifp, ends, 0); 
    DEBUG(fprintf(stderr, ".\n");); 
    return s; 
} 
+0

1) Хотя результат находится в буфере malloc, промежуточный буфер находится в локальном VLA. Хммм - я бы подумал, что если для кода нужен буфер «без максимальной длины буфера», с аналогичными размерами промежуточных звеньев, поскольку локальные VLA могут быть проблемой. Подозреваю, что и поэтому длина 'unsigned', а не' size_t'. Я бы не сказал, что это не для интенсивного использования VLA. 2) Заметил, что он останавливается на '' \ 0'' в отличие от 'fgets()'. Наверное, так же хорошо. – chux

+1

Да, вещь VLA показывает смещение GCC. «Беззнаковое» использование, на мой взгляд, связано с тем, В VLA ограничение стека, которое может быть намного меньше кучи (я написал код, который использовал 1K стеков для потока), может оказаться проблемой, поэтому, вероятно, было бы разумнее придерживаться realloc и его многочисленные дополнительные копии, чтобы убедиться, что в качестве ограничителя задействован только размер кучи. Вероятно, было бы неплохо иметь быструю (используя стек) и такую ​​же рутинную версию с кучей. –

+0

Этот код может не работать на любом процессоре, где sizeof (size_t) или sizeof (ptr_t)! = Sizeof (int), в котором много встроенных процессоров и многие из них обычно используются в пространстве ввода-вывода. VLA интересны, но они могут сожрать стек, как сумасшедший. Наконец, поведение realloc() при сбое malloc() было определено, по крайней мере, с 7-го издания (я просто посмотрел на исходный код 7-го издания), поэтому лучшим решением было бы итеративное расширение буфера с помощью realloc() и избежание рекурсия полностью. Тем не менее, чрезмерный realloc() фрагментирует кучу, поэтому ... –

Смежные вопросы