2012-08-28 18 views
5

Tôi muốn có tương đương (trong C) của getline trong C++:Làm cách nào để đọc một dòng dài tùy ý trong C?

std::string s; 
getline(std::cin,s); // reads an arbitrarily long line and 
        // inserts its contents on s 

Có cách nào để làm một điều như vậy trong C? Tôi đang tìm kiếm cái gì đó trông như thế này:

char* s; 
getline(stdin,s); // allocates the space necessary to fit the read 
        // line and make s point to it 

EDIT: Tôi quyết định sử dụng POSIX getline chức năng ở cuối kể từ khi tôi đang trên Linux (chạy man getline nếu bạn không biết những gì tôi đang nói về), nhưng Michael Burr đã cung cấp việc triển khai getline hoạt động trên các hệ điều hành khác trong đó getline không có sẵn theo mặc định. Ngay cả khi thực hiện của ông không phải là một trong những hiệu quả nhất có thể thụ thai, nó hiện công việc như tôi muốn vì vậy tôi đánh dấu nó như là câu trả lời cho câu hỏi của tôi.

+2

Quảng cáo vô danh: http://stackoverflow.com/a/8164021/714501 Ngoài ra còn có ['getline'] (http://pubs.opengroup.org/onlinepubs/9699919799/functions/getline.html) được chỉ định bởi POSIX. – cnicutar

+0

Mặc dù điều đó thực hiện công việc, cá nhân tôi thấy giải pháp 'realloc' thực sự khủng khiếp. Không C cung cấp một số công cụ thực hiện điều này cho tôi tự động? –

+4

Không, nó thực sự không. Nếu bạn nghiên cứu thư viện C chuẩn, bạn sẽ thấy rằng có rất ít hàm làm phân bổ bộ nhớ động. Ngay cả những bổ sung đơn giản rõ ràng như 'strdup' đã bị từ chối; bạn phải xây dựng của riêng bạn trên đầu trang của malloc. Vì vậy, có một tỷ thực hiện riêng tư của getline, tất cả đều không tương thích với nhau. Tôi thực sự hy vọng một POSIX một (mà là giống với một GNU) bắt trên. Làm một phần của bạn bằng cách sử dụng nó, và thực hiện nó cho nền tảng của bạn nếu nó chưa có. –

Trả lời

5

Trong trường hợp bạn không có quyền truy cập vào một thực hiện POSIX getline(), đây là một triển khai tên miền công cộng mà tôi đã nằm xung quanh.

Tôi đã thêm một hàm getline_simple() nhỏ mà chỉ trả lại dòng tiếp theo trong bộ đệm được cấp phát động. Nếu bạn không quan tâm đến việc xử lý lỗi chi tiết, bạn có thể sử dụng chức năng đó để đọc một tệp theo từng dòng:

#define _CRT_SECURE_NO_WARNINGS 1 

#include <assert.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <stdio.h> 
#include <errno.h> 
#include <limits.h> 
#include <sys/types.h> 


#if !__GNUC__ 
#if _WIN64 
typedef long long ssize_t; 
#else 
typedef long ssize_t; 
#endif 
#endif 


#if !defined(SSIZE_MAX) 
#define SSIZE_MAX ((ssize_t)(SIZE_MAX/2)) 
#endif 

#if !defined(EOVERFLOW) 
#define EOVERFLOW (ERANGE)  /* is there something better to use? */ 
#endif 


/* 
    nx_getdelim() 

    a version of the POSIX getdelim() function that return error codes directly 
    instead of messing with the global `errno` value. 
*/ 
ssize_t nx_getdelim(char **lineptr, size_t *n, int delim, FILE *stream); 



