[C/C++]get() and fget() in C/C++ – Cảnh báo khi dùng gets() – Warning when use gets()

Thông thường để nhập một biến ta dùng hàm scanf(), hàm này cũng dùng để nhập 1 xâu ký tự tuy nhiên khi gặp các dấu trắng (dấu cách, enter,…) thì không đọc nữa và chuỗi bị cắt từ đó, VD nhập “nguyen van quan” thì ta chỉ nhận được “nguyen”.
Cách khắc phục ở đây là ta dùng hàm gets() để nhập chuỗi. 😀 Khi đó ta sẽ nhận được chuỗi nguyên vẹn.
Tuy nhiên lại nảy sinh một điều khá rắc rối là nếu trong bộ đệm (tệp stdin) mà có chứa các ký tự chưa được đọc (VD các ký tự trắng do scanf() chưa đọc được và vẫn còn trong tệp stdin) thì gets() lại nhận ngay các ký tự đó và ta không nhập được chuỗi (Hiện tượng trôi lệnh). Và cách xử lý của chúng ta là trước khi dùng gets() thì dùng thêm 1 lệnh xóa bộ đêm stdin đó là fflush(stdin). Khi đó ta đã có thể nhập chuỗi 1 cách ổn định

 
#include <stdio.h>
  
int main()
{
    int age;
    char name [256];
    printf ("Insert your age: ");
    scanf("%d",&age);
    fflush(stdin);
    printf ("Insert your name: ");
    gets (name);
    printf ("Your name is: %s and you are %d years oldn",name, age);
    return 0;
}

Tuy nhiên vấn đề lại một lần nữa đặt ra là trong windows chúng ta sẽ không gặp 1 cảnh báo nào trong code trên, nhung trong Linux thì đoạn code trên mặc dù không có lỗi nhưng lại phát sinh 2 vấn đề. Thứ nhất là khi biên dịch máy sẽ cảnh bảo ” warning: ‘char* gets(char*)’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations] ” và thứ hai là lệnh fflush(stdin) sẽ không có tác dụng, ta lại bị trôi lệnh.

Trước tiên ta đi xử lý cảnh báo này. Trong cuốn sách “Secure Programming Cookbook for C and C++” có một đoạn ở chương 3.1 có nói rằng: gets() là một hàm nằm trong bộ thư viện cũ của C. Hàm này tuy dùng để nhập chuỗi rất tiện nhưng có một điểm vô cùng nguy hiểm là không kiểm tra buffer size của dữ liệu khi nhập liệu. Điều đó sẽ làm cho chương trình bị crash hay nếu nặng hơn là cả hệ thống sụp đổ nếu dữ liệu nhập vào vượt quá khai báo hay xử lí. Và nhất là trên linux, sự quản lí bộ nhớ rất chặt chẽ (hơn windows nhiều !) nên việc cảnh báo là hoàn toàn chính xác, không có cảnh báo mới là có vấn đề !
Bạn có thể thử bằng cách compile chương trình của bạn trong windows. Trong hầu hết các trường hợp (sử dụng nhiều trình biên dịch khác nhau) không hề có cảnh báo. Nhưng linux thì khác, vì đó là chính là cơ chế đảm bảo tính ổn định của hệ thống, đảm bảo cho hệ thống không bị ăn đạn lạc của người lập trình , mặt khác GCC là một trình biên dịch tuy miễn phí nhưng rất hiện đại, nó luôn được sửa lỗi và cập nhật những chuẩn mới nhất của C/C++
(Bình luận của drnoxxx tại ddth.com)
Vậy xử lý nó thế nào? Một giải pháp cho chúng ta là sử dụng fgets() để nhập. fgets() là hàm cho phép chúng ta nhập từ file và có chỉ định kịch thước chuỗi khi nhập. Code minh họa sử dụng file để nhập

#include <stdio.h>

int main()
{
	FILE * pFile;
	char mystring [100];
	pFile = fopen ("myfile.txt" , "r");
	if (pFile == NULL) perror ("Error opening file");
	else
	{
		if ( fgets (mystring , 100 , pFile) != NULL ) //nhap chuoi toi da 100 phan tu tu file
		puts (mystring);
		fclose (pFile);
   }
   return 0;
}

Nhưng nếu bạn muốn sử dunụ fgets() nhập từ bàn phím thì sao? đơn giản thôi, bạn không cần khai báo file như trên, chỉ cần thay file trên bằng stdin là được rồi !

fgets (mystring , 100 , stdin);

Giải quyết vấn đề thứ 2 là xóa bộ đệm nếu không dùng được fflush(stdin) ? Câu trả lời đơn giản là chỉ cần đọc tất cả các khoảng trắng từ stdin ra 1 biến nào đó và tiếp tục công việc của mình là được rồi.
Nhưng một vấn đề nữa lại nảy sinh là khi dùng fgets() nhập từ bàn phím thì khi gõ xong chuỗi ta phải ấn enter và no sẽ đọc luôn phím enter của mình vào chuỗi, khi xuất ra thì con trỏ tự động xuống dòng, và điều này trở nên khó khăn khi chúng ta muốn viết 2 xuâu cùng 1 dòng. Giải quyết vấn đề này ta dùng thêm một kỹ thuật nữa đó là gán NULL cho ký tự cuối cùng của chuối nhập vào bằng lệnh

string[strlen(string)-1] = '&#092;&#048;'; //chu ý khai báo string.h

Dưới đây là code sử dụng fgets().

#include <stdio.h>
#include <string.h>
int main()
{
	int age;
	char name [256];
	printf ("Insert your age: ");
	scanf("%d",&age);
	if (fgets (name,256,stdin) != NULL); //xoa bo dem
	//fflush(stdin); //khong co tac dung xoa bo dem
	printf ("Insert your name: ");
	if (fgets (name,256,stdin) != NULL); //nhap ten
	name[strlen(name)-1] = '&#092;&#048;';
	printf ("Your name is: %s and you are %d years oldn",name, age);
	return 0;
}