2011-08-22 34 views
3

如果我使用C gets(),並且正在從用戶讀取一個字符串,但我不知道需要多大的緩衝區,並且輸入可能非常大。 有沒有一種方法可以確定用戶輸入的字符串有多大,然後分配內存然後將其放入變量中?或者至少有一種方法可以在不知道它有多大的情況下接受輸入,有可能它不適合我已經分配的緩衝區。在C中讀取字符串

回答

2

我認爲使用一個適當大的中間緩衝區,並用fgets或其他函數將字符串輸入到緩衝區中,方法是將字符串長度限制爲最大緩衝區大小。稍後當字符串被輸入時。計算字符串長度並分配字符串大小的緩衝區並將其複製到新分配的緩衝區中。舊的大緩衝區可以重用於這種輸入。

你可以這樣做:

fgets (buffer, BUFSIZ, stdin);

scanf ("%128[^\n]%*c", buffer);

在這裏,您可以指定緩衝區長度爲128個字節%128..,也包括在字符串中的所有blankspace。

,然後計算長度,並分配新的緩衝區:

len = strlen (buffer); 
string = malloc (sizeof (char) * len + 1); 
strcpy (string, buffer); 
. 
. 
. 
free (string); 

編輯

這是一個方式,我摸索出:

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

int main (void) 
{ 
    char *buffer[10]; /* temporary buffers 10 nos, or make this dynamically allocated */ 
    char *main_str; /* The main string to work with after input */ 
    int k, i=0, n, retval; 

    while (1) 
    { 
    buffer[i] = malloc (sizeof (char) * 16); /* allocate buffer size 16 */ 
    scanf ("%15[^\n]%n", buffer[i], &n);  /* input length 15 string + 1 byte for null */ 
    if (n<16)        /* Buffer is not filled and end of string reached */ 
     break; 
    n=0;          /* reinitialize n=0 for next iteration. to make the process work if the length of the string is exactly the sizeof the buffer */ 
    i++; 
    } 
    /* need to fix the while loop so that the buffer array does not overflow and protect it from doing so */ 

    /* allocate buffer of exact size of the string */ 
    main_str = malloc (sizeof (char) * 16 * i + strlen (buffer[i])); 

    /* copy the segmented string into the main string to be worked with 
    * and free the buffers 
    */ 
    strcpy (main_str, ""); 
    for (k=0; k<=i; k++) 
    { 
    strcat (main_str, buffer[k]); 
    free (buffer[k]); 
    } 

    /* work with main string */ 
    printf ("\n%s", main_str); 

    /* free main string */ 
    free (main_str); 

    return 0; 
} 

您需要修改的代碼在某些情況下停止崩潰,但這應該回答你的問題。

+0

但是,這仍然會剪輯字符串。即使我爲原始字符串創建了一個非常大的緩衝區,仍然有可能輸入更大。 – Josh

+0

輸入可能是無限的,但您需要採取一些上限。或者您可以製作自己的輸入例程,以保持軌道和分配塊或根據需要使用多個緩衝區。 – phoxis

+0

我該怎麼做:)? – Josh

1

不與gets()改爲使用fgets()

您無法安全地通過gets()獲得用戶輸入。

您需要在循環中使用fgets()(或fgetc())。

+1

另請注意'gets()'將在下一個C標準IIRC中被棄用。 – ninjalj

+0

即使使用fgets(),這些緩衝區也不會足夠大以容納輸入。如果即時通訊從標準輸入讀取,循環中的fgetc()不起作用,除非我完全錯誤。 – Josh

+0

你需要在循環內部「realloc」。 – pmg

1

請勿使用gets()。使用fgets(),以及大概需要多少緩衝空間。

fgets的優點是,如果你過去了,它只會寫最大數量的字符,並且不會破壞程序另一部分的內存。

char buff[100]; 
fgets(buff,100,stdin); 

只能讀取最多99個字符或直到遇到「\ n」。如果有空間,它會將新行讀入數組中。

1