/* 
    getdelim_calc_new_alloc() 

    Helper function for getdelim() to figure out an appropriate new 
    allocation size that's not too small or too big. 

    These numbers seem to work pretty well for most text files. 

    returns the input value if it decides that new allocation block 
    would be just too big (the caller should handle this as 
    an error). 
*/ 
static 
size_t nx_getdelim_get_realloc_size(size_t current_size) 
{ 
    enum { 
     k_min_realloc_inc = 32, 
     k_max_realloc_inc = 1024, 
    }; 

    if (SSIZE_MAX < current_size) return current_size; 

    if (current_size <= k_min_realloc_inc) return current_size + k_min_realloc_inc; 

    if (current_size >= k_max_realloc_inc) return current_size + k_max_realloc_inc; 

    return current_size * 2; 
} 



/* 
    getdelim_append() 

    a helper function for getdelim() that adds a new character to 
    the outbuffer, reallocating as necessary to ensure the character 
    and a following null terminator can fit 

*/ 
static 
int nx_getdelim_append(char** lineptr, size_t* bufsize, size_t count, char ch) 
{ 
    char* tmp = NULL; 
    size_t tmp_size = 0; 

    // assert the contracts for this functions inputs 
    assert(lineptr != NULL); 
    assert(bufsize != NULL); 

    if (count >= (((size_t) SSIZE_MAX) + 1)) { 
     // writing more than SSIZE_MAX to the buffer isn't supported 
     return -1; 
    } 

    tmp = *lineptr; 
    tmp_size = tmp ? *bufsize : 0; 

    // need room for the character plus the null terminator 
    if ((count + 2) > tmp_size) { 
     tmp_size = nx_getdelim_get_realloc_size(tmp_size); 

     tmp = (char*) realloc(tmp, tmp_size); 

     if (!tmp) { 
      return -1; 
     } 
    } 

    *lineptr = tmp; 
    *bufsize = tmp_size; 

    // remember, the reallocation size calculation might not have 
    // changed the block size, so we have to check again 
    if (tmp && ((count+2) <= tmp_size)) { 
     tmp[count++] = ch; 
     tmp[count] = 0; 
     return 1; 
    } 

    return -1; 
} 


/* 
    nx_getdelim() 

    A getdelim() function modeled on the Linux/POSIX/GNU 
    function of the same name. 

    Read data into a dynamically resizable buffer until 
    EOF or until a delimiter character is found. The returned 
    data will be null terminated (unless there's an error allocating 
    memory that prevents it). 



    params: 

     lineptr - a pointer to a char* allocated by malloc() 
        (actually any pointer that can legitimately be 
        passed to free()). *lineptr will be updated 
        by getdelim() if the memory block needs to be 
        reallocated to accommodate the input data. 

        *lineptr can be NULL (though lineptr itself cannot), 
        in which case the function will allocate any necessary 
        buffer. 

     n -   a pointer to a size_t object that contains the size of 
        the buffer pointed to by *lineptr (if non-NULL). 

        The size of whatever buff the resulting data is 
        returned in will be passed back in *n 

     delim -  the delimiter character. The function will stop 
        reading one this character is read form the stream. 

        It will be included in the returned data, and a 
        null terminator character will follow it. 

     stream - A FILE* stream object to read data from. 

    Returns: 

     The number of characters placed in the returned buffer, including 
     the delimiter character, but not including the terminating null. 

     If no characters are read and EOF is set (or attempting to read 
     from the stream on the first attempt caused the eof indication 
     to be set), a null terminator will be written to the buffer and 
     0 will be returned. 

     If an error occurs while reading the stream, a 0 will be returned. 
     A null terminator will not necessarily be at the end of the data 
     written. 

     On the following error conditions, the negative value of the error 
     code will be returned: 

      ENOMEM:  out of memory 
      EOVERFLOW: SSIZE_MAX character written to te buffer before 
         reaching the delimiter 
         (on Windows, EOVERFLOW is mapped to ERANGE) 

     The buffer will not necessarily be null terminated in these cases. 


    Notes: 

     The returned data might include embedded nulls (if they exist 
     in the data stream) - in that case, the return value of the 
     function is the only way to reliably determine how much data 
     was placed in the buffer. 

     If the function returns 0 use feof() and/or ferror() to determine 
     which case caused the return. 

     If EOF is returned after having written one or more characters 
     to the buffer, a normal count will be returned (but there will 
     be no delimiter character in the buffer). 

     If 0 is returned and ferror() returns a non-zero value, 
     the data buffer may not be null terminated. 

     In other cases where a negative value is returned, the data 
     buffer is not necessarily null terminated and there 
     is no reliable means to determining what data in the buffer is 
     valid. 

     The pointer returned in *lineptr and the buffer size 
     returned in *n will be valid on error returns unless 
     NULL pointers are passed in for one or more of these 
     parameters (in which case the return value will be -EINVAL). 

*/ 
ssize_t nx_getdelim(char **lineptr, size_t *n, int delim, FILE *stream) 
{ 
    int retval = 0; 

    ssize_t result = 0;  
    char* line = NULL; 
    size_t size = 0; 
    size_t count = 0; 
    int err = 0; 
    int ch = 0; 

    if (!lineptr || !n) { 
     return -EINVAL; 
    } 

    line = *lineptr; 
    size = *n; 

    for (;;) { 
     ch = fgetc(stream); 

     if (ch == EOF) { 
      break; 
     } 

     result = nx_getdelim_append(&line, &size, count, ch); 

     // check for error adding to the buffer (ie., out of memory) 
     if (result < 0) { 
      err = -ENOMEM; 
      break; 
     } 

     ++count; 

     // check if we're done because we've found the delimiter 
     if ((unsigned char)ch == (unsigned char)delim) { 
      break; 
     } 

     // check if we're passing the maximum supported buffer size 
     if (count > SSIZE_MAX) { 
      err = -EOVERFLOW; 
      break; 
     } 
    } 

    // update the caller's data 
    *lineptr = line; 
    *n = size; 

    // check for various error returns 
    if (err != 0) { 
     return err; 
    } 

    if (ferror(stream)) { 
     return 0; 
    } 

    if (feof(stream) && (count == 0)) { 
     if (nx_getdelim_append(&line, &size, count, 0) < 0) { 
      return -ENOMEM; 
     } 
    } 

    return count; 
} 




