2009-11-29 37 views
5

Tôi biết trong C bạn có thể khai báo một chuỗi và số ký tự như dưới đây,Xác định chiều dài của Char String trong C - nếu người dùng đầu vào nội dung của chuỗi

char mystring[50]; 

với '50' là số ký tự .

Tuy nhiên, thủ tục thích hợp là gì nếu người dùng sẽ nhập nội dung của chuỗi (thông qua scanf ("% s", mystring);)? Tôi có để nguyên nó là,

char mystring[0]; 

để nó là '0' vì tôi không biết có bao nhiêu ký tự người dùng sẽ nhập?

Hoặc Tôi phải làm gì,

char mystring[400]; 

đưa lên đến 400 ký tự cho người sử dụng đầu vào?

Trả lời

6

Bạn đã gặp phải vấn đề chính xác với scanf() và% s - điều gì sẽ xảy ra khi bạn không biết có bao nhiêu đầu vào?

Nếu bạn thử chạy char mystring[0];, chương trình của bạn sẽ biên dịch tốt. Nhưng bạn sẽ luôn luôn segfault. Bạn đang tạo một mảng có kích thước 0, vì vậy khi bạn cố gắng đặt một thứ gì đó vào mảng đó, bạn sẽ ngay lập tức thoát khỏi giới hạn cho chuỗi của bạn (vì không có bộ nhớ nào được phân bổ) - đó là một segfault.

Vì vậy, điểm 1: bạn phải luôn phân bổ kích thước cho chuỗi của mình. Tôi có thể nghĩ đến rất ít trường hợp (được, không có) nơi bạn muốn nói char mystring[0] thay vì char *mystring.

Tiếp theo, khi bạn sử dụng scanf, bạn không bao giờ muốn sử dụng trình chỉ định "% s" - vì điều này sẽ không thực hiện bất kỳ kiểm tra giới hạn nào về kích thước của chuỗi. vì vậy ngay cả khi bạn có:

char mystring[512]; 
scanf("%s", mystring); 

nếu người dùng nhập hơn 511 ký tự (kể từ thứ 512 là \ 0), bạn sẽ vượt ra ngoài giới hạn của mảng.Cách khắc phục điều này là:

scanf("%511s", mystring); 

Điều này là tất cả để nói rằng C không có cơ sở để tự động thay đổi kích thước một chuỗi nếu có nhiều đầu vào hơn bạn mong đợi. Đây là loại điều bạn phải làm bằng tay.

Một cách để giải quyết vấn đề này là sử dụng fgets().

Bạn có thể nói:

while (fgets(mystring, 512, stdin)) 
{ 
    /* process input */ 
} 

Sau đó, bạn có thể sử dụng sscanf() để phân tích mystring

Hãy thử đoạn mã trên, với một chuỗi có độ dài 5. Sau 4 nhân vật đã được đọc, mã mà lặp lại một lần nữa để lấy phần còn lại của đầu vào. "Đang xử lý" có thể bao gồm mã để phân bổ lại chuỗi là kích thước lớn hơn và sau đó nối đầu vào mới nhất từ ​​fgets().

Mã trên không hoàn hảo - nó sẽ làm cho chương trình của bạn lặp và xử lý bất kỳ độ dài chuỗi vô hạn nào, vì vậy bạn có thể muốn có một số giới hạn cứng bên trong (ví dụ, lặp tối đa 10 lần).

+0

t nên được thêm rằng% s đọc từ, chứ không phải toàn bộ chuỗi. Bởi vì chuỗi định dạng scanf sử dụng dấu cách và dòng mới làm dấu phân tách.Trong trường hợp này, hãy sử dụng% c thay thế (với chiều rộng trường), hoặc fgets như bạn đã đề cập. Trong trường hợp% c với chiều rộng trường, hãy nhớ khởi tạo toàn bộ chuỗi bộ đệm về 0. –

+0

Chương trình sẽ không luôn luôn segfault. Trong thực tế, có lẽ không phải hầu hết thời gian. Chương trình của bạn có thể sẽ bị âm thầm. Không phải là C đáng yêu sao? :-) –

2

Người dùng sẽ luôn có thể nhập nhiều ký tự hơn, do đó làm tràn bộ đệm của bạn (một nguồn lỗ hổng bảo mật phổ biến). Tuy nhiên, bạn có thể chỉ định "chiều rộng trường" cho scanf, như vậy:

scanf("%50s", mystring); 

Trong trường hợp này, bộ đệm của bạn phải là 51 ký tự, để tính toán trường 50 ký tự cộng với dấu kết thúc null. Hoặc làm cho bộ đệm của bạn 50 ký tự và nói với scanf 49 là chiều rộng.

+0

nhưng khi khai báo chuỗi, tôi có nên chỉ định '0' hoặc một số lớn không? – HollerTrain

+1

Bạn nên chỉ định ít nhất 51, trong ví dụ này. (Độ dài + 1 cho null terminator.) – Thanatos

+0

ok. như vậy là liệt kê nó như là '0' khi khai báo chuỗi không đúng mã hóa? Vấn đề của tôi là tôi không biết có bao nhiêu người dùng sẽ nhập nhưng đồng thời muốn tìm hiểu phương pháp đúng ... – HollerTrain

