คู่มือสไตล์ linux ให้เหตุผลเฉพาะในการใช้งานgoto
ซึ่งสอดคล้องกับตัวอย่างของคุณ:
https://www.kernel.org/doc/Documentation/process/coding-style.rst
เหตุผลในการใช้ gotos คือ:
- ข้อความที่ไม่มีเงื่อนไขนั้นง่ายต่อการเข้าใจและติดตาม
- การทำรังจะลดลง
- ข้อผิดพลาดโดยไม่อัปเดตจุดออกแต่ละจุดเมื่อมีการป้องกันแก้ไข
- บันทึกการทำงานของคอมไพเลอร์เพื่อเพิ่มประสิทธิภาพรหัสซ้ำซ้อนออกไป;)
ข้อจำกัดความรับผิดชอบฉันไม่ควรแบ่งปันงานของฉัน ตัวอย่างที่นี่มีการประดิษฐ์เล็กน้อยดังนั้นโปรดอดทนกับฉัน
นี่เป็นสิ่งที่ดีสำหรับการจัดการหน่วยความจำ ฉันเพิ่งทำงานกับโค้ดที่มีการจัดสรรหน่วยความจำแบบไดนามิก (เช่นchar *
ฟังก์ชันที่ส่งคืนโดยฟังก์ชัน) ฟังก์ชั่นที่ดูเส้นทางและตรวจสอบว่าเส้นทางนั้นถูกต้องหรือไม่โดยการแยกโทเค็นของเส้นทาง:
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;
ตอนนี้สำหรับฉันรหัสต่อไปนี้ดีกว่าและง่ายกว่าถ้าคุณต้องการเพิ่ม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;
ตอนนี้โค้ดมีปัญหาอื่น ๆ อีกมากมายซึ่งก็คือ N อยู่ที่ใดที่หนึ่งที่สูงกว่า 10 และฟังก์ชั่นมีมากกว่า 450 บรรทัดโดยมีความซ้อนกัน 10 ระดับในบางสถานที่
แต่ฉันเสนอหัวหน้างานของฉันให้ refactor ซึ่งฉันทำและตอนนี้มันเป็นฟังก์ชั่นมากมายที่สั้นและพวกเขาทั้งหมดมีสไตล์ของ 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;
}
หากเราพิจารณาสิ่งที่เทียบเท่าโดยไม่มี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;
}
ให้ฉันในกรณีแรกก็เป็นที่ชัดเจนกับผมว่าถ้าฟังก์ชั่นแรกผลตอบแทนที่เราอยู่ที่นี่และเรากำลังกลับมาNULL
0
ในกรณีที่สองฉันต้องเลื่อนลงเพื่อดูว่าถ้ามีฟังก์ชั่นทั้งหมด ที่ได้รับคนแรกบ่งบอกสิ่งนี้กับฉันโวหาร (ชื่อ " out
") และคนที่สองทำ syntactically คนแรกยังคงชัดเจนมากขึ้น
นอกจากนี้ฉันชอบที่จะมีfree()
งบในตอนท้ายของฟังก์ชั่น ส่วนหนึ่งเป็นเพราะในประสบการณ์ของฉันfree()
ข้อความที่อยู่ตรงกลางของฟังก์ชั่นมีกลิ่นเหม็นและบอกฉันว่าฉันควรสร้างรูทีนย่อย ในกรณีนี้ฉันสร้างขึ้นvar1
ในฟังก์ชั่นของฉันและไม่สามารถทำได้free()
ในรูทีนย่อย แต่นั่นเป็นเหตุผลว่าทำไมgoto out_free
สไตล์ goto out จึงใช้งานได้จริง
ฉันคิดว่าโปรแกรมเมอร์ต้องถูกนำขึ้นมาเชื่อว่าgoto
เป็นสิ่งที่ชั่วร้าย จากนั้นเมื่อโตพอพวกเขาควรเรียกดูซอร์สโค้ด Linux และอ่านคู่มือสไตล์ลินุกซ์
ฉันควรเพิ่มว่าฉันใช้สไตล์นี้อย่างสม่ำเสมอทุกฟังก์ชั่นมี int retval
, out_free
label และ out label เนื่องจากความสอดคล้องกับโวหารทำให้การอ่านง่ายขึ้น
โบนัส: ทำลายและดำเนินการต่อ
สมมติว่าคุณมีวงวนสักครู่
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);
}
มีสิ่งอื่น ๆ ที่ผิดกับรหัสนี้ แต่สิ่งหนึ่งคือคำสั่งที่ยังคง ฉันอยากจะเขียนใหม่ทั้งหมด แต่ฉันได้รับมอบหมายให้แก้ไขมันด้วยวิธีเล็ก ๆ น้อย ๆ ฉันต้องใช้เวลาหลายวันกว่าจะสร้างมันใหม่ในแบบที่ทำให้ฉันพึงพอใจ แต่การเปลี่ยนแปลงที่เกิดขึ้นจริงนั้นเกี่ยวกับการทำงานครึ่งวัน ปัญหาคือว่าแม้ว่าเรา ' continue
' เรายังคงต้องเป็นอิสระและvar1
var2
ฉันต้องเพิ่มvar3
และมันทำให้ฉันต้องการอ้วกที่จะต้องสะท้อนงบ () ฟรี
ฉันเป็นนักศึกษาฝึกงานครั้งใหม่ แต่ฉันได้ดูซอร์สโค้ด linux เพื่อความสนุกในขณะนั้นฉันจึงถามหัวหน้างานของฉันว่าฉันสามารถใช้คำสั่ง goto ได้หรือไม่ เขาตอบว่าใช่และฉันก็ทำสิ่งนี้:
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);
}
ฉันคิดว่ายังคงโอเคที่ดีที่สุด แต่สำหรับฉันพวกเขาเป็นเหมือน goto ที่มีป้ายกำกับที่มองไม่เห็น เช่นเดียวกันสำหรับการหยุดพัก ฉันยังคงต้องการดำเนินการต่อหรือหยุดพักเว้นแต่กรณีนี้จะบังคับให้คุณทำการแก้ไขในหลาย ๆ สถานที่
และฉันควรจะเพิ่มด้วยว่าการใช้goto next;
และnext:
ฉลากนี้ไม่เป็นที่น่าพอใจสำหรับฉัน สิ่งเหล่านี้ดีกว่าการทำมิเรอร์free()
และcount++
ข้อความ
goto
มีความผิดพลาดอยู่เกือบตลอดเวลา แต่ก็ต้องรู้ว่าเมื่อไรจะใช้งานได้ดี
สิ่งหนึ่งที่ฉันไม่ได้พูดถึงคือการจัดการข้อผิดพลาดที่ได้รับคำตอบอื่น ๆ
ประสิทธิภาพ
หนึ่งสามารถดูการใช้งานของ strtok () http://opensource.apple.com //source/Libc/Libc-167/string.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 */
}
โปรดแก้ไขฉันหากฉันผิด แต่ฉันเชื่อว่าcont:
ฉลากและgoto cont;
ข้อความสั่งนั้นมีไว้เพื่อการปฏิบัติงาน (แน่นอนว่าพวกเขาไม่ได้ทำให้อ่านรหัสได้มากขึ้น) พวกเขาสามารถถูกแทนที่ด้วยรหัสที่อ่านได้โดยการทำ
while( isDelim(*s++,delim));
เพื่อข้ามตัวคั่น แต่เพื่อให้เร็วที่สุดและหลีกเลี่ยงการเรียกใช้ฟังก์ชันที่ไม่จำเป็นพวกเขาทำเช่นนี้
ฉันอ่านบทความโดย Dijkstra และฉันพบว่ามันค่อนข้างลึกลับ
google "คำสั่ง dijkstra goto ถือว่าเป็นอันตราย" เพราะฉันไม่มีชื่อเสียงพอที่จะโพสต์มากกว่า 2 ลิงก์
ฉันเคยเห็นมันอ้างว่าเป็นเหตุผลที่จะไม่ใช้ goto และการอ่านมันไม่ได้เปลี่ยนแปลงอะไรเลยตราบใดที่การใช้ goto ของฉันแน่นแฟ้นขึ้น
ภาคผนวก :
ฉันคิดกฎระเบียบเรียบร้อยในขณะที่คิดถึงเรื่องทั้งหมดนี้เกี่ยวกับการดำเนินการต่อและการหยุดพัก
- หากในขณะที่คุณมีการดำเนินการต่อจากนั้นร่างกายของห่วงขณะที่ควรจะเป็นฟังก์ชั่นและดำเนินการต่อเป็นคำสั่งกลับมา
- หากใน a while loop คุณมีคำสั่ง break ดังนั้น while while นั้นควรจะเป็นฟังก์ชั่นและตัวแบ่งควรเป็นคำสั่ง return
- หากคุณมีทั้งคู่แล้วอาจมีบางอย่างผิดปกติ
เป็นไปไม่ได้เสมอเนื่องจากปัญหาขอบเขต แต่ฉันพบว่าการทำเช่นนี้ทำให้ง่ายขึ้นมากในการให้เหตุผลเกี่ยวกับรหัสของฉัน ฉันสังเกตุเห็นว่าเมื่อใดก็ตามที่วงวนหนึ่งมีพักหรือต่อเนื่องมันทำให้ฉันรู้สึกแย่