ssize_t nx_getline(char **lineptr, size_t *n, FILE *stream) 
{ 
    return nx_getdelim(lineptr, n, '\n', stream); 
} 



/* 
    versions of getline() and getdelim() that attempt to follow 
    POSIX semantics (ie. they set errno on error returns and 
    return -1 when the stream error indicator or end-of-file 
    indicator is set (ie., ferror() or feof() would return 
    non-zero). 
*/ 
ssize_t getdelim(char **lineptr, size_t *n, char delim, FILE *stream) 
{ 
    ssize_t retval = nx_getdelim(lineptr, n, delim, stream); 

    if (retval < 0) { 
     errno = -retval; 
     retval = -1; 
    } 

    if (retval == 0) { 
     retval = -1; 
    } 

    return retval; 
} 

ssize_t getline(char **lineptr, size_t *n, FILE *stream) 
{ 
    return getdelim(lineptr, n, '\n', stream); 
} 


/* 
    A simple function to return the next line of text in a dynamically 
    allocated buffer 

    On error a NULL pointer is returned. When the caller no longer needs the 
    returned data, the pointer returned should be passed to `free()`. 


*/ 
char* getline_simple(FILE* stream) 
{ 
    char* p = NULL; 
    size_t size = 0; 

    ssize_t result = getline(&p, &size, stream); 

    if (result < 0) { 
     free(p); 
     p = NULL; 
    } 

    return p; 
} 

Tuyên bố miễn trừ - mã này đã hoạt động đủ tốt cho mục đích của tôi, nhưng tôi không đảm bảo rằng sẽ là trường hợp cho bạn. Vui lòng sử dụng thẩm định nếu có ý định sử dụng mã này.

+0

