2012-08-28 3 views
5

나는 C++로 getline의 (C)에 동등하고 싶은 : C에서 그런 일을 할 수있는 방법이C에서 임의로 긴 행을 읽으려면 어떻게해야합니까?

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

있습니까?

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

편집 : :이처럼 보이는 뭔가를 찾고 있어요 당신이 모르는 경우 내가 (man getline을 실행 리눅스에있어 이후 끝에 POSIX getline 기능을 사용하기로 결정 내가) Michael Burr는 getline을 기본적으로 사용할 수없는 다른 운영 체제에서 작동하는 getline의 구현을 제공했습니다. 자신의 구현이 가장 효율적인 사람이 아니라고 생각할지라도, 나는 원하는대로 작업을 수행하여 내 질문에 대한 답변으로 표시했습니다. 당신이 행의 끝을 잡으려고하지 않은 경우

+2

뻔뻔한 광고 : http://stackoverflow.com/a/8164021/714501 ['getline'] (http://pubs.opengroup.org/onlinepubs/9699919799/functions/getline.html)도 있습니다. POSIX. – cnicutar

+0

그 일을하지만, 나는 개인적으로'realloc' 해결책이 정말로 끔찍한 것을 발견한다. C가 나를 위해 자동으로 이것을 해주는 도구를 제공하지 않습니까? –

+4

아니요, 실제로 그렇지 않습니다. 표준 C 라이브러리를 공부하면 동적 메모리 할당을 수행하는 기능이 거의 없음을 알 수 있습니다. 'strdup'와 같은 단순한 추가조차도 거부되었습니다. malloc 위에 직접 빌드해야합니다. 그래서 수많은 서로 다른 getline의 구현이 존재합니다. POSIX (GNU와 동일)가 붙잡고 싶습니다. 그것을 사용하여 귀하의 부분을 수행하고, 귀하의 플랫폼에 이미 구현되어 있지 않은 경우 그것을 구현하십시오. –

답변

5

가 여기에 공개 구현이 있다고 I 주위에 거짓말을했습니다.

동적으로 할당 된 버퍼의 다음 줄만 반환하는 getline_simple() 함수를 조금 추가했습니다. 당신은 자세한 오류 처리에 관심이 있다면, 당신은 파일을 읽을 해당 기능을 사용할 수있는 줄 단위 :

#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; 
} 

면책 조항 -이 코드는 내 목적을 위해 충분히 잘 근무하고있다,하지만 난하지 않습니다 영장을 받아야합니다. 이 코드를 사용하려면 실사를 이용하십시오.

+0

이것은 비효율적입니다. 좋은 사람이라면 한 번에 한 번씩 읽는 것이 아니라'fgets '를 사용할 것입니다.임베디드 0 바이트의 경우를 처리하는 데 신경 쓰지 않는다면 쉽게 이해할 수 있지만 완전히 호환되기를 원한다면 몇 가지 멋진 트릭이 필요합니다. 또한 일반적인'getdelim '을 돕지도 않는다. 'getline' 만. 이 문제를 해결하기 위해'% NNN [^ x]'와 함께'fscanf'를 사용할 수 있습니다. 여기서'NNN'은 증분 버퍼 크기로 대체되고'x'는 구분 기호로 대체됩니다. 그러나'scanf' 구현이 이것을 최적화 할 지 여부는 의심 스럽습니다. 'getc'와 같이 느리거나 느릴 수도 있습니다 ... –

+0

@R : 이것이 효율적이지 않다는 것에 동의하지 않습니다 - 이것은 프로덕션 코드가 아닙니다. 나는 그것을 위해 순간적으로 최적화에 관심이 없다. –

+1

답변에 대해 불평하지 않습니다. OP 또는 다른 사람이 답변을 읽고 성능을 필요로하고 상황을 개선 할 수있는 방법을 제안하는 경우에주의해야합니다. 필자는'fgets' 기반의 버전을 지금 쓰는 것에 익숙하지 않았거나 주석 대신 대답으로 작성했을 것입니다. 어쨌든,이 대답은 당신이 고성능을 필요로하지 않는 한, 유용하고 유용합니다. –

2
당신은 데이터 블록을 읽을 수 fgets()를 사용하여, 루프에서 점진적으로 그것을 할 필요가

realloc()는 메모리 버퍼를 확대됩니다.

당신을 위해 그렇게 할 라이브러리 기능이있을 수 있습니다.

#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; 
} 

이 주요

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; 
} 

이 스크립트 테스트 :

당신이 POSIX getline() 구현에 액세스 할 수없는 경우
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 
관련 문제