動態分配緩衝區並使用fgets。如果你直接填充緩衝區,那麼它不夠大,所以使用realloc然後再fgets再擴大它(但是寫到字符串的末尾來維護你已經抓取的內容)。繼續這樣做,直到你的緩衝區比輸入大:

buffer = malloc(bufsize); 
do{ 
    GotStuff = fgets(buffer, bufsize, stdin)) 
    buffer[bufsize-1] = 0; 
    if (GotStuff && (strlen(buffer) >= bufsize-1)) 
    { 
     oldsize = bufsize; 
     buffer = realloc(bufsize *= 2); 
     GotStuff = fgets(buffer + oldsize, bufsize - oldsize, stdin) 
     buffer[bufsize-1] = 0; 
    } 
} while (GotStuff && (strlen(buffer) >= bufsize-1)); 
0

gets()說明問題 - 沒有了解目標緩衝器需要多大是存儲輸入的方式 - 是正是爲什麼圖書館電話在1999年的標準中被棄用,預計將完全從下一次修訂中消失;希望大多數編譯器能夠相對迅速地遵循。由一個圖書館功能引起的混亂局面比打破40年遺留代碼的前景更加可怕。

一個解決方案是使用fgets()和一個固定長度的緩衝區讀取輸入零碎,然後將其附加到可動態調整大小的目標緩衝區中。例如:

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

#define SIZE 512; 

char *getNextLine(FILE *stream, size_t *length) 
{ 
    char *output; 
    char input[SIZE+1]; 
    *length = 0; 
    int foundNewline = 0; 

    /** 
    * Initialize our output buffer 
    */ 
    if ((output = malloc(1)) != NULL); 
    { 
    *output = 0; 
    *length = 1; 
    } 
    else 
    { 
    return NULL; 
    } 

    /** 
    * Read SIZE chars from the input stream until we hit EOF or 
    * see a newline character 
    */ 
    while(fgets(input, sizeof input, stream) != NULL && !foundNewline) 
    { 
    char *newline = strchr(input, '\n'); 
    char *tmp = NULL; 

    /** 
    * Strip the newline if present 
    */ 
    foundNewline = (newline != NULL); 
    if (foundNewline) 
    { 
     *newline = 0; 
    } 

    /** 
    * Extend the output buffer 
    */ 
    tmp = realloc(output, *length + strlen(input)); 
    if (tmp) 
    { 
     output = tmp; 
     strcat(output, input); 
     *length += strlen(input); 
    } 
    } 
    return *output; 
} 

當輸入完成時,調用者將負責釋放緩衝區。

0

如果你在Unix平臺上,你應該使用getline()這是完全適合這種事情。

如果你的平臺沒有getline(),這裏有一些公共領域的代碼應該讓你使用它。這篇文章有點長,但那是因爲代碼嘗試去處理現實生活中的錯誤和情況(甚至不像現實生活中的內存耗盡)。

它可能不是最高性能的版本,也不是最優雅的版本。它使用fgetc()逐個挑選字符,並且它在讀取字符時將空終止符放到數據的末尾,每獲得一次就會有機會。但是,即使面對錯誤和大小數據集,我相信它也是正確的。它對我的目的足夠好。

我不是特別喜歡getline()接口,但是我使用它,因爲它是一個標準的排序。

下面將使用GCC(MinGW)和MSVC(作爲C++ - 它使用與語句混合的聲明,編譯爲C時MSVC仍不支持的聲明)。也許我會修復那一天。

#define _CRT_SECURE_NO_WARNINGS 1 

#include <assert.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.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 



ssize_t nx_getdelim(char **lineptr, size_t *n, int delim, FILE *stream); 
ssize_t nx_getline(char **lineptr, size_t *n, FILE *stream); 




/* 
    nx_getdelim_get_realloc_size() 

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



/* 
    nx_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 
    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; 

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

    ssize_t result = 0;  
    char* line = *lineptr; 
    size_t size = *n; 
    size_t count = 0; 
    int err = 0; 

    int ch; 

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