Điều này khá kém hiệu quả. Một người tốt sẽ sử dụng 'fgets' thay vì đọc nhân vật tại một thời điểm.Điều này thật dễ dàng nếu bạn không quan tâm đến việc xử lý trường hợp đã nhúng 0 byte, nhưng yêu cầu một số thủ thuật lạ mắt nếu bạn muốn hoàn toàn tương thích. Nó cũng không giúp với 'getdelim' chung; chỉ 'getline'. Để giải quyết vấn đề này, bạn có thể sử dụng 'fscanf' với'% NNN [^ x] 'trong đó' NNN' được thay thế bằng kích thước bộ đệm gia tăng của bạn và 'x' được thay thế bằng dấu phân tách. Tuy nhiên, điều đáng ngờ là liệu việc thực thi 'scanf' sẽ tối ưu hóa điều này hay không; nó có thể chỉ chậm hoặc chậm hơn 'getc' ... –

+0

@R .: Tôi không đồng ý rằng điều này không hiệu quả - đây không phải là mã sản xuất. Tôi, đối với một, có ít quan tâm đến việc tối ưu hóa nó vào lúc này. –

+1

Tôi không phàn nàn về câu trả lời của bạn; Tôi chỉ muốn chắc chắn rằng nó được ghi nhận trong trường hợp OP hoặc ai đó khác đọc câu trả lời cần hiệu suất, và đề nghị làm thế nào để cải thiện tình hình. Tôi không cảm thấy muốn viết phiên bản dựa trên 'fgets' vào lúc này hoặc tôi đã viết nó như một câu trả lời chứ không phải là một bình luận. Trong mọi trường hợp, câu trả lời này là tốt và hữu ích như-là nếu bạn không cần hiệu suất cao. –

2

Bạn cần tăng dần theo vòng lặp, sử dụng fgets() để đọc khối dữ liệu và realloc() để phóng to bộ đệm bộ nhớ nếu bạn không bắt đầu dòng.

Có thể có một số chức năng thư viện để thực hiện điều đó cho bạn.

#define BLOCKSIZE 1024 

char *readAllocLine(FILE *fp) { 
    char *line = NULL; 
    size_t maxlength = 0; 
    assert(fp != NULL); 
    for(;;) { // Read the line in BLOCKSIZE -blocks. 
     char *crlf, *block; 

     maxlength += BLOCKSIZE; 
     // This exploits realloc behaviour upon hitting NULL 
     if (NULL == (line = realloc(line, maxlength+1))) { 
      break; // realloc error returns NULL. 
     } 
     block = line + maxlength - BLOCKSIZE; 
     // BLOCKSIZE+1 to accommodate final zero 
     if (NULL == fgets(block, BLOCKSIZE+1, fp)) { 
      // TODO: rewind fp in case of error. 
      if (block == line) { 
        // Error. 
        free(line); line = NULL; 
      } 
      break; 
     } 
     // This was the last block iff we find a CRLF inside. 
     if (NULL != (crlf = strchr(block, '\n'))) { 
      *crlf = 0x0; 
      if (crlf != block) { 
       if ('\r' == *(--crlf)) 
        *crlf = 0x0; 
      } 
      break; 
     } /* if */ 
    } /* for */ 
    return line; 
} 

Thử nghiệm với chính

int main(int argc, char **argv) { 
    FILE *fp; 
    if (argc !=2) { 
      fprintf(stderr, "Syntax: %s testfile.txt\n", argv[0]); 
      return -1; 
    } 
    fp = fopen(argv[1], "r"); 
    while(!feof(fp)) { 
     char *s = readAllocLine(fp); 
     if (NULL != s) { 
      printf("\"%s\"\n", s); 
      free(s); 
     } else { 
      printf("--- end of file ---\n"); 
      break; 
     } 
    } 
    fclose(fp); 
    return 0; 
} 

và kịch bản này này:

for i in $(seq 1020 1028); do 
    # Didn't want to think over 
    yes x | tr -d "\n" | dd of=test bs=1 count=$i 2>/dev/null 
    ./readline test | wc -c | tr "\n" "," 
    echo "" >> test 
    ./readline test | wc -c | tr "\n" "," 
    echo "" >> test 
    ./readline test | wc -c 
done 
Các vấn đề liên quan