2

Có một hàm gọi là ggets() không phải là một phần của thư viện C chuẩn. Đó là một chức năng khá đơn giản. Nó khởi tạo một mảng char bằng cách sử dụng malloc(). Sau đó nó đọc các ký tự từ stdin một char tại một thời điểm. Nó theo dõi xem có bao nhiêu ký tự được đọc và mở rộng mảng char với realloc() khi nó hết dung lượng.

Nó có sẵn ở đây: http://cbfalconer.home.att.net/download/index.htm

tôi sẽ đề nghị bạn đọc mã và tái thực hiện chính mình.

0

Các thực hành thông thường trong C là sử dụng cái gì đó như GNU readline hoặc có lẽ NetBSD editline, aka libedit. (Same API, thực hiện khác nhau và bản quyền phần mềm.)

Đối với một chương trình bài tập đơn giản hay, bạn có thể về mặt lý thuyết cho một chiều rộng lĩnh vực để scanf , nhưng thực hành thông thường hơn là fgets() với mảng có chiều rộng cố định và sau đó chạy sscanf() trên đó. Bằng cách này, bạn kiểm soát số lượng các dòng được đọc.

0

Ví dụ: nếu người dùng nhập tên của họ thì bạn không phải lúc nào cũng an toàn tối đa kích thước 'bí ẩn' là 35 ký tự vì một số người có tên thật dài. Bạn không muốn tiếp cận trường hợp người dùng không thể nhập thông tin bạn đang yêu cầu, đầy đủ. Cách đúng đắn để làm điều đó sẽ là có một bộ đệm tạm thời với kích thước rất lớn sẽ bao gồm tất cả các đầu vào có thể bởi người dùng. Một khi người dùng nhập thông tin và nó được lưu vào bộ đệm, sau đó bạn chuyển các ký tự từ bộ đệm đến bí ẩn trong khi cắt bỏ tất cả khoảng trống thừa ở cuối bộ đệm. Bạn sẽ có thể cho biết kích thước bạn cần cho 'mystring' chính xác và bạn có thể malloc chỉ số lượng không gian cho nó và loại bỏ bộ đệm. Bằng cách này bạn sẽ không sử dụng một chuỗi sử dụng bộ nhớ nhiều hơn cho phần còn lại của chương trình ... bạn sẽ chỉ sử dụng một chuỗi với số lượng bộ nhớ bạn cần.

+0

Bạn vẫn phải thực hiện một số loại kiểm tra để đảm bảo rằng những gì người dùng nhập không lớn hơn bộ đệm được cấp phát trong các trường hợp rất hiếm hoặc khi ai đó đang cố gắng khai thác chương trình của bạn. –

1

Đây là mã cbfalconer của (http://cbfalconer.home.att.net/download/index.htm) với một vài thay đổi nhỏ và biên soạn thành một tập tin:

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

#define INITSIZE 112 /* power of 2 minus 16, helps malloc */ 
#define DELTASIZE (INITSIZE + 16) 

enum {OK = 0, NOMEM}; 

int fggets(char* *ln, FILE *f) 
{ 
    int  cursize, ch, ix; 
    char *buffer, *temp; 

    *ln = NULL; /* default */ 
    if (NULL == (buffer = malloc(INITSIZE))) return NOMEM; 
    cursize = INITSIZE; 

    ix = 0; 
    while ((EOF != (ch = getc(f))) && ('\n' != ch)) { 
     if (ix >= (cursize - 1)) { /* extend buffer */ 
     cursize += DELTASIZE; 
     if (NULL == (temp = realloc(buffer, (size_t)cursize))) { 
      /* ran out of memory, return partial line */ 
      buffer[ix] = '\0'; 
      *ln = buffer; 
      return NOMEM; 
     } 
     buffer = temp; 
     } 
     buffer[ix++] = ch; 
    } 
    if ((EOF == ch) && (0 == ix)) { 
     free(buffer); 
     return EOF; 
    } 

    buffer[ix] = '\0'; 
    if (NULL == (temp = realloc(buffer, (size_t)ix + 1))) { 
     *ln = buffer; /* without reducing it */ 
    } 
    else *ln = temp; 
    return OK; 
} /* fggets */ 
/* End of ggets.c */ 

int main(int argc, char **argv) 
{ 
    FILE *infile; 
    char *line; 
    int cnt; 

    //if (argc == 2) 
     //if ((infile = fopen(argv[1], "r"))) { 
     cnt = 0; 
     while (0 == fggets(&line, stdin)) { 
      fprintf(stderr, "%4d %4d\n", ++cnt, (int)strlen(line)); 
      (void)puts(line); 
      free(line); 
     } 
     return 0; 
     //} 
    //(void)puts("Usage: tggets filetodisplay"); 
    //return EXIT_FAILURE; 
} /* main */ 
/* END file tggets.c */ 

Tôi đã thử nghiệm nó ra và nó sẽ luôn luôn cung cấp cho bạn những gì bạn muốn.

+0

Về cơ bản, để lấy mã gốc của mình, bạn bỏ ghi chú và thay thế stdin bằng mật khẩu trong cuộc gọi. –

Các vấn đề liên quan