Hướng dẫn kiểu linux đưa ra các lý do cụ thể để sử dụng goto
các ví dụ phù hợp với ví dụ của bạn:
https://www.kernel.org/doc/Documentation/ Process /oding-style.rst
Lý do để sử dụng gotos là:
- tuyên bố vô điều kiện dễ hiểu và làm theo
- làm tổ được giảm
- lỗi bằng cách không cập nhật các điểm thoát riêng lẻ khi thực hiện sửa đổi bị ngăn chặn
- lưu công cụ biên dịch để tối ưu hóa mã dự phòng đi;)
Tuyên bố từ chối trách nhiệm Tôi không được phép chia sẻ công việc của tôi. Các ví dụ ở đây là một chút giả tạo vì vậy hãy chịu đựng với tôi.
Điều này là tốt cho quản lý bộ nhớ. Gần đây tôi đã làm việc với mã có bộ nhớ được cấp phát động (ví dụ: được char *
trả về bởi một hàm). Một hàm xem xét một đường dẫn và xác định xem đường dẫn đó có hợp lệ hay không bằng cách phân tích các mã thông báo của đường dẫn:
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
free(var1);
free(var2);
...
free(varN);
return 1;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
free(var1);
free(var2);
...
free(varN);
return 1;
} else {
free(var1);
free(var2);
...
free(varN);
return 0;
}
}
token = strtok(NULL,delim);
}
free(var1);
free(var2);
...
free(varN);
return 1;
Bây giờ với tôi, đoạn mã sau đẹp hơn và dễ bảo trì hơn nếu bạn cần thêm varNplus1
:
int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
retval = 1;
goto out_free;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
retval = 1;
goto out_free;
} else {
retval = 0;
goto out_free;
}
}
token = strtok(NULL,delim);
}
out_free:
free(var1);
free(var2);
...
free(varN);
return retval;
Bây giờ mã có tất cả các loại vấn đề khác với nó, cụ thể là N ở đâu đó trên 10 và hàm có hơn 450 dòng, với 10 cấp độ lồng nhau ở một số nơi.
Nhưng tôi đã đề nghị người giám sát của tôi cấu trúc lại nó, điều mà tôi đã làm và bây giờ nó là một loạt các chức năng đều ngắn và tất cả chúng đều có kiểu linux
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 == NULL ){
retval = 0;
goto out;
}
if( isValid(var1) ){
retval = some_function(var1);
goto out_free;
}
if( isGood(var1) ){
retval = 0;
goto out_free;
}
out_free:
free(var1);
out:
return retval;
}
Nếu chúng ta xem xét tương đương mà không có goto
s:
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 != NULL ){
if( isValid(var1) ){
retval = some_function(var1);
} else {
if( isGood(var1) ){
retval = 0;
}
}
free(var1);
} else {
retval = 0;
}
return retval;
}
Đối với tôi, trong trường hợp đầu tiên, rõ ràng với tôi rằng nếu hàm đầu tiên quay trở lại NULL
, chúng tôi sẽ ra khỏi đây và chúng tôi sẽ quay trở lại 0
. Trong trường hợp thứ hai, tôi phải cuộn xuống để xem if có chứa toàn bộ hàm không. Cấp cái đầu tiên chỉ ra điều này cho tôi theo kiểu cách (tên " out
") và cái thứ hai thực hiện theo cú pháp. Cái đầu tiên vẫn còn rõ ràng hơn.
Ngoài ra, tôi rất thích có các free()
câu lệnh ở cuối hàm. Đó là một phần bởi vì, theo kinh nghiệm của tôi, các free()
câu lệnh ở giữa các hàm có mùi khó chịu và cho tôi biết rằng tôi nên tạo một chương trình con. Trong trường hợp này, tôi đã tạo var1
trong hàm của mình và không thể free()
theo chương trình con, nhưng đó là lý do tại sao goto out_free
, kiểu goto out rất thiết thực.
Tôi nghĩ rằng các lập trình viên cần phải được đưa lên để tin rằng đó goto
là xấu xa. Sau đó, khi họ đủ trưởng thành, họ nên duyệt mã nguồn Linux và đọc hướng dẫn kiểu linux.
Tôi nên thêm rằng tôi sử dụng phong cách này rất nhất quán, mọi chức năng đều có int retval
, out_free
nhãn và nhãn ngoài. Bởi vì sự nhất quán về phong cách, khả năng đọc được cải thiện.
Tiền thưởng: Nghỉ giải lao và tiếp tục
Nói rằng bạn có một vòng lặp while
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
if( functionC(var1, var2){
count++
continue;
}
...
a bunch of statements
...
count++;
free(var1);
free(var2);
}
Có những điều khác với mã này, nhưng có một điều là tuyên bố tiếp tục. Tôi muốn viết lại toàn bộ, nhưng tôi được giao nhiệm vụ sửa đổi nó theo một cách nhỏ. Tôi sẽ mất nhiều ngày để cấu trúc lại nó theo cách làm tôi hài lòng, nhưng sự thay đổi thực sự là khoảng nửa ngày làm việc. Vấn đề là ngay cả khi chúng continue
ta vẫn cần giải phóng var1
và var2
. Tôi đã phải thêm một var3
, và nó khiến tôi muốn ngất đi khi phải phản ánh các câu lệnh free ().
Tôi là một thực tập sinh tương đối mới vào thời điểm đó, nhưng tôi đã xem mã nguồn linux để giải trí một thời gian, vì vậy tôi đã hỏi người giám sát của mình nếu tôi có thể sử dụng câu lệnh goto. Anh ấy nói có, và tôi đã làm điều này:
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
var3 = newFunction(line,count);
if( functionC(var1, var2){
goto next;
}
...
a bunch of statements
...
next:
count++;
free(var1);
free(var2);
}
Tôi nghĩ tiếp tục là tốt nhất nhưng với tôi họ giống như một goto với một nhãn vô hình. Cùng đi cho nghỉ. Tôi vẫn thích tiếp tục hoặc phá vỡ trừ khi, như trường hợp ở đây, nó buộc bạn phải phản chiếu các sửa đổi ở nhiều nơi.
Và tôi cũng nên thêm rằng việc sử dụng này goto next;
và next:
nhãn không đạt yêu cầu đối với tôi. Chúng chỉ tốt hơn là phản chiếu free()
các count++
tuyên bố và tuyên bố.
goto
Hầu như luôn luôn sai, nhưng người ta phải biết khi nào chúng tốt để sử dụng.
Một điều mà tôi đã không thảo luận là xử lý lỗi đã được bao phủ bởi các câu trả lời khác.
Hiệu suất
Người ta có thể nhìn vào việc thực hiện strtok () http://opensource.apple.com//source/Libc/Libc-167/opes.subproj/strtok.c
#include <stddef.h>
#include <string.h>
char *
strtok(s, delim)
register char *s;
register const char *delim;
{
register char *spanp;
register int c, sc;
char *tok;
static char *last;
if (s == NULL && (s = last) == NULL)
return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
if (c == sc)
goto cont;
}
if (c == 0) { /* no non-delimiter characters */
last = NULL;
return (NULL);
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;) {
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0)
s = NULL;
else
s[-1] = 0;
last = s;
return (tok);
}
} while (sc != 0);
}
/* NOTREACHED */
}
Vui lòng sửa cho tôi nếu tôi sai, nhưng tôi tin rằng cont:
nhãn và goto cont;
tuyên bố là có hiệu suất (chắc chắn họ không làm cho mã dễ đọc hơn). Chúng có thể được thay thế bằng mã có thể đọc được bằng cách làm
while( isDelim(*s++,delim));
để bỏ qua các dấu phân cách. Nhưng để nhanh nhất có thể và tránh các cuộc gọi chức năng không cần thiết, họ làm theo cách này.
Tôi đọc bài báo của Dijkstra và tôi thấy nó khá bí truyền.
google "dijkstra goto tuyên bố có hại" vì tôi không đủ uy tín để đăng hơn 2 liên kết.
Tôi đã xem nó được trích dẫn là một lý do không sử dụng goto và đọc nó đã không thay đổi bất cứ điều gì cho đến khi việc sử dụng goto của tôi được che giấu.
Phụ lục :
Tôi đã đưa ra một quy tắc gọn gàng trong khi suy nghĩ về tất cả những điều này về tiếp tục và phá vỡ.
- Nếu trong một vòng lặp while, bạn có một tiếp tục, thì phần thân của vòng lặp while sẽ là một hàm và tiếp tục là một câu lệnh return.
- Nếu trong một vòng lặp while, bạn có một câu lệnh break, thì chính vòng lặp while sẽ là một hàm và break sẽ trở thành một câu lệnh return.
- Nếu bạn có cả hai, thì có thể có gì đó không ổn.
Không phải lúc nào cũng có thể do các vấn đề về phạm vi nhưng tôi đã thấy rằng việc này giúp cho việc lập luận về mã của tôi dễ dàng hơn nhiều. Tôi đã nhận thấy rằng bất cứ khi nào một vòng lặp nghỉ ngơi hoặc tiếp tục, nó mang lại cho tôi một cảm giác tồi